UNPKG

@giraphql/plugin-validation

Version:

A GiraphQL plugin for adding argument validation

171 lines (136 loc) 5.08 kB
import './global-types'; import { GraphQLFieldResolver } from 'graphql'; import * as zod from 'zod'; import SchemaBuilder, { BasePlugin, GiraphQLInputFieldConfig, GiraphQLInputFieldType, GiraphQLOutputFieldConfig, mapInputFields, resolveInputTypeConfig, SchemaTypes, } from '@giraphql/core'; import createZodSchema, { combine, createArrayValidator, isArrayValidator, refine, } from './createZodSchema'; import { RefineConstraint, ValidationOptionUnion } from './types'; export * from './types'; const pluginName = 'validation' as const; export class GiraphQLValidationPlugin<Types extends SchemaTypes> extends BasePlugin<Types> { inputFieldValidators = new Map<string, Record<string, zod.ZodType<unknown>>>(); inputTypeValidators = new Map<string, zod.ZodType<unknown>>(); override onInputFieldConfig( fieldConfig: GiraphQLInputFieldConfig<Types>, ): GiraphQLInputFieldConfig<Types> { const fieldType = resolveInputTypeConfig(fieldConfig.type, this.buildCache); const validationOptions = fieldConfig.giraphqlOptions.validate as | ValidationOptionUnion | undefined; if (!validationOptions && fieldType.kind !== 'InputObject') { return fieldConfig; } const fieldName = fieldConfig.kind === 'Arg' ? `${fieldConfig.parentType}.${fieldConfig.parentField}(${fieldConfig.name})` : `${fieldConfig.parentType}.${fieldConfig.name}`; const validator = this.createValidator(validationOptions, fieldConfig.type, fieldName); if (fieldConfig.kind === 'InputObject') { this.inputFieldValidators.set(fieldConfig.parentType, { ...this.inputFieldValidators.get(fieldConfig.parentType), [fieldConfig.name]: validator, }); } if (fieldConfig.kind === 'Arg') { return { ...fieldConfig, extensions: { ...fieldConfig.extensions, validator, }, }; } this.inputFieldValidators.set(fieldConfig.parentType, { ...this.inputFieldValidators.get(fieldConfig.parentType), [fieldConfig.name]: validator, }); return fieldConfig; } override wrapResolve( resolver: GraphQLFieldResolver<unknown, Types['Context'], object>, fieldConfig: GiraphQLOutputFieldConfig<Types>, ): GraphQLFieldResolver<unknown, Types['Context'], object> { // Only used to check if validation is required const argMap = mapInputFields( fieldConfig.args, this.buildCache, (field) => field.extensions?.validator ?? null, ); if (!argMap && !fieldConfig.giraphqlOptions.validate) { return resolver; } const args: Record<string, zod.ZodType<unknown>> = {}; Object.keys(fieldConfig.args).forEach((argName) => { const validator = fieldConfig.args[argName].extensions?.validator as | zod.ZodType<unknown> | undefined; if (validator) { args[argName] = validator; } }); let validator: zod.ZodTypeAny = zod.object(args).passthrough(); if (fieldConfig.giraphqlOptions.validate) { validator = refine(validator, { refine: fieldConfig.giraphqlOptions.validate as RefineConstraint<unknown>, }); } return async (parent, rawArgs, context, info) => resolver(parent, (await validator.parseAsync(rawArgs)) as object, context, info); } createValidator( optionsOrConstraint: RefineConstraint | ValidationOptionUnion | undefined, type: GiraphQLInputFieldType<Types> | null, fieldName: string, ): zod.ZodTypeAny { const options: ValidationOptionUnion | undefined = Array.isArray(optionsOrConstraint) || typeof optionsOrConstraint === 'function' ? { refine: optionsOrConstraint } : optionsOrConstraint; if (type?.kind === 'InputObject') { const typeConfig = this.buildCache.getTypeConfig(type.ref, 'InputObject'); let fieldValidator = refine( zod.lazy(() => zod.object(this.inputFieldValidators.get(typeConfig.name) ?? {}).passthrough(), ), options, ); if (typeConfig.giraphqlOptions.validate) { fieldValidator = refine(fieldValidator, { refine: typeConfig.giraphqlOptions.validate as RefineConstraint<unknown>, }); } return combine([fieldValidator], type.required); } if (type?.kind === 'List') { if (options && !isArrayValidator(options)) { throw new Error(`Expected valid array validator for ${fieldName}`); } const items = options?.items ? this.createValidator(options.items, type.type, fieldName) : zod.unknown(); if (options) { return combine([createArrayValidator(options, items)], type.required); } return combine([items.array()], type.required); } if (!options) { return zod.unknown(); } return createZodSchema(options, !type || type.required); } } SchemaBuilder.registerPlugin(pluginName, GiraphQLValidationPlugin); export default pluginName; export { default as createZodSchema } from './createZodSchema';