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

Custom directive not working #4027

Open
Tracked by #5201 ...
longlee218 opened this issue Dec 17, 2021 · 3 comments
Open
Tracked by #5201 ...

Custom directive not working #4027

longlee218 opened this issue Dec 17, 2021 · 3 comments

Comments

@longlee218
Copy link

longlee218 commented Dec 17, 2021

Custom directive for input is not working in Apollo version 3

Description
I'm following a tutorial at graphql-tools for Enforcing value restrictions to build a directive to validate length of string input.

 input AddCycleWorkInput {
        name: String! @length(max: 10)
    }

Expected
If the length's name input exceeds the specified, i hope throwing any ValidateError or Error

My Environment:

  • OS: Windowns 10
  • @graphql-tools/utils: 8.1.1
  • apollo-server-core: ^3.3.0
  • typescript: ^4.4.3
  • @graphql-tools/utils: 8.1.1,

Schema build

private static buildSchema(accountsGraphQL: any): GraphQLSchema {
        // List directives.
        const { lengthDirectiveTypeDefs, lengthDirectiveTransformer } = lengthDirective('length');

        // Build schema.
        let schema = makeExecutableSchema({
            typeDefs: mergeTypeDefs([
                lengthDirectiveTypeDefs,        // This is problem
                accountsGraphQL.typeDefs,
                typeDefs
            ]),
            resolvers: mergeResolvers([accountsGraphQL.resolvers, resolvers]),
            schemaDirectives: {
                ...accountsGraphQL.schemaDirectives,
            },
        });
        // needed to validate input fields!
        schema = lengthDirectiveTransformer(schema);
        return schema;
    }

Constructor of Apollo Server

   const server = new ApolloServer({
            schema: this.buildSchema(accountsGraphQL),         // Schema build
            context: accountsGraphQL.context,
            uploads: false,
            formatError: (error) => {
                if (error) {
                    return {
                        message: error.message,
                        status: codeToStatus[error.extensions.code.toUpperCase()] || 400,
                        path: error.path
                    };
                }
            }
        });

Length directive, to short you can watch this link

export default function lengthDirective(directiveName: string) {
    class LimitedLengthType extends GraphQLScalarType {
        constructor(type: GraphQLScalarType, maxLength: number) {
            super({
                name: `${type.name}WithLengthAtMost${maxLength.toString()}`,
                serialize(value: string) {
                    console.log("value1", value);
                    const newValue: string = type.serialize(value)
                    if (newValue.length > maxLength) {
                        throw new Error(`expected ${newValue.length.toString(10)} to be at most ${maxLength.toString(10)}`)
                    }
                    return newValue
                },

                parseValue(value: string) {
                    console.log("value2", value);
                    return type.parseValue(value)
                },

                parseLiteral(ast) {
                    return type.parseLiteral(ast, {})
                }
            })
        }
    }

    const limitedLengthTypes: Record<string, Record<number, GraphQLScalarType>> = {}

    function getLimitedLengthType(type: GraphQLScalarType, maxLength: number): GraphQLScalarType {
        const limitedLengthTypesByTypeName = limitedLengthTypes[type.name]
        if (!limitedLengthTypesByTypeName) {
            const newType = new LimitedLengthType(type, maxLength)
            limitedLengthTypes[type.name] = {}
            limitedLengthTypes[type.name][maxLength] = newType
            return newType
        }

        const limitedLengthType = limitedLengthTypesByTypeName[maxLength]
        if (!limitedLengthType) {
            const newType = new LimitedLengthType(type, maxLength)
            limitedLengthTypesByTypeName[maxLength] = newType
            return newType
        }

        return limitedLengthType
    }

    function wrapType<F extends GraphQLFieldConfig<any, any> | GraphQLInputFieldConfig>(
        fieldConfig: F,
        directiveArgumentMap: Record<string, any>
    ): void {
        if (isNonNullType(fieldConfig.type) && isScalarType(fieldConfig.type.ofType)) {
            fieldConfig.type = getLimitedLengthType(fieldConfig.type.ofType, directiveArgumentMap['max'])
        } else if (isScalarType(fieldConfig.type)) {
            fieldConfig.type = getLimitedLengthType(fieldConfig.type, directiveArgumentMap['max'])
        } else {
            throw new Error(`Not a scalar type: ${fieldConfig.type.toString()}`)
        }
    }
    return {
        lengthDirectiveTypeDefs: `directive @${directiveName}(max: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION`,
        lengthDirectiveTransformer: (schema: GraphQLSchema) =>
            mapSchema(schema, {
                [MapperKind.FIELD]: fieldConfig => {
                    const lengthDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
                    if (lengthDirective) {
                        wrapType(fieldConfig, lengthDirective)
                        return fieldConfig
                    }
                },
            })
    }
}

I know this is a bug of version 3 ApolloServer, but can other way to fix it. Please help me. Thanks a lot.

@mogupta-ecfmg
Copy link

I am also facing issues with custom directives when using codegen. Everything works fine without codegen, when entire schema is kept in one single file, as shown in samples here. However, when I introduced codegen to generate the schema dynamically, then the directive transformer function is not getting executed when the field is queried.

@brampurnot
Copy link

Hi!

I'm facing the same issue. Did anyone manage to solve this?

Bram

@avaly
Copy link
Contributor

avaly commented Jun 23, 2022

I think you may need to also add the validation logic in the parseLiteral method to handle input fields.

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

4 participants