graphql-codegen-typescript-validation-schema
Version:
GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema
240 lines (239 loc) • 12.3 kB
JavaScript
;
// Shared utilities used by both ZodSchemaVisitor (zod) and ZodV4SchemaVisitor (zodv4).
// These functions are identical across both implementations and are extracted here to
// eliminate duplication.
Object.defineProperty(exports, "__esModule", { value: true });
exports.anySchema = void 0;
exports.generateFieldZodSchema = generateFieldZodSchema;
exports.generateFieldTypeZodSchema = generateFieldTypeZodSchema;
exports.isOneOfInputObject = isOneOfInputObject;
exports.buildObjectExpression = buildObjectExpression;
exports.buildObjectReturn = buildObjectReturn;
exports.strictObjectSuffix = strictObjectSuffix;
exports.descriptionSuffix = descriptionSuffix;
exports.withTypeDescription = withTypeDescription;
exports.zodOptionalType = zodOptionalType;
exports.withNullDefault = withNullDefault;
exports.schemaDepthVariable = schemaDepthVariable;
exports.schemaDepthParameter = schemaDepthParameter;
exports.withDescription = withDescription;
exports.applyDefaultValue = applyDefaultValue;
exports.defaultValueExpression = defaultValueExpression;
exports.hasNullDefault = hasNullDefault;
exports.inputObjectFields = inputObjectFields;
exports.enumDefaultTypeName = enumDefaultTypeName;
exports.enumDefaultValueName = enumDefaultValueName;
exports.applyDirectives = applyDirectives;
exports.generateNameNodeZodSchema = generateNameNodeZodSchema;
exports.maybeLazy = maybeLazy;
exports.zod4Scalar = zod4Scalar;
exports.unionLiterals = unionLiterals;
const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
const graphql_1 = require("graphql");
const directive_js_1 = require("./directive.js");
const graphql_js_1 = require("./graphql.js");
const lazy_js_1 = require("./lazy.js");
const scalar_js_1 = require("./scalar.js");
exports.anySchema = `definedNonNullAnySchema`;
function generateFieldZodSchema(config, visitor, field, indentCount, depthVariable) {
const gen = generateFieldTypeZodSchema(config, visitor, field, field.type, undefined, true, false, depthVariable);
return (0, visitor_plugin_common_1.indent)(`${field.name.value}: ${withDescription(config, field, maybeLazy(visitor, field.type, gen))}`, indentCount);
}
function generateFieldTypeZodSchema(config, visitor, field, type, parentType, isRoot = true, forceRequired = false, depthVariable) {
if ((0, graphql_js_1.isListType)(type)) {
const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type, false, false, depthVariable);
const arrayGen = `z.array(${maybeLazy(visitor, type.type, gen)})`;
const maybeDirectivesGen = isRoot ? applyDirectives(config, field, arrayGen) : arrayGen;
const maybeDefaultGen = hasNullDefault(field) ? maybeDirectivesGen : applyDefaultValue(config, visitor, field, type, maybeDirectivesGen);
if (!(0, graphql_js_1.isNonNullType)(parentType) && !forceRequired) {
if (hasNullDefault(field))
return withNullDefault(config, maybeDirectivesGen);
return `${maybeDefaultGen}.${zodOptionalType(config)}()`;
}
return maybeDefaultGen;
}
if ((0, graphql_js_1.isNonNullType)(type)) {
const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type, isRoot, forceRequired, depthVariable);
return maybeLazy(visitor, type.type, gen);
}
if ((0, graphql_js_1.isNamedType)(type)) {
const gen = generateNameNodeZodSchema(config, visitor, type.name, depthVariable);
if ((0, graphql_js_1.isListType)(parentType))
return `${gen}.nullable()`;
const appliedDirectivesGen = isRoot
? hasNullDefault(field)
? applyDirectives(config, field, gen)
: applyDefaultValue(config, visitor, field, type, applyDirectives(config, field, gen))
: gen;
if ((0, graphql_js_1.isNonNullType)(parentType)) {
if (visitor.shouldEmitAsNotAllowEmptyString(type.name.value))
return `${appliedDirectivesGen}.min(1)`;
return appliedDirectivesGen;
}
if ((0, graphql_js_1.isListType)(parentType))
return `${appliedDirectivesGen}.nullable()`;
if (forceRequired)
return appliedDirectivesGen;
return hasNullDefault(field)
? withNullDefault(config, appliedDirectivesGen)
: `${appliedDirectivesGen}.${zodOptionalType(config)}()`;
}
console.warn('unhandled type:', type);
return '';
}
function isOneOfInputObject(node) {
return node.directives?.some(directive => directive.name.value === 'oneOf') === true;
}
function buildObjectExpression(config, shape, description) {
return ['z.object({', shape, `})${strictObjectSuffix(config)}${descriptionSuffix(config, description)}`].join('\n');
}
function buildObjectReturn(config, shape, description) {
return [(0, visitor_plugin_common_1.indent)('return z.object({'), shape, (0, visitor_plugin_common_1.indent)(`})${strictObjectSuffix(config)}${descriptionSuffix(config, description)}`)].join('\n');
}
function strictObjectSuffix(config) {
return config.strictObjectSchemas === true ? '.strict()' : '';
}
function descriptionSuffix(config, description) {
if (config.withDescriptions !== true || !description)
return '';
return `.describe(${JSON.stringify(description)})`;
}
function withTypeDescription(config, description, gen) {
return `${gen}${descriptionSuffix(config, description)}`;
}
function zodOptionalType(config) {
return config.nullishBehavior ?? config.zodOptionalType ?? 'nullish';
}
function withNullDefault(config, gen) {
if (zodOptionalType(config) === 'optional')
return `${gen}.nullable().optional().default(null)`;
return `${gen}.${zodOptionalType(config)}().default(null)`;
}
function schemaDepthVariable(config) {
return typeof config.maxDepth === 'number' && config.validationSchemaExportType !== 'const'
? 'depth'
: undefined;
}
function schemaDepthParameter(config) {
return schemaDepthVariable(config) ? 'depth = 0' : '';
}
function withDescription(config, field, gen) {
if (config.withDescriptions !== true || !field.description?.value)
return gen;
return `${gen}.describe(${JSON.stringify(field.description.value)})`;
}
function applyDefaultValue(config, visitor, field, type, gen) {
if (field.kind !== graphql_1.Kind.INPUT_VALUE_DEFINITION || !field.defaultValue)
return gen;
return `${gen}.default(${defaultValueExpression(config, visitor, type, field.defaultValue)})`;
}
function defaultValueExpression(config, visitor, type, value) {
if (value.kind === graphql_1.Kind.NULL)
return 'null';
if ((0, graphql_js_1.isNonNullType)(type))
return defaultValueExpression(config, visitor, type.type, value);
if ((0, graphql_js_1.isListType)(type)) {
if (value.kind === graphql_1.Kind.LIST)
return `[${value.values.map(item => defaultValueExpression(config, visitor, type.type, item)).join(', ')}]`;
return `[${defaultValueExpression(config, visitor, type.type, value)}]`;
}
if ((0, graphql_js_1.isNamedType)(type) && visitor.getType(type.name.value)?.astNode?.kind === 'EnumTypeDefinition' && value.kind === graphql_1.Kind.ENUM) {
if (!config.enumsAsTypes)
return `${enumDefaultTypeName(visitor, type)}.${enumDefaultValueName(config, value.value)}`;
return JSON.stringify(value.value);
}
if ((0, graphql_js_1.isNamedType)(type) && value.kind === graphql_1.Kind.OBJECT) {
const graphQLType = visitor.getType(type.name.value);
const astNode = graphQLType?.astNode;
if (astNode?.kind === graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION && (0, graphql_1.isInputObjectType)(graphQLType)) {
const explicitFields = new Map(value.fields.map(field => [field.name.value, field.value]));
const fields = inputObjectFields(astNode, graphQLType.extensionASTNodes).flatMap((field) => {
const fieldValue = explicitFields.get(field.name.value) ?? field.defaultValue;
if (!fieldValue)
return [];
return `${field.name.value}: ${defaultValueExpression(config, visitor, field.type, fieldValue)}`;
});
return `{ ${fields.join(', ')} }`;
}
}
if (value.kind === graphql_1.Kind.INT || value.kind === graphql_1.Kind.FLOAT || value.kind === graphql_1.Kind.BOOLEAN)
return `${value.value}`;
if (value.kind === graphql_1.Kind.STRING)
return `"${(0, graphql_js_1.escapeGraphQLCharacters)(value.value)}"`;
return JSON.stringify((0, graphql_1.valueFromASTUntyped)(value));
}
function hasNullDefault(field) {
return field.kind === graphql_1.Kind.INPUT_VALUE_DEFINITION && field.defaultValue?.kind === graphql_1.Kind.NULL;
}
function inputObjectFields(astNode, extensionASTNodes) {
return [
...(astNode.fields ?? []),
...(extensionASTNodes?.flatMap(extension => extension.fields ?? []) ?? []),
];
}
function enumDefaultTypeName(visitor, type) {
if ((0, graphql_js_1.isNonNullType)(type))
return enumDefaultTypeName(visitor, type.type);
if ((0, graphql_js_1.isNamedType)(type))
return visitor.prefixTypeNamespace(visitor.convertSchemaName(type.name.value, visitor.getType(type.name.value)?.astNode?.kind));
return '';
}
function enumDefaultValueName(config, value) {
let enumValue = (0, visitor_plugin_common_1.convertNameParts)(value, (0, plugin_helpers_1.resolveExternalModuleAndFn)('change-case-all#pascalCase'), config.namingConvention?.transformUnderscore);
if (config.namingConvention?.enumValues)
enumValue = (0, visitor_plugin_common_1.convertNameParts)(value, (0, plugin_helpers_1.resolveExternalModuleAndFn)(config.namingConvention?.enumValues), config.namingConvention?.transformUnderscore);
return enumValue;
}
function applyDirectives(config, field, gen) {
if (config.directives && field.directives) {
const formatted = (0, directive_js_1.formatDirectiveConfig)(config.directives);
return gen + (0, directive_js_1.buildApi)(formatted, field.directives);
}
return gen;
}
function generateNameNodeZodSchema(config, visitor, node, depthVariable) {
const converter = visitor.getNameNodeConverter(node);
switch (converter?.targetKind) {
case 'InterfaceTypeDefinition':
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:
if (depthVariable
&& (converter.targetKind === 'InterfaceTypeDefinition'
|| converter.targetKind === 'ObjectTypeDefinition'
|| converter.targetKind === 'UnionTypeDefinition')) {
return `${depthVariable} >= ${config.maxDepth} ? ${exports.anySchema} : ${converter.convertName()}Schema(${depthVariable} + 1)`;
}
return `${converter.convertName()}Schema()`;
}
case 'EnumTypeDefinition':
return `${converter.convertName()}Schema`;
case 'ScalarTypeDefinition':
return zod4Scalar(config, visitor, node.value);
default:
if (converter?.targetKind)
console.warn('Unknown targetKind', converter?.targetKind);
return zod4Scalar(config, visitor, node.value);
}
}
function maybeLazy(visitor, type, schema) {
return (0, lazy_js_1.buildMaybeLazy)(visitor, type, schema, s => `z.lazy(() => ${s})`);
}
function zod4Scalar(config, visitor, scalarName) {
return (0, scalar_js_1.buildScalarSchema)(config, visitor, scalarName, {
typeMap: { string: 'z.string()', number: 'z.number()', boolean: 'z.boolean()' },
fallback: exports.anySchema,
});
}
function unionLiterals(values) {
if (values.length === 0)
return 'never';
return values.map(value => JSON.stringify(value)).join(' | ');
}