graphql-tools icon indicating copy to clipboard operation
graphql-tools copied to clipboard

Custom directive not working

Open longlee218 opened this issue 3 years ago • 3 comments

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.

longlee218 avatar Dec 17 '21 11:12 longlee218

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.

mogupta-ecfmg avatar Dec 28 '21 22:12 mogupta-ecfmg

Hi!

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

Bram

brampurnot avatar Jun 18 '22 06:06 brampurnot

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

avaly avatar Jun 23 '22 13:06 avaly