Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

delegateToSchema breaks enum resolution of internal values #2214

Closed
mjbcopland opened this issue Nov 12, 2020 · 4 comments
Closed

delegateToSchema breaks enum resolution of internal values #2214

mjbcopland opened this issue Nov 12, 2020 · 4 comments

Comments

@mjbcopland
Copy link

mjbcopland commented Nov 12, 2020

Motivation

Using GraphQL Mesh with the gRPC handler which has enums with numeric internal values. However I believe that this use case has only discovered the issue and is not exclusive to it.

The issue

Delegating to a subschema with enum types with internal values causes an error when fields of that type are resolved in the outer schema. This root cause of this appears to be an intentional design decision:

[Enum] values might be internally mapped to a set of integers. However, these details don't leak out to the client, which can operate entirely in terms of the string names of the enum values.

Consider a simple enum with a single value. Note that the internal value is the number 1 and not the string "ONE".

const EnumType = new GraphQLEnumType({
  name: "Enum",
  values: { ONE: { value: 1 } },
});

The subschema does not leak the internal values to the delegating schema, however the delegating schema cannot serialise the values a second time, causing an error when it attempts to do so:

GraphQLError [Object]: Enum "Enum" cannot represent value: "ONE"

It should have the internal value 1 but instead has the external value "ONE". Presumably this issue occurs for any scalar type with non-idempotent serialisation, however I have not tested this.

Intended outcome

{
  "data": {
    "object": {
      "value": "ONE"
    }
  }
}

Actual outcome

{
  "errors": [
    {
      "message": "Enum \"Enum\" cannot represent value: \"ONE\"",
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ],
      "path": [
        "object",
        "value"
      ]
    }
  ],
  "data": {
    "object": {
      "value": null
    }
  }
}

Steps to reproduce

Create a subschema with internal enum values and another schema with a resolver which delegates to said subschema. I have created a minimal reproduction here: https://github.com/mjbcopland/graphql-tools-issue-2214

Note that the query works as expected when executed directly against the subschema, but the issue occurs when trivially delegating to the subschema.

Workaround

I've managed to get this working by applying an additional transform which wraps TransformEnumValues by trivially transforming enums to their internal values. Should something like this be added to the existing default transforms? I haven't yet considered or tested any other scenarios or edge cases in the hope that this discussion might spark ideas for other possible solutions.

const enumValueTransformer = (typeName, externalValue, enumValueConfig) => {
  return [enumValueConfig.value, enumValueConfig];
};

This workaround can be seen on the branch workaround in commit 010192a.

@yaacovCR
Copy link
Collaborator

This is not a bug in delegateToSchema per se, which will reserialize to the outer schema internal value, at least when used within the schema stitching context.

Maybe it is as simple as helping mesh define those internal values as desired in the outer schema?

I am not 100% as to how mesh uses delegation to be honest.

Note that schema stitching reserializes in the outer schema during outer schema resolution, so if an enum is nested, reserializing of the nested value will not be performed by delegateToSchema, that is done in a later step by defaultMergedResolver.

@mjbcopland
Copy link
Author

mjbcopland commented Nov 12, 2020

reserializing [...] is done in a later step by defaultMergedResolver

Interesting. I will see if I can integrate defaultMergedResolver in my reproduction, and if that works I'll try it with the mesh too.

in the hope that this discussion might spark ideas for other possible solutions

Looks like you've pointed me in the right direction, thanks!

@mjbcopland
Copy link
Author

mjbcopland commented Nov 12, 2020

I am not 100% as to how mesh uses delegation to be honest

I traced the error to here and was able to reproduce with just a call to delegateToSchema, so assumed that's where the error was.

Using defaultMergedResolver in my reproduction works perfectly. Looks like the issue is with the mesh instead. Thanks for the quick help, @yaacovCR.

@alicialics
Copy link

@mjbcopland do you have an example of how to use defaultMergedResolver?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants