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.
Install
npm install graphql-field-arguments-coercion -D
Usage
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.
Example
Here's an implementation of a directive-based length validation @length(max: Int!)
:
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
.
const directiveTypeDefs = `
directive @length(max: Int!) on INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
`;
class LengthDirective<TContext> extends SchemaDirectiveVisitor<{ max: number }, TContext> {
visitInputFieldDefinition(field: CoercibleGraphQLInputField<string, TContext>) {
this.installCoercer(field);
}
visitArgumentDefinition(argument: CoercibleGraphQLArgument<string, TContext>) {
this.installCoercer(argument);
}
installCoercer(
input:
CoercibleGraphQLInputField<string, TContext> |
CoercibleGraphQLArgument<string, TContext>
) {
const { coerce = defaultCoercer } = input;
input.coerce = async (value, ...args) => {
if (coerce) value = await coerce(value, ...args);
const { path } = args[1];
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:
const typeDefs = `
type Query {
books: [Book]
}
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
:
class FieldResoverWrapperVisitor<TContext> extends SchemaVisitor {
visitFieldDefinition(field: GraphQLField<any, TContext>) {
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.
Related