Implementation of the support of coerce function on GraphQL Input types
npm install @alugha/graphql-field-arguments-coercion
Implementation of the support of coerce function on GraphQL Input types.
Used to implement directive-based validation and transformation of field arguments.
``sh`
npm install graphql-field-arguments-coercion -D
Use coerceFieldArgumentsValues(field, args, ...) to coerce the arguments of the given field. To coerce the arguments' values, it will recursively use the coerce property, a coercer function, hold by ArgumentDefinition, InputObject and InputObjectField.
A coercer function receives 4 arguments:
- value: the value to be coerced.context
- : the GraphQLContext of the current executioninputCoerceInfo
- : an object holding info about the current argument, input or input field. See its type definition for more details.fieldResolveInfo
- : the received GraphQLResolveInfo of the field being resolved.
A coercer function can return the coerced value, a promise resolving the coerced value or throw an error.
:First, we need to add the coercer to evey argument definition and input definition targeted by the directive. To do so, we use
graphql-tools's SchemaDirectiveVisitor.`ts
const directiveTypeDefs = ;class LengthDirective extends SchemaDirectiveVisitor<{ max: number }, TContext> {
visitInputFieldDefinition(field: CoercibleGraphQLInputField) {
this.installCoercer(field);
}
visitArgumentDefinition(argument: CoercibleGraphQLArgument) {
this.installCoercer(argument);
}
installCoercer(
input:
CoercibleGraphQLInputField |
CoercibleGraphQLArgument
) {
const { coerce = defaultCoercer } = input;
input.coerce = async (value, ...args) => {
// call previous coercers if any
if (coerce) value = await coerce(value, ...args);
const { path } = args[1]; // inputCoerceInfo
const { max } = this.args;
assert.isAtMost(value.length, max,
${pathToArray(path).join('.')} length exceeds ${max}); return value;
}
}
}
`We define the schema as usual but add the directive:
`ts
const typeDefs = type Book {
title: String
}
type Mutation {
createBook(book: BookInput): Book
}
input BookInput {
title: String! @length(max: 50)
};
const schema = makeExecutableSchema({
typeDefs: [directiveTypeDefs, typeDefs],
resolvers: {
Mutation: {
createBook: (_, { book }) => book,
}
},
schemaDirectives: {
length: LengthDirective
}
});
`
Now we'll wrap all fields' resolvers with a use of coerceFieldArgumentsValues so that we make sure the arguments are valid before calling the resolver — otherwise, we throw the appropriate error.
To do so, we'll use graphql-tools's visitSchema and SchemaVisitor:
`ts
class FieldResoverWrapperVisitor
visitFieldDefinition(field: GraphQLField
const { resolve = defaultFieldResolver } = field;
field.resolve = async (parent, argumentValues, context, info) => {
const coercionErrors: Error[] = [];
const onCoercionError = e => coercionErrors.push(e);
const coercedArgumentValues = await coerceFieldArgumentsValues(
field,
argumentValues,
context,
info,
onCoercionError,
);
if (coercionErrors.length > 0) {
throw new UserInputError(Arguments are incorrect: ${coercionErrors.join(',')});
}
return resolve(parent, coercedArgumentValues, context, info);
}
}
}
visitSchema(schema, new FieldResoverWrapperVisitor);
``
The full example is runnable here.
- Validation in Input types proposal (graphql-js#361)
- Support resolve on Input types (graphql-js#747)
- Directive visitArgumentDefinition variable healing (graphql-tools#789)
- SchemaDirectiveVisitor.visitInputFieldDefinition resolver doesn't fire (graphql-tools#858)