Apollo-server: Bug: It is impossible to implement input type validation directive without breaking Playground

Created on 6 Jul 2018  Â·  16Comments  Â·  Source: apollographql/apollo-server

How to reproduce:

Problem:

  • Either directive works, but Playgound doesn't. (If I follow the instructions then I get this error in the GraphQL Playground console: Invalid or incomplete schema, unknown type: LengthAtMost64.)
  • Or directive does not work, but introspection works. (If I add scalar LengthAtMost64 to my schema then the directive stops working.)

I tried both Apollo Stack v1 and v2.

The GraphQL Playground points to this file: http://cdn.jsdelivr.net/npm/[email protected]/build/static/node_modules/graphql/utilities/buildClientSchema.js
Namely, this function:

  function getNamedType(typeName) {
    if (typeDefCache[typeName]) {
      return typeDefCache[typeName];
    }
    var typeIntrospection = typeIntrospectionMap[typeName];
    if (!typeIntrospection) {
      throw new Error('Invalid or incomplete schema, unknown type: ' + typeName + '. Ensure ' + 'that a full introspection query is used in order to build a ' + 'client schema.');
    }
    var typeDef = buildType(typeIntrospection);
    typeDefCache[typeName] = typeDef;
    return typeDef;
  }

I found this bug by trying to use the @constraint directive.

I have no idea what to do.

Are there any other ways to implement input type validation directive?

documentation has-reproduction

Most helpful comment

Is there a chance that this is fixable in the foreseeable future?

All 16 comments

The directiveResolvers used to work as charm. Could you please add its support to Apollo Stack 2.0?

Currently the Apollo Stack 2 throws an error when I'm trying to create the schema with directiveResolvers myself:

