Skip to content

Schema composition with directives that use complex argument types #2162

@azaxarov

Description

@azaxarov

Greetings, Apollo team!

Consider the following type definitions that will be used to build an Apollo Federation schema:

directive @something(rules: [Rule!]!) on OBJECT | FIELD_DEFINITION

enum Number {
  one
  two
}

input Rule {
  numbers: [Number!]!
}

type Something {
  id: ID!
}

extend type Query {
  getSomething(id: ID!): Something!
    @something(rules: [{ numbers: [one, two] }])
}

A single directive called @something is declared, that takes in a non-nullable list of GraphQL input object types. This directive can then be used on objects and field definitions to perform some task. An example of a similar directive can be found here: https://docs.amplify.aws/cli/graphql-transformer/auth#auth.

The problem arises during composition of the schema, namely that input type Rule is being registered twice, leading to this error: https://github.com/graphql/graphql-js/blob/00eab30fb269b6e2a4e8e61d097d3e249319420e/src/type/schema.js#L223.

I originally posted the issue in graphql-js repository: graphql/graphql-js#2845

Upon further investigation, it appears that the actual problem occurs during schema transformation:

typeMap[newType.name] = recreateNamedType(newType);

The problem is that recreateNamedType creates a new instance of a named type and changes its internal JS object reference. However, graphql package insists on that JS references are kept intact during schema composition, since it uses a Set.has check to ensure types are not registered twice.

As far as I can see, there is no need to use recreateNamedType if type remains the same in

typeMap[newType.name] = recreateNamedType(newType);

Perhaps replacing the following code:

// Returning `undefined` keeps the old type.
const newType = result || oldType;
typeMap[newType.name] = recreateNamedType(newType);

with the following can fix the problem?

// Returning `undefined` keeps the old type.
typeMap[oldType.name] = oldType || recreateNamedType(result);

At least it seems to work when I change it in my local node_modules.

I published an example repository to illustrate the problem: https://github.com/azaxarov/graphql-service-with-complex-directive-parameter

Hope to hear from you guys!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions