@acaldas/graphql-codegen-typescript-validation-schema
Version:
GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema
265 lines (264 loc) • 12.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZodSchemaVisitor = void 0;
const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
const directive_1 = require("../directive");
const schema_visitor_1 = require("../schema_visitor");
const graphql_1 = require("./../graphql");
const anySchema = `definedNonNullAnySchema`;
class ZodSchemaVisitor extends schema_visitor_1.BaseSchemaVisitor {
constructor(schema, config) {
super(schema, config);
}
importValidationSchema() {
return `import { z } from 'zod'`;
}
initialEmit() {
return ('\n' +
[
new visitor_plugin_common_1.DeclarationBlock({})
.asKind('type')
.withName('Properties<T>')
.withContent(['Required<{', ' [K in keyof T]: z.ZodType<T[K], any, T[K]>;', '}>'].join('\n')).string,
// Unfortunately, zod doesn’t provide non-null defined any schema.
// This is a temporary hack until it is fixed.
// see: https://github.com/colinhacks/zod/issues/884
new visitor_plugin_common_1.DeclarationBlock({}).asKind('type').withName('definedNonNullAny').withContent('{}').string,
new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('const')
.withName(`isDefinedNonNullAny`)
.withContent(`(v: any): v is definedNonNullAny => v !== undefined && v !== null`).string,
new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('const')
.withName(`${anySchema}`)
.withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`).string,
...this.enumDeclarations,
].join('\n'));
}
get InputObjectTypeDefinition() {
return {
leave: (node) => {
var _a;
const visitor = this.createVisitor('input');
const name = visitor.convertName(node.name.value);
this.importTypes.push(name);
return this.buildInputFields((_a = node.fields) !== null && _a !== void 0 ? _a : [], visitor, name);
},
};
}
get ObjectTypeDefinition() {
return {
leave: (0, graphql_1.ObjectTypeDefinitionBuilder)(this.config.withObjectType, (node) => {
var _a;
const visitor = this.createVisitor('output');
const name = visitor.convertName(node.name.value);
this.importTypes.push(name);
// Building schema for field arguments.
const argumentBlocks = this.buildObjectTypeDefinitionArguments(node, visitor);
const appendArguments = argumentBlocks ? '\n' + argumentBlocks : '';
// Building schema for fields.
const shape = (_a = node.fields) === null || _a === void 0 ? void 0 : _a.map(field => generateFieldZodSchema(this.config, visitor, field, 2)).join(',\n');
switch (this.config.validationSchemaExportType) {
case 'const':
return (new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('const')
.withName(`${name}Schema: z.ZodObject<Properties<${name}>>`)
.withContent([
`z.object({`,
(0, visitor_plugin_common_1.indent)(`__typename: z.literal('${node.name.value}').optional(),`, 2),
shape,
'})',
].join('\n')).string + appendArguments);
case 'function':
default:
return (new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('function')
.withName(`${name}Schema(): z.ZodObject<Properties<${name}>>`)
.withBlock([
(0, visitor_plugin_common_1.indent)(`return z.object({`),
(0, visitor_plugin_common_1.indent)(`__typename: z.literal('${node.name.value}').optional(),`, 2),
shape,
(0, visitor_plugin_common_1.indent)('})'),
].join('\n')).string + appendArguments);
}
}),
};
}
get EnumTypeDefinition() {
return {
leave: (node) => {
var _a;
const visitor = this.createVisitor('both');
const enumname = visitor.convertName(node.name.value);
this.importTypes.push(enumname);
// hoist enum declarations
this.enumDeclarations.push(this.config.enumsAsTypes
? new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('const')
.withName(`${enumname}Schema`)
.withContent(`z.enum([${(_a = node.values) === null || _a === void 0 ? void 0 : _a.map(enumOption => `'${enumOption.name.value}'`).join(', ')}])`)
.string
: new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('const')
.withName(`${enumname}Schema`)
.withContent(`z.nativeEnum(${enumname})`).string);
},
};
}
get UnionTypeDefinition() {
return {
leave: (node) => {
var _a;
if (!node.types || !this.config.withObjectType)
return;
const visitor = this.createVisitor('output');
const unionName = visitor.convertName(node.name.value);
const unionElements = node.types
.map(t => {
var _a;
const element = visitor.convertName(t.name.value);
const typ = visitor.getType(t.name.value);
if (((_a = typ === null || typ === void 0 ? void 0 : typ.astNode) === null || _a === void 0 ? void 0 : _a.kind) === 'EnumTypeDefinition') {
return `${element}Schema`;
}
switch (this.config.validationSchemaExportType) {
case 'const':
return `${element}Schema`;
case 'function':
default:
return `${element}Schema()`;
}
})
.join(', ');
const unionElementsCount = (_a = node.types.length) !== null && _a !== void 0 ? _a : 0;
const union = unionElementsCount > 1 ? `z.union([${unionElements}])` : unionElements;
switch (this.config.validationSchemaExportType) {
case 'const':
return new visitor_plugin_common_1.DeclarationBlock({}).export().asKind('const').withName(`${unionName}Schema`).withContent(union)
.string;
case 'function':
default:
return new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('function')
.withName(`${unionName}Schema()`)
.withBlock((0, visitor_plugin_common_1.indent)(`return ${union}`)).string;
}
},
};
}
buildInputFields(fields, visitor, name) {
const shape = fields.map(field => generateFieldZodSchema(this.config, visitor, field, 2)).join(',\n');
switch (this.config.validationSchemaExportType) {
case 'const':
return new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('const')
.withName(`${name}Schema: z.ZodObject<Properties<${name}>>`)
.withContent(['z.object({', shape, '})'].join('\n')).string;
case 'function':
default:
return new visitor_plugin_common_1.DeclarationBlock({})
.export()
.asKind('function')
.withName(`${name}Schema(): z.ZodObject<Properties<${name}>>`)
.withBlock([(0, visitor_plugin_common_1.indent)(`return z.object({`), shape, (0, visitor_plugin_common_1.indent)('})')].join('\n')).string;
}
}
}
exports.ZodSchemaVisitor = ZodSchemaVisitor;
const generateFieldZodSchema = (config, visitor, field, indentCount) => {
const gen = generateFieldTypeZodSchema(config, visitor, field, field.type);
return (0, visitor_plugin_common_1.indent)(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
};
const generateFieldTypeZodSchema = (config, visitor, field, type, parentType) => {
const nullType = field.kind === 'InputValueDefinition' ? 'nullish' : 'nullable';
if ((0, graphql_1.isListType)(type)) {
const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type);
if (!(0, graphql_1.isNonNullType)(parentType)) {
const arrayGen = `z.array(${maybeLazy(type.type, gen)})`;
const maybeLazyGen = applyDirectives(config, field, arrayGen);
return `${maybeLazyGen}.${nullType}()`;
}
return `z.array(${maybeLazy(type.type, gen)})`;
}
if ((0, graphql_1.isNonNullType)(type)) {
const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type);
return maybeLazy(type.type, gen);
}
if ((0, graphql_1.isNamedType)(type)) {
const gen = generateNameNodeZodSchema(config, visitor, type.name);
if ((0, graphql_1.isListType)(parentType)) {
return `${gen}.${nullType}()`;
}
const appliedDirectivesGen = applyDirectives(config, field, gen);
if ((0, graphql_1.isNonNullType)(parentType)) {
if (visitor.shouldEmitAsNotAllowEmptyString(type.name.value)) {
return `${appliedDirectivesGen}.min(1)`;
}
return appliedDirectivesGen;
}
if ((0, graphql_1.isListType)(parentType)) {
return `${appliedDirectivesGen}.${nullType}()`;
}
return `${appliedDirectivesGen}.${nullType}()`;
}
console.warn('unhandled type:', type);
return '';
};
const applyDirectives = (config, field, gen) => {
if (config.directives && field.directives) {
const formatted = (0, directive_1.formatDirectiveConfig)(config.directives);
return gen + (0, directive_1.buildApi)(formatted, field.directives);
}
return gen;
};
const generateNameNodeZodSchema = (config, visitor, node) => {
const converter = visitor.getNameNodeConverter(node);
switch (converter === null || converter === void 0 ? void 0 : converter.targetKind) {
case 'InputObjectTypeDefinition':
case 'ObjectTypeDefinition':
case 'UnionTypeDefinition':
// using switch-case rather than if-else to allow for future expansion
switch (config.validationSchemaExportType) {
case 'const':
return `${converter.convertName()}Schema`;
case 'function':
default:
return `${converter.convertName()}Schema()`;
}
case 'EnumTypeDefinition':
return `${converter.convertName()}Schema`;
default:
return zod4Scalar(config, visitor, node.value);
}
};
const maybeLazy = (type, schema) => {
if ((0, graphql_1.isNamedType)(type) && (0, graphql_1.isInput)(type.name.value)) {
return `z.lazy(() => ${schema})`;
}
return schema;
};
const zod4Scalar = (config, visitor, scalarName) => {
var _a;
if ((_a = config.scalarSchemas) === null || _a === void 0 ? void 0 : _a[scalarName]) {
return config.scalarSchemas[scalarName];
}
const tsType = visitor.getScalarType(scalarName);
switch (tsType) {
case 'string':
return `z.string()`;
case 'number':
return `z.number()`;
case 'boolean':
return `z.boolean()`;
}
console.warn('unhandled scalar name:', scalarName);
return anySchema;
};