SchemaDirectiveVisitor for @constraint must implement visitInputFieldDefinition method

    const server = new ApolloServer({
        schema: makeExecutableSchema({
            typeDefs,
            resolvers,
            directiveResolvers: {
                constraint(next, src, args, context) {
                    // anything here
                }
            }
        }),
...

If I fork graphql-tools and implement the visitInputFieldDefinition myself the directive resolver is never executed (just like in my previous message).

Ah interesting! I updated the example to compile. It appears that the LengthAtMost64 would need to be added to the schema by wrapType in order to ensure that the introspection query contains all of the scalar values. My first impression is that GraphQL Playground is overzealous about calling a schema invalid or incomplete, since it doesn't allow dynamic schema.

@benjamn do you know a way to add each scalar to the schema inside of a more specific visit method?

For security reasons the best solution would be to not leak LengthAtMost64 to Playground (client introspection) side. The LengthAtMost64 should stay internal to the server.

That's why directiveResolvers worked really well in the past. It did not create any types in my schema.

A temporary workaround for Apollo Stack 2.0 was found by @FrankSandqvist in here: https://github.com/confuser/graphql-constraint-directive/issues/2#issuecomment-404399563

TL;DR: make schema from resolvers+directives+typedefs, then merge more scalar typedefs, and then use the final schema in ApolloServer:

    const mainSchema = makeExecutableSchema({
        resolvers,
        typeDefs: myMainTypeDefs,
        schemaDirectives: { constraint: ConstraintDirective }
    });
    const scalarsOnlySchema = makeExecutableSchema({
        typeDefs: readFileSync(path/to/scalars).toString()
    });
    const schema = mergeSchemas({ schemas: [scalarsOnlySchema, mainSchema] });

    const server = new ApolloServer({ schema, introspection: true });

Boom! Now both your directive and Playground are working!

WARNING!
The above solution is a bit dirty.

  • You would need to add all your runtime generated directives to the path/to/scalars schema. So you might end up with loads of LengthAtMost64 and StringWithMaxLength50WithMinLength5ThatConformsToEMAIL etc rubbish types in your schema.
  • All those rubbish types are leaking (visible) to your API consumers. Which is what you don't want in most use cases.

Any solution or update on this?

@evans, It's not only breaks the Playground. It is also breaks custom scalars

in my case ("apollo-server-express": "2.0.4") enabling schemaDirectives leads to such errors:

"GraphQLError: Unknown type "AuthToken". Did you mean "OauthState"?"
1:"    at Object.NamedType (/home/_xxx_/node_modules/graphql/validation/rules/KnownTypeNames.js:66:29)"
2:"    at Object.enter (/home/_xxx_/node_modules/graphql/language/visitor.js:324:29)"
3:"    at Object.enter (/home/_xxx_/node_modules/graphql/language/visitor.js:366:25)"
4:"    at visit (/home/_xxx_/node_modules/graphql/language/visitor.js:254:26)"
5:"    at visitUsingRules (/home/_xxx_/node_modules/graphql/validation/validate.js:74:22)"
6:"    at Object.validate (/home/_xxx_/node_modules/graphql/validation/validate.js:59:10)"
7:"    at Promise.resolve.then (/home/_xxx_/node_modules/apollo-server-core/dist/runQuery.js:92:42)"
8:"    at process._tickCallback (internal/process/next_tick.js:68:7)"

UPD: in my case the problem was in a way I used import:
was: import { SchemaDirectiveVisitor } from 'graphql-tools';
must be: import { SchemaDirectiveVisitor } from 'apollo-server-express';

I have the same issue. When I use package graphql-constraint-directive and provide scalar and directive to typeDefs, constraint directive stops working and scalar validation is skipped. If I don't provide scalar and directive to my typeDefs - constraint works fine but IntrospectionSchema query fails on a client and GraphiQL shows errors that scalar is not defined in the schema. In the official apollo-server 2 directive example scalar type also isn't provided and this example doesn't work fine on GraphiQL and Playground. Also, a name of the scalar type is dynamically calculated by maxLength.
Have you come up with solutions?

image

The only solution that comes to mind - use Apollo stuff v1. It was a
masterpiece. The v2 have a number of flaws and inconveniences yet.

On Sat., 8 Sep. 2018, 03:43 Alexey, notifications@github.com wrote:

I have the same issue. When I use package graphql-constraint-directive
and provide scalar and directive to typeDefs, constraint directive stops
working and scalar validation is skipped. If I don't provide scalar and
directive to my typeDefs - constraint works fine but IntrospectionSchema
query fails on a client and GraphiQL shows errors that scalar is not
defined in the schema. In the official apollo-server 2 directive example
scalar type also isn't provided and this example doesn't work fine on
GraphiQL and Playground. Also, a name of the scalar type is dynamically
calculated by maxLength.
Have you come up with solutions?

[image: image]
https://user-images.githubusercontent.com/16045765/45234430-5c8c9100-b2de-11e8-9bf4-31add28ef39f.png

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/apollographql/apollo-server/issues/1303#issuecomment-419514551,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABjCLzbGNtMVI0o93UdLL1L-1VH8WBSfks5uYrBMgaJpZM4VE9As
.

I am experiencing the same issue, it's either the playground documentation will work or the constraints will work... cannot have both. :sweat_smile:

@koresar may I know what is the example of this one?
typeDefs: readFileSync(path/to/scalars).toString()

Thanks!

I was able to dupe and wrote it up under the following:
https://github.com/tdharris/apollo-graphql-constraint-directive

I think this is the same behavior seen here. It starts in a working state with the suggested workaround, and then it can be broken quite easily with details provided in the README. I may be off on this, but it seems like the issue here as far as I can tell for now.

This is a problem I have in another project where I am using prisma/graphql-yoga and the directive resolvers have been skipped over as well. My issue with the workaround approach is that I use merge-graphql-schemas since I have layers of schema files and need to have it attempt to merge various duplicates - I have ran into some issues there with apollographql/graphql-tools on that point that I may need to dig around a bit to find again.

Is there a chance that this is fixable in the foreseeable future?

Does this bug affect only DirectiveLocation.INPUT_FIELD_DEFINITION or other directive locations too?

I'm going to close this in favor of the issue in graphql-tools which is the root cause here

So, you decided to remove directiveResolvers which used to work just fine. And "it's graphql-tools problem" now. Hm...

How about reviving directiveResolvers?

We also got the exact same issue, coming from the documentation: https://www.apollographql.com/docs/graphql-tools/schema-directives/#enforcing-value-restrictions

The example above doesn't add any scalar XXX to the schema, leading us to believe "this is supposed to work".

Edit: Found solution in https://github.com/confuser/graphql-constraint-directive/issues/2#issuecomment-487838340.

Was this page helpful?
0 / 5 - 0 ratings