@kubb/plugin-zod
Version:
Zod schema generator plugin for Kubb, creating type-safe validation schemas from OpenAPI specifications for runtime data validation.
453 lines (451 loc) • 17.5 kB
JavaScript
import { File, Const, Type } from '@kubb/react';
import transformers2 from '@kubb/core/transformers';
import { jsxs, Fragment, jsx } from '@kubb/react/jsx-runtime';
import { SchemaGenerator, schemaKeywords, isKeyword } from '@kubb/plugin-oas';
// src/components/Operations.tsx
function Operations({ name, operations }) {
const operationsJSON = operations.reduce(
(prev, acc) => {
prev[`"${acc.operation.getOperationId()}"`] = acc.data;
return prev;
},
{}
);
const pathsJSON = operations.reduce(
(prev, acc) => {
prev[`"${acc.operation.path}"`] = {
...prev[`"${acc.operation.path}"`] || {},
[acc.operation.method]: `operations["${acc.operation.getOperationId()}"]`
};
return prev;
},
{}
);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(File.Source, { name, isExportable: true, isIndexable: true, children: /* @__PURE__ */ jsx(Const, { export: true, name, asConst: true, children: `{${transformers2.stringifyObject(operationsJSON)}}` }) }),
/* @__PURE__ */ jsx(File.Source, { name: "paths", isExportable: true, isIndexable: true, children: /* @__PURE__ */ jsx(Const, { export: true, name: "paths", asConst: true, children: `{${transformers2.stringifyObject(pathsJSON)}}` }) })
] });
}
var zodKeywordMapper = {
any: () => "z.any()",
unknown: () => "z.unknown()",
void: () => "z.void()",
number: (coercion, min, max) => {
return [coercion ? "z.coerce.number()" : "z.number()", min !== void 0 ? `.min(${min})` : void 0, max !== void 0 ? `.max(${max})` : void 0].filter(Boolean).join("");
},
integer: (coercion, min, max, version = "3") => {
return [
coercion ? "z.coerce.number().int()" : version === "4" ? "z.int()" : "z.number().int()",
min !== void 0 ? `.min(${min})` : void 0,
max !== void 0 ? `.max(${max})` : void 0
].filter(Boolean).join("");
},
interface: (value, strict) => {
if (strict) {
return `z.strictInterface({
${value}
})`;
}
return `z.interface({
${value}
})`;
},
object: (value, strict, version = "3") => {
if (version === "4" && strict) {
return `z.strictObject({
${value}
})`;
}
if (strict) {
return `z.object({
${value}
}).strict()`;
}
return `z.object({
${value}
})`;
},
string: (coercion, min, max) => {
return [coercion ? "z.coerce.string()" : "z.string()", min !== void 0 ? `.min(${min})` : void 0, max !== void 0 ? `.max(${max})` : void 0].filter(Boolean).join("");
},
//support for discriminatedUnion
boolean: () => "z.boolean()",
undefined: () => "z.undefined()",
nullable: () => ".nullable()",
null: () => "z.null()",
nullish: () => ".nullish()",
array: (items = [], min, max, unique) => {
return [
`z.array(${items?.join("")})`,
min !== void 0 ? `.min(${min})` : void 0,
max !== void 0 ? `.max(${max})` : void 0,
unique ? `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : void 0
].filter(Boolean).join("");
},
tuple: (items = []) => `z.tuple([${items?.join(", ")}])`,
enum: (items = []) => `z.enum([${items?.join(", ")}])`,
union: (items = []) => `z.union([${items?.join(", ")}])`,
const: (value) => `z.literal(${value ?? ""})`,
/**
* ISO 8601
*/
datetime: (offset = false, local = false, version = "3") => {
if (offset) {
return version === "4" ? `z.iso.datetime({ offset: ${offset} })` : `z.string().datetime({ offset: ${offset} })`;
}
if (local) {
return version === "4" ? `z.iso.datetime({ local: ${local} })` : `z.string().datetime({ local: ${local} })`;
}
return "z.string().datetime()";
},
/**
* Type `'date'` Date
* Type `'string'` ISO date format (YYYY-MM-DD)
* @default ISO date format (YYYY-MM-DD)
*/
date: (type = "string", coercion, version = "3") => {
if (type === "string") {
return version === "4" ? "z.iso.date()" : "z.string().date()";
}
if (coercion) {
return "z.coerce.date()";
}
return "z.date()";
},
/**
* Type `'date'` Date
* Type `'string'` ISO time format (HH:mm:ss[.SSSSSS])
* @default ISO time format (HH:mm:ss[.SSSSSS])
*/
time: (type = "string", coercion, version = "3") => {
if (type === "string") {
return version === "4" ? "z.iso.time()" : "z.string().time()";
}
if (coercion) {
return "z.coerce.date()";
}
return "z.date()";
},
uuid: (coercion, version = "3") => version === "4" ? coercion ? "z.coerce.string().uuid()" : "z.uuid()" : coercion ? "z.coerce.string().uuid()" : "z.string().uuid()",
url: (coercion, version = "3") => version === "4" ? coercion ? "z.coerce.string().url()" : "z.url()" : coercion ? "z.coerce.string().url()" : "z.string().url()",
default: (value) => {
if (typeof value === "object") {
return ".default({})";
}
return `.default(${value ?? ""})`;
},
and: (items = []) => items?.map((item) => `.and(${item})`).join(""),
describe: (value = "") => `.describe(${value})`,
min: (value) => `.min(${value ?? ""})`,
max: (value) => `.max(${value ?? ""})`,
optional: () => ".optional()",
matches: (value = "", coercion) => coercion ? `z.coerce.string().regex(${value})` : `z.string().regex(${value})`,
email: (coercion, version = "3") => version === "4" ? coercion ? "z.coerce.string().email()" : "z.email()" : coercion ? "z.coerce.string().email()" : "z.string().email()",
firstName: void 0,
lastName: void 0,
password: void 0,
phone: void 0,
readOnly: void 0,
writeOnly: void 0,
ref: (value, version = "3") => {
if (!value) {
return void 0;
}
return version === "4" ? value : `z.lazy(() => ${value})`;
},
blob: () => "z.instanceof(File)",
deprecated: void 0,
example: void 0,
schema: void 0,
catchall: (value) => value ? `.catchall(${value})` : void 0,
name: void 0
};
function sort(items) {
const order = [
schemaKeywords.string,
schemaKeywords.datetime,
schemaKeywords.date,
schemaKeywords.time,
schemaKeywords.tuple,
schemaKeywords.number,
schemaKeywords.object,
schemaKeywords.enum,
schemaKeywords.url,
schemaKeywords.email,
schemaKeywords.firstName,
schemaKeywords.lastName,
schemaKeywords.password,
schemaKeywords.matches,
schemaKeywords.uuid,
schemaKeywords.null,
schemaKeywords.min,
schemaKeywords.max,
schemaKeywords.default,
schemaKeywords.describe,
schemaKeywords.optional,
schemaKeywords.nullable,
schemaKeywords.nullish
];
if (!items) {
return [];
}
return transformers2.orderBy(items, [(v) => order.indexOf(v.keyword)], ["asc"]);
}
var shouldCoerce = (coercion, type) => {
if (coercion === void 0) {
return false;
}
if (typeof coercion === "boolean") {
return coercion;
}
return !!coercion[type];
};
function parse({ parent, current, name, siblings }, options) {
const value = zodKeywordMapper[current.keyword];
const hasMatches = siblings.some((it) => isKeyword(it, schemaKeywords.matches));
const hasRef = siblings.some((it) => isKeyword(it, schemaKeywords.ref));
if (hasMatches && hasRef && isKeyword(current, schemaKeywords.matches)) {
return void 0;
}
if (!value) {
return void 0;
}
if (isKeyword(current, schemaKeywords.union)) {
if (Array.isArray(current.args) && current.args.length === 1) {
return parse({ parent, name, current: current.args[0], siblings }, options);
}
if (Array.isArray(current.args) && !current.args.length) {
return "";
}
return zodKeywordMapper.union(
sort(current.args).map((schema, _index, siblings2) => parse({ parent: current, name, current: schema, siblings: siblings2 }, options)).filter(Boolean)
);
}
if (isKeyword(current, schemaKeywords.and)) {
const items = sort(current.args).filter((schema) => {
return ![schemaKeywords.optional, schemaKeywords.describe].includes(schema.keyword);
}).map((schema, _index, siblings2) => parse({ parent: current, name, current: schema, siblings: siblings2 }, options)).filter(Boolean);
return `${items.slice(0, 1)}${zodKeywordMapper.and(items.slice(1))}`;
}
if (isKeyword(current, schemaKeywords.array)) {
return zodKeywordMapper.array(
sort(current.args.items).map((schemas, _index, siblings2) => parse({ parent: current, name, current: schemas, siblings: siblings2 }, options)).filter(Boolean),
current.args.min,
current.args.max,
current.args.unique
);
}
if (isKeyword(current, schemaKeywords.enum)) {
if (current.args.asConst) {
if (current.args.items.length === 1) {
const child = {
keyword: schemaKeywords.const,
args: current.args.items[0]
};
return parse({ parent: current, name, current: child, siblings: [child] }, options);
}
return zodKeywordMapper.union(
current.args.items.map((schema) => ({
keyword: schemaKeywords.const,
args: schema
})).map((schema, _index, siblings2) => {
return parse({ parent: current, name, current: schema, siblings: siblings2 }, options);
}).filter(Boolean)
);
}
return zodKeywordMapper.enum(
current.args.items.map((schema) => {
if (schema.format === "boolean") {
return transformers2.stringify(schema.value);
}
if (schema.format === "number") {
return transformers2.stringify(schema.value);
}
return transformers2.stringify(schema.value);
})
);
}
if (isKeyword(current, schemaKeywords.ref)) {
return zodKeywordMapper.ref(current.args?.name, options.version);
}
if (isKeyword(current, schemaKeywords.object)) {
const propertyEntries = Object.entries(current.args?.properties || {}).filter((item) => {
const schema = item[1];
return schema && typeof schema.map === "function";
});
const properties = propertyEntries.map(([name2, schemas]) => {
const nameSchema = schemas.find((schema) => schema.keyword === schemaKeywords.name);
const mappedName = nameSchema?.args || name2;
if (options.mapper?.[mappedName]) {
return `"${name2}": ${options.mapper?.[mappedName]}`;
}
const baseSchemaOutput = sort(schemas).map((schema) => parse({ parent: current, name: name2, current: schema, siblings: schemas }, options)).filter(Boolean).join("");
const objectValue = options.wrapOutput ? options.wrapOutput({ output: baseSchemaOutput, schema: options.rawSchema?.properties?.[name2] }) || baseSchemaOutput : baseSchemaOutput;
if (options.version === "4" && SchemaGenerator.find(schemas, schemaKeywords.ref)) {
return `get ${name2}(){
return ${objectValue}
}`;
}
return `"${name2}": ${objectValue}`;
}).join(",\n");
const additionalProperties = current.args?.additionalProperties?.length ? current.args.additionalProperties.map((schema, _index, siblings2) => parse({ parent: current, name, current: schema, siblings: siblings2 }, options)).filter(Boolean).join("") : void 0;
const text = [
zodKeywordMapper.object(properties, current.args?.strict, options.version),
additionalProperties ? zodKeywordMapper.catchall(additionalProperties) : void 0
].filter(Boolean);
return text.join("");
}
if (isKeyword(current, schemaKeywords.tuple)) {
return zodKeywordMapper.tuple(
current.args.items.map((schema, _index, siblings2) => parse({ parent: current, name, current: schema, siblings: siblings2 }, options)).filter(Boolean)
);
}
if (isKeyword(current, schemaKeywords.const)) {
if (current.args.format === "number" && current.args.value !== void 0) {
return zodKeywordMapper.const(Number(current.args.value));
}
if (current.args.format === "boolean" && current.args.value !== void 0) {
return zodKeywordMapper.const(current.args.value);
}
return zodKeywordMapper.const(transformers2.stringify(current.args.value));
}
if (isKeyword(current, schemaKeywords.matches)) {
if (current.args) {
return zodKeywordMapper.matches(transformers2.toRegExpString(current.args, null), shouldCoerce(options.coercion, "strings"));
}
}
if (isKeyword(current, schemaKeywords.default)) {
if (current.args) {
return zodKeywordMapper.default(current.args);
}
}
if (isKeyword(current, schemaKeywords.describe)) {
if (current.args) {
return zodKeywordMapper.describe(transformers2.stringify(current.args.toString()));
}
}
if (isKeyword(current, schemaKeywords.string)) {
return zodKeywordMapper.string(shouldCoerce(options.coercion, "strings"));
}
if (isKeyword(current, schemaKeywords.uuid)) {
return zodKeywordMapper.uuid(shouldCoerce(options.coercion, "strings"), options.version);
}
if (isKeyword(current, schemaKeywords.email)) {
return zodKeywordMapper.email(shouldCoerce(options.coercion, "strings"), options.version);
}
if (isKeyword(current, schemaKeywords.url)) {
return zodKeywordMapper.url(shouldCoerce(options.coercion, "strings"), options.version);
}
if (isKeyword(current, schemaKeywords.number)) {
return zodKeywordMapper.number(shouldCoerce(options.coercion, "numbers"));
}
if (isKeyword(current, schemaKeywords.integer)) {
return zodKeywordMapper.integer(shouldCoerce(options.coercion, "numbers"), void 0, void 0, options.version);
}
if (isKeyword(current, schemaKeywords.min)) {
return zodKeywordMapper.min(current.args);
}
if (isKeyword(current, schemaKeywords.max)) {
return zodKeywordMapper.max(current.args);
}
if (isKeyword(current, schemaKeywords.datetime)) {
return zodKeywordMapper.datetime(current.args.offset, current.args.local, options.version);
}
if (isKeyword(current, schemaKeywords.date)) {
return zodKeywordMapper.date(current.args.type, shouldCoerce(options.coercion, "dates"), options.version);
}
if (isKeyword(current, schemaKeywords.time)) {
return zodKeywordMapper.time(current.args.type, shouldCoerce(options.coercion, "dates"), options.version);
}
if (current.keyword in zodKeywordMapper && "args" in current) {
const value2 = zodKeywordMapper[current.keyword];
return value2(current.args);
}
if (isKeyword(current, schemaKeywords.optional)) {
if (siblings.some((schema) => isKeyword(schema, schemaKeywords.default))) return "";
return value();
}
if (current.keyword in zodKeywordMapper) {
return value();
}
return void 0;
}
function Zod({
name,
typeName,
tree,
rawSchema,
inferTypeName,
mapper,
coercion,
keysToOmit,
description,
wrapOutput,
version,
emptySchemaType
}) {
const hasTuple = !!SchemaGenerator.deepSearch(tree, schemaKeywords.tuple);
const schemas = sort(tree).filter((item) => {
if (hasTuple && (isKeyword(item, schemaKeywords.min) || isKeyword(item, schemaKeywords.max))) {
return false;
}
return true;
});
const output = schemas.map(
(schema, _index, siblings) => parse(
{ parent: void 0, current: schema, siblings },
{ name, keysToOmit, typeName, description, mapper, coercion, wrapOutput, rawSchema, version }
)
).filter(Boolean).join("");
let suffix = "";
const firstSchema = schemas.at(0);
const lastSchema = schemas.at(-1);
if (lastSchema && isKeyword(lastSchema, schemaKeywords.nullable)) {
if (firstSchema && isKeyword(firstSchema, schemaKeywords.ref)) {
if (version === "3") {
suffix = ".unwrap().schema.unwrap()";
} else {
suffix = ".unwrap().unwrap()";
}
} else {
suffix = ".unwrap()";
}
} else {
if (firstSchema && isKeyword(firstSchema, schemaKeywords.ref) && version === "3") {
suffix = ".schema";
}
}
const emptyValue = parse(
{
parent: void 0,
current: {
keyword: schemaKeywords[emptySchemaType]
},
siblings: []
},
{ name, keysToOmit, typeName, description, mapper, coercion, wrapOutput, rawSchema, version }
);
const baseSchemaOutput = [output, keysToOmit?.length ? `${suffix}.omit({ ${keysToOmit.map((key) => `${key}: true`).join(",")} })` : void 0].filter(Boolean).join("") || emptyValue || "";
const wrappedSchemaOutput = wrapOutput ? wrapOutput({ output: baseSchemaOutput, schema: rawSchema }) || baseSchemaOutput : baseSchemaOutput;
const finalOutput = typeName ? `${wrappedSchemaOutput} as unknown as ToZod<${typeName}>` : wrappedSchemaOutput;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(File.Source, { name, isExportable: true, isIndexable: true, children: /* @__PURE__ */ jsx(
Const,
{
export: true,
name,
JSDoc: {
comments: [description ? `@description ${transformers2.jsStringEscape(description)}` : void 0].filter(Boolean)
},
children: finalOutput
}
) }),
inferTypeName && /* @__PURE__ */ jsxs(File.Source, { name: inferTypeName, isExportable: true, isIndexable: true, isTypeOnly: true, children: [
typeName && /* @__PURE__ */ jsx(Type, { export: true, name: inferTypeName, children: typeName }),
!typeName && /* @__PURE__ */ jsx(Type, { export: true, name: inferTypeName, children: `z.infer<typeof ${name}>` })
] })
] });
}
export { Operations, Zod };
//# sourceMappingURL=chunk-4QMHDKCS.js.map
//# sourceMappingURL=chunk-4QMHDKCS.js.map