@asteasolutions/zod-to-openapi
Version:
Builds OpenAPI schemas from Zod schemas
699 lines (698 loc) • 35.2 kB
JavaScript
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAPIGenerator = void 0;
const errors_1 = require("./errors");
const enum_info_1 = require("./lib/enum-info");
const lodash_1 = require("./lib/lodash");
const zod_is_type_1 = require("./lib/zod-is-type");
// List of Open API Versions. Please make sure these are in ascending order
const openApiVersions = ['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.1.0'];
class OpenAPIGenerator {
constructor(definitions, openAPIVersion) {
this.definitions = definitions;
this.openAPIVersion = openAPIVersion;
this.schemaRefs = {};
this.paramRefs = {};
this.pathRefs = {};
this.webhookRefs = {};
this.rawComponents = [];
this.openApiVersionSatisfies = (inputVersion, comparison) => openApiVersions.indexOf(inputVersion) >=
openApiVersions.indexOf(comparison);
this.sortDefinitions();
}
generateDocument(config) {
this.definitions.forEach(definition => this.generateSingle(definition));
return Object.assign(Object.assign(Object.assign({}, config), { openapi: this.openAPIVersion, components: this.buildComponents(), paths: this.pathRefs }), (Object.keys(this.webhookRefs).length && {
webhooks: this.webhookRefs,
}));
}
generateComponents() {
this.definitions.forEach(definition => this.generateSingle(definition));
return {
components: this.buildComponents(),
};
}
buildComponents() {
var _a, _b;
const rawComponents = {};
this.rawComponents.forEach(({ componentType, name, component }) => {
var _a;
(_a = rawComponents[componentType]) !== null && _a !== void 0 ? _a : (rawComponents[componentType] = {});
rawComponents[componentType][name] = component;
});
return Object.assign(Object.assign({}, rawComponents), { schemas: Object.assign(Object.assign({}, ((_a = rawComponents.schemas) !== null && _a !== void 0 ? _a : {})), this.schemaRefs), parameters: Object.assign(Object.assign({}, ((_b = rawComponents.parameters) !== null && _b !== void 0 ? _b : {})), this.paramRefs) });
}
sortDefinitions() {
const generationOrder = [
'schema',
'parameter',
'component',
'route',
'webhook',
];
this.definitions.sort((left, right) => {
const leftIndex = generationOrder.findIndex(type => type === left.type);
const rightIndex = generationOrder.findIndex(type => type === right.type);
return leftIndex - rightIndex;
});
}
generateSingle(definition) {
switch (definition.type) {
case 'parameter':
this.generateParameterDefinition(definition.schema);
return;
case 'schema':
this.generateSchemaDefinition(definition.schema);
return;
case 'route':
this.generateSingleRoute(definition.route);
return;
case 'webhook':
this.generateSingleWebhook(definition.webhook);
return;
case 'component':
this.rawComponents.push(definition);
return;
}
}
generateParameterDefinition(zodSchema) {
const refId = this.getRefId(zodSchema);
const result = this.generateParameter(zodSchema);
if (refId) {
this.paramRefs[refId] = result;
}
return result;
}
getParameterRef(schemaMetadata, external) {
var _a, _b, _c, _d, _e;
const parameterMetadata = (_a = schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata.metadata) === null || _a === void 0 ? void 0 : _a.param;
const existingRef = ((_b = schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata._internal) === null || _b === void 0 ? void 0 : _b.refId)
? this.paramRefs[(_c = schemaMetadata._internal) === null || _c === void 0 ? void 0 : _c.refId]
: undefined;
if (!((_d = schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata._internal) === null || _d === void 0 ? void 0 : _d.refId) || !existingRef) {
return undefined;
}
if ((parameterMetadata && existingRef.in !== parameterMetadata.in) ||
((external === null || external === void 0 ? void 0 : external.in) && existingRef.in !== external.in)) {
throw new errors_1.ConflictError(`Conflicting location for parameter ${existingRef.name}`, {
key: 'in',
values: (0, lodash_1.compact)([
existingRef.in,
external === null || external === void 0 ? void 0 : external.in,
parameterMetadata === null || parameterMetadata === void 0 ? void 0 : parameterMetadata.in,
]),
});
}
if ((parameterMetadata && existingRef.name !== parameterMetadata.name) ||
((external === null || external === void 0 ? void 0 : external.name) && existingRef.name !== (external === null || external === void 0 ? void 0 : external.name))) {
throw new errors_1.ConflictError(`Conflicting names for parameter`, {
key: 'name',
values: (0, lodash_1.compact)([
existingRef.name,
external === null || external === void 0 ? void 0 : external.name,
parameterMetadata === null || parameterMetadata === void 0 ? void 0 : parameterMetadata.name,
]),
});
}
return {
$ref: `#/components/parameters/${(_e = schemaMetadata._internal) === null || _e === void 0 ? void 0 : _e.refId}`,
};
}
generateInlineParameters(zodSchema, location) {
var _a;
const metadata = this.getMetadata(zodSchema);
const parameterMetadata = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) === null || _a === void 0 ? void 0 : _a.param;
const referencedSchema = this.getParameterRef(metadata, { in: location });
if (referencedSchema) {
return [referencedSchema];
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodObject')) {
const propTypes = zodSchema._def.shape();
const parameters = Object.entries(propTypes).map(([key, schema]) => {
var _a, _b;
const innerMetadata = this.getMetadata(schema);
const referencedSchema = this.getParameterRef(innerMetadata, {
in: location,
name: key,
});
if (referencedSchema) {
return referencedSchema;
}
const innerParameterMetadata = (_a = innerMetadata === null || innerMetadata === void 0 ? void 0 : innerMetadata.metadata) === null || _a === void 0 ? void 0 : _a.param;
if ((innerParameterMetadata === null || innerParameterMetadata === void 0 ? void 0 : innerParameterMetadata.name) &&
innerParameterMetadata.name !== key) {
throw new errors_1.ConflictError(`Conflicting names for parameter`, {
key: 'name',
values: [key, innerParameterMetadata.name],
});
}
if ((innerParameterMetadata === null || innerParameterMetadata === void 0 ? void 0 : innerParameterMetadata.in) &&
innerParameterMetadata.in !== location) {
throw new errors_1.ConflictError(`Conflicting location for parameter ${(_b = innerParameterMetadata.name) !== null && _b !== void 0 ? _b : key}`, {
key: 'in',
values: [location, innerParameterMetadata.in],
});
}
return this.generateParameter(schema.openapi({ param: { name: key, in: location } }));
});
return parameters;
}
if ((parameterMetadata === null || parameterMetadata === void 0 ? void 0 : parameterMetadata.in) && parameterMetadata.in !== location) {
throw new errors_1.ConflictError(`Conflicting location for parameter ${parameterMetadata.name}`, {
key: 'in',
values: [location, parameterMetadata.in],
});
}
return [
this.generateParameter(zodSchema.openapi({ param: { in: location } })),
];
}
generateParameter(zodSchema) {
var _a;
const metadata = this.getMetadata(zodSchema);
const paramMetadata = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) === null || _a === void 0 ? void 0 : _a.param;
const paramName = paramMetadata === null || paramMetadata === void 0 ? void 0 : paramMetadata.name;
const paramLocation = paramMetadata === null || paramMetadata === void 0 ? void 0 : paramMetadata.in;
if (!paramName) {
throw new errors_1.MissingParameterDataError({ missingField: 'name' });
}
if (!paramLocation) {
throw new errors_1.MissingParameterDataError({
missingField: 'in',
paramName,
});
}
const required = !this.isOptionalSchema(zodSchema) && !zodSchema.isNullable();
const schema = this.generateSimpleSchema(zodSchema);
return Object.assign({ in: paramLocation, name: paramName, schema,
required }, (paramMetadata ? this.buildParameterMetadata(paramMetadata) : {}));
}
/**
* Generates an OpenAPI SchemaObject or a ReferenceObject with all the provided metadata applied
*/
generateSimpleSchema(zodSchema) {
var _a, _b, _c;
const innerSchema = this.unwrapChained(zodSchema);
const metadata = this.getMetadata(zodSchema);
const defaultValue = this.getDefaultValue(zodSchema);
const refId = (_a = metadata === null || metadata === void 0 ? void 0 : metadata._internal) === null || _a === void 0 ? void 0 : _a.refId;
if (refId && this.schemaRefs[refId]) {
const schemaRef = this.schemaRefs[refId];
const referenceObject = {
$ref: this.generateSchemaRef(refId),
};
// New metadata from .openapi()
const newMetadata = (0, lodash_1.omitBy)(
// We do not want to check our "custom" metadata fields. We only want
// the plain metadata for a SchemaObject.
this.buildSchemaMetadata((_b = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) !== null && _b !== void 0 ? _b : {}), (value, key) => value === undefined || (0, lodash_1.objectEquals)(value, schemaRef[key]));
// New metadata from ZodSchema properties.
// Do not calculate schema metadata overrides if type is provided in .openapi
// https://github.com/asteasolutions/zod-to-openapi/pull/52/files/8ff707fe06e222bc573ed46cf654af8ee0b0786d#r996430801
const newSchemaMetadata = !newMetadata.type
? (0, lodash_1.omitBy)(this.constructReferencedOpenAPISchema(zodSchema, innerSchema, defaultValue), (value, key) => value === undefined || (0, lodash_1.objectEquals)(value, schemaRef[key]))
: {};
const appliedMetadata = this.applySchemaMetadata(newSchemaMetadata, newMetadata);
if (Object.keys(appliedMetadata).length > 0) {
return {
allOf: [referenceObject, appliedMetadata],
};
}
return referenceObject;
}
const result = ((_c = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) === null || _c === void 0 ? void 0 : _c.type)
? {
type: metadata === null || metadata === void 0 ? void 0 : metadata.metadata.type,
}
: this.toOpenAPISchema(innerSchema, zodSchema.isNullable(), defaultValue);
return (metadata === null || metadata === void 0 ? void 0 : metadata.metadata)
? this.applySchemaMetadata(result, metadata.metadata)
: (0, lodash_1.omitBy)(result, lodash_1.isNil);
}
generateSchemaDefinition(zodSchema) {
const metadata = this.getMetadata(zodSchema);
const refId = this.getRefId(zodSchema);
const simpleSchema = this.generateSimpleSchema(zodSchema);
const result = (metadata === null || metadata === void 0 ? void 0 : metadata.metadata)
? this.applySchemaMetadata(simpleSchema, metadata.metadata)
: simpleSchema;
if (refId) {
this.schemaRefs[refId] = result;
}
return result;
}
generateSchemaRef(refId) {
return `#/components/schemas/${refId}`;
}
getRequestBody(requestBody) {
if (!requestBody) {
return;
}
const { content } = requestBody, rest = __rest(requestBody, ["content"]);
const requestBodyContent = this.getBodyContent(content);
return Object.assign(Object.assign({}, rest), { content: requestBodyContent });
}
getParameters(request) {
if (!request) {
return [];
}
const { query, params, headers } = request;
const queryParameters = this.enhanceMissingParametersError(() => (query ? this.generateInlineParameters(query, 'query') : []), { location: 'query' });
const pathParameters = this.enhanceMissingParametersError(() => (params ? this.generateInlineParameters(params, 'path') : []), { location: 'path' });
const headerParameters = this.enhanceMissingParametersError(() => headers
? (0, zod_is_type_1.isZodType)(headers, 'ZodObject')
? this.generateInlineParameters(headers, 'header')
: headers.flatMap(header => this.generateInlineParameters(header, 'header'))
: [], { location: 'header' });
return [...pathParameters, ...queryParameters, ...headerParameters];
}
generatePath(route) {
const { method, path, request, responses } = route, pathItemConfig = __rest(route, ["method", "path", "request", "responses"]);
const generatedResponses = (0, lodash_1.mapValues)(responses, response => {
return this.getResponse(response);
});
const parameters = this.enhanceMissingParametersError(() => this.getParameters(request), { route: `${method} ${path}` });
const requestBody = this.getRequestBody(request === null || request === void 0 ? void 0 : request.body);
const routeDoc = {
[method]: Object.assign(Object.assign(Object.assign(Object.assign({}, pathItemConfig), (parameters.length > 0 ? { parameters } : {})), (requestBody ? { requestBody } : {})), { responses: generatedResponses }),
};
return routeDoc;
}
generateSingleRoute(route) {
const routeDoc = this.generatePath(route);
this.pathRefs[route.path] = Object.assign(Object.assign({}, this.pathRefs[route.path]), routeDoc);
return routeDoc;
}
generateSingleWebhook(route) {
const routeDoc = this.generatePath(route);
this.webhookRefs[route.path] = Object.assign(Object.assign({}, this.webhookRefs[route.path]), routeDoc);
return routeDoc;
}
getResponse(_a) {
var { content } = _a, rest = __rest(_a, ["content"]);
const responseContent = content
? { content: this.getBodyContent(content) }
: {};
return Object.assign(Object.assign({}, rest), responseContent);
}
getBodyContent(content) {
return (0, lodash_1.mapValues)(content, config => {
if (!(0, zod_is_type_1.isAnyZodType)(config.schema)) {
return config;
}
const { schema: configSchema } = config, rest = __rest(config, ["schema"]);
const schema = this.generateSimpleSchema(configSchema);
return Object.assign({ schema }, rest);
});
}
getZodStringCheck(zodString, kind) {
return zodString._def.checks.find((check) => {
return check.kind === kind;
});
}
/**
* Attempts to map Zod strings to known formats
* https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
*/
mapStringFormat(zodString) {
if (zodString.isUUID) {
return 'uuid';
}
if (zodString.isEmail) {
return 'email';
}
if (zodString.isURL) {
return 'uri';
}
if (zodString.isDatetime) {
return 'date-time';
}
return undefined;
}
mapDiscriminator(zodObjects, discriminator) {
// All schemas must be registered to use a discriminator
if (zodObjects.some(obj => this.getRefId(obj) === undefined)) {
return undefined;
}
const mapping = {};
zodObjects.forEach(obj => {
var _a;
const refId = this.getRefId(obj); // type-checked earlier
const value = (_a = obj.shape) === null || _a === void 0 ? void 0 : _a[discriminator];
if ((0, zod_is_type_1.isZodType)(value, 'ZodEnum')) {
value._def.values.forEach((enumValue) => {
mapping[enumValue] = this.generateSchemaRef(refId);
});
return;
}
const literalValue = value === null || value === void 0 ? void 0 : value._def.value;
// This should never happen because Zod checks the disciminator type but to keep the types happy
if (typeof literalValue !== 'string') {
throw new Error(`Discriminator ${discriminator} could not be found in one of the values of a discriminated union`);
}
mapping[literalValue] = this.generateSchemaRef(refId);
});
return {
propertyName: discriminator,
mapping,
};
}
mapNullableOfArray(objects, isNullable) {
if (isNullable) {
if (this.openApiVersionSatisfies(this.openAPIVersion, '3.1.0')) {
return [...objects, { type: 'null' }];
}
return [...objects, { nullable: true }];
}
return objects;
}
mapNullableType(type, isNullable) {
// Open API 3.1.0 made the `nullable` key invalid and instead you use type arrays
if (isNullable &&
this.openApiVersionSatisfies(this.openAPIVersion, '3.1.0')) {
return {
type: Array.isArray(type) ? [...type, 'null'] : [type, 'null'],
};
}
return {
type,
nullable: isNullable ? true : undefined,
};
}
getNumberChecks(checks) {
return Object.assign({}, ...checks.map(check => {
switch (check.kind) {
case 'min':
return (check.inclusive
? { minimum: check.value }
: this.openApiVersionSatisfies(this.openAPIVersion, '3.1.0')
? { exclusiveMinimum: check.value }
: { minimum: check.value, exclusiveMinimum: true }); // TODO: Fix in a separate PR
case 'max':
return (check.inclusive
? { maximum: check.value }
: this.openApiVersionSatisfies(this.openAPIVersion, '3.1.0')
? { exclusiveMaximum: check.value }
: { maximum: check.value, exclusiveMaximum: true }); // TODO: Fix in a separate PR
default:
return {};
}
}));
}
constructReferencedOpenAPISchema(zodSchema, innerSchema, defaultValue) {
var _a;
const isNullableSchema = zodSchema.isNullable();
const metadata = this.getMetadata(zodSchema);
if ((_a = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) === null || _a === void 0 ? void 0 : _a.type) {
return this.mapNullableType(metadata.metadata.type, isNullableSchema);
}
return this.toOpenAPISchema(innerSchema, isNullableSchema, defaultValue);
}
toOpenAPISchema(zodSchema, isNullable, defaultValue) {
var _a, _b, _c, _d, _e;
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodNull')) {
return { type: 'null' };
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodString')) {
const regexCheck = this.getZodStringCheck(zodSchema, 'regex');
const length = (_a = this.getZodStringCheck(zodSchema, 'length')) === null || _a === void 0 ? void 0 : _a.value;
const maxLength = Number.isFinite(zodSchema.minLength)
? (_b = zodSchema.minLength) !== null && _b !== void 0 ? _b : undefined
: undefined;
const minLength = Number.isFinite(zodSchema.maxLength)
? (_c = zodSchema.maxLength) !== null && _c !== void 0 ? _c : undefined
: undefined;
return Object.assign(Object.assign({}, this.mapNullableType('string', isNullable)), {
// FIXME: https://github.com/colinhacks/zod/commit/d78047e9f44596a96d637abb0ce209cd2732d88c
minLength: length !== null && length !== void 0 ? length : maxLength, maxLength: length !== null && length !== void 0 ? length : minLength, format: this.mapStringFormat(zodSchema), pattern: regexCheck === null || regexCheck === void 0 ? void 0 : regexCheck.regex.source, default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodNumber')) {
return Object.assign(Object.assign(Object.assign({}, this.mapNullableType(zodSchema.isInt ? 'integer' : 'number', isNullable)), this.getNumberChecks(zodSchema._def.checks)), { default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodBoolean')) {
return Object.assign(Object.assign({}, this.mapNullableType('boolean', isNullable)), { default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodEffects') &&
(zodSchema._def.effect.type === 'refinement' ||
zodSchema._def.effect.type === 'preprocess')) {
const innerSchema = zodSchema._def.schema;
return this.generateSimpleSchema(innerSchema);
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodLiteral')) {
return Object.assign(Object.assign({}, this.mapNullableType(typeof zodSchema._def.value, isNullable)), { enum: [zodSchema._def.value], default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodEnum')) {
// ZodEnum only accepts strings
return Object.assign(Object.assign({}, this.mapNullableType('string', isNullable)), { enum: zodSchema._def.values, default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodNativeEnum')) {
const { type, values } = (0, enum_info_1.enumInfo)(zodSchema._def.values);
if (type === 'mixed') {
// enum Test {
// A = 42,
// B = 'test',
// }
//
// const result = z.nativeEnum(Test).parse('42');
//
// This is an error, so we can't just say it's a 'string'
throw new errors_1.ZodToOpenAPIError('Enum has mixed string and number values, please specify the OpenAPI type manually');
}
return Object.assign(Object.assign({}, this.mapNullableType(type === 'numeric' ? 'integer' : 'string', isNullable)), { enum: values, default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodObject')) {
return this.toOpenAPIObjectSchema(zodSchema, isNullable, defaultValue);
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodArray')) {
const itemType = zodSchema._def.type;
return Object.assign(Object.assign({}, this.mapNullableType('array', isNullable)), { items: this.generateSimpleSchema(itemType), minItems: (_d = zodSchema._def.minLength) === null || _d === void 0 ? void 0 : _d.value, maxItems: (_e = zodSchema._def.maxLength) === null || _e === void 0 ? void 0 : _e.value, default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodTuple')) {
const { items } = zodSchema._def;
const tupleLength = items.length;
const schemas = items.map(schema => this.generateSimpleSchema(schema));
const uniqueSchemas = (0, lodash_1.uniq)(schemas);
if (uniqueSchemas.length === 1) {
return {
type: 'array',
items: uniqueSchemas[0],
minItems: tupleLength,
maxItems: tupleLength,
};
}
return Object.assign(Object.assign({}, this.mapNullableType('array', isNullable)), { items: {
anyOf: uniqueSchemas,
}, minItems: tupleLength, maxItems: tupleLength });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodUnion')) {
const options = this.flattenUnionTypes(zodSchema);
return {
anyOf: this.mapNullableOfArray(options.map(schema => this.generateSimpleSchema(schema)), isNullable),
default: defaultValue,
};
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDiscriminatedUnion')) {
const options = [...zodSchema.options.values()];
const optionSchema = options.map(schema => this.generateSimpleSchema(schema));
if (isNullable) {
return {
oneOf: this.mapNullableOfArray(optionSchema, isNullable),
default: defaultValue,
};
}
return {
oneOf: optionSchema,
discriminator: this.mapDiscriminator(options, zodSchema.discriminator),
default: defaultValue,
};
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodIntersection')) {
const subtypes = this.flattenIntersectionTypes(zodSchema);
const allOfSchema = {
allOf: subtypes.map(schema => this.generateSimpleSchema(schema)),
};
if (isNullable) {
return {
anyOf: this.mapNullableOfArray([allOfSchema], isNullable),
default: defaultValue,
};
}
return Object.assign(Object.assign({}, allOfSchema), { default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodRecord')) {
const propertiesType = zodSchema._def.valueType;
return Object.assign(Object.assign({}, this.mapNullableType('object', isNullable)), { additionalProperties: this.generateSimpleSchema(propertiesType), default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodUnknown')) {
return {};
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDate')) {
return Object.assign(Object.assign({}, this.mapNullableType('string', isNullable)), { default: defaultValue });
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodPipeline')) {
return this.toOpenAPISchema(zodSchema._def.in, isNullable, defaultValue);
}
const refId = this.getRefId(zodSchema);
throw new errors_1.UnknownZodTypeError({
currentSchema: zodSchema._def,
schemaName: refId,
});
}
isOptionalSchema(zodSchema) {
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodEffects')) {
return this.isOptionalSchema(zodSchema._def.schema);
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDefault')) {
return this.isOptionalSchema(zodSchema._def.innerType);
}
return zodSchema.isOptional();
}
getDefaultValue(zodSchema) {
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodOptional') ||
(0, zod_is_type_1.isZodType)(zodSchema, 'ZodNullable')) {
return this.getDefaultValue(zodSchema.unwrap());
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodEffects')) {
return this.getDefaultValue(zodSchema._def.schema);
}
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDefault')) {
return zodSchema._def.defaultValue();
}
return undefined;
}
requiredKeysOf(objectSchema) {
return Object.entries(objectSchema._def.shape())
.filter(([_key, type]) => !this.isOptionalSchema(type))
.map(([key, _type]) => key);
}
toOpenAPIObjectSchema(zodSchema, isNullable, defaultValue) {
var _a;
const extendedFrom = (_a = this.getInternalMetadata(zodSchema)) === null || _a === void 0 ? void 0 : _a.extendedFrom;
const parentShape = extendedFrom === null || extendedFrom === void 0 ? void 0 : extendedFrom.schema._def.shape();
const childShape = zodSchema._def.shape();
const keysRequiredByParent = extendedFrom
? this.requiredKeysOf(extendedFrom.schema)
: [];
const keysRequiredByChild = this.requiredKeysOf(zodSchema);
const propsOfParent = parentShape
? (0, lodash_1.mapValues)(parentShape, _ => this.generateSimpleSchema(_))
: {};
const propsOfChild = (0, lodash_1.mapValues)(childShape, _ => this.generateSimpleSchema(_));
const properties = Object.fromEntries(Object.entries(propsOfChild).filter(([key, type]) => {
return !(0, lodash_1.objectEquals)(propsOfParent[key], type);
}));
const additionallyRequired = keysRequiredByChild.filter(prop => !keysRequiredByParent.includes(prop));
const unknownKeysOption = zodSchema._def.unknownKeys;
const objectData = Object.assign(Object.assign(Object.assign(Object.assign({}, this.mapNullableType('object', isNullable)), { default: defaultValue, properties }), (additionallyRequired.length > 0
? { required: additionallyRequired }
: {})), (unknownKeysOption === 'strict'
? { additionalProperties: false }
: {}));
if (extendedFrom) {
return {
allOf: [
{ $ref: `#/components/schemas/${extendedFrom.refId}` },
objectData,
],
};
}
return objectData;
}
flattenUnionTypes(schema) {
if (!(0, zod_is_type_1.isZodType)(schema, 'ZodUnion')) {
return [schema];
}
const options = schema._def.options;
return options.flatMap(option => this.flattenUnionTypes(option));
}
flattenIntersectionTypes(schema) {
if (!(0, zod_is_type_1.isZodType)(schema, 'ZodIntersection')) {
return [schema];
}
const leftSubTypes = this.flattenIntersectionTypes(schema._def.left);
const rightSubTypes = this.flattenIntersectionTypes(schema._def.right);
return [...leftSubTypes, ...rightSubTypes];
}
unwrapChained(schema) {
if ((0, zod_is_type_1.isZodType)(schema, 'ZodOptional') ||
(0, zod_is_type_1.isZodType)(schema, 'ZodNullable') ||
(0, zod_is_type_1.isZodType)(schema, 'ZodBranded')) {
return this.unwrapChained(schema.unwrap());
}
if ((0, zod_is_type_1.isZodType)(schema, 'ZodDefault')) {
return this.unwrapChained(schema._def.innerType);
}
if ((0, zod_is_type_1.isZodType)(schema, 'ZodEffects') &&
['refinement', 'transform'].includes(schema._def.effect.type)) {
return this.unwrapChained(schema._def.schema);
}
return schema;
}
/**
* A method that omits all custom keys added to the regular OpenAPI
* metadata properties
*/
buildSchemaMetadata(metadata) {
return (0, lodash_1.omitBy)((0, lodash_1.omit)(metadata, ['param']), lodash_1.isNil);
}
buildParameterMetadata(metadata) {
return (0, lodash_1.omitBy)(metadata, lodash_1.isNil);
}
getMetadata(zodSchema) {
var _a;
const innerSchema = this.unwrapChained(zodSchema);
const metadata = zodSchema._def.openapi
? zodSchema._def.openapi
: innerSchema._def.openapi;
/**
* Every zod schema can receive a `description` by using the .describe method.
* That description should be used when generating an OpenApi schema.
* The `??` bellow makes sure we can handle both:
* - schema.describe('Test').optional()
* - schema.optional().describe('Test')
*/
const zodDescription = (_a = zodSchema.description) !== null && _a !== void 0 ? _a : innerSchema.description;
// A description provided from .openapi() should be taken with higher precedence
return {
_internal: metadata === null || metadata === void 0 ? void 0 : metadata._internal,
metadata: Object.assign({ description: zodDescription }, metadata === null || metadata === void 0 ? void 0 : metadata.metadata),
};
}
getInternalMetadata(zodSchema) {
const innerSchema = this.unwrapChained(zodSchema);
const openapi = zodSchema._def.openapi
? zodSchema._def.openapi
: innerSchema._def.openapi;
return openapi === null || openapi === void 0 ? void 0 : openapi._internal;
}
getRefId(zodSchema) {
var _a;
return (_a = this.getInternalMetadata(zodSchema)) === null || _a === void 0 ? void 0 : _a.refId;
}
applySchemaMetadata(initialData, metadata) {
return (0, lodash_1.omitBy)(Object.assign(Object.assign({}, initialData), this.buildSchemaMetadata(metadata)), lodash_1.isNil);
}
enhanceMissingParametersError(action, paramsToAdd) {
try {
return action();
}
catch (error) {
if (error instanceof errors_1.MissingParameterDataError) {
throw new errors_1.MissingParameterDataError(Object.assign(Object.assign({}, error.data), paramsToAdd));
}
throw error;
}
}
}
exports.OpenAPIGenerator = OpenAPIGenerator;