@asteasolutions/zod-to-openapi
Advanced tools
Comparing version 2.3.0 to 3.0.0
@@ -1,4 +0,5 @@ | ||
export * from './zod-extensions'; | ||
export { ZodOpenAPIMetadata, extendZodWithOpenApi } from './zod-extensions'; | ||
export * from './openapi-metadata'; | ||
export { OpenAPIGenerator } from './openapi-generator'; | ||
export { OpenAPIRegistry, RouteConfig, ResponseConfig, } from './openapi-registry'; | ||
export * as OpenAPI from 'openapi3-ts'; |
@@ -29,4 +29,6 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.OpenAPI = exports.OpenAPIRegistry = exports.OpenAPIGenerator = void 0; | ||
__exportStar(require("./zod-extensions"), exports); | ||
exports.OpenAPI = exports.OpenAPIRegistry = exports.OpenAPIGenerator = exports.extendZodWithOpenApi = void 0; | ||
var zod_extensions_1 = require("./zod-extensions"); | ||
Object.defineProperty(exports, "extendZodWithOpenApi", { enumerable: true, get: function () { return zod_extensions_1.extendZodWithOpenApi; } }); | ||
__exportStar(require("./openapi-metadata"), exports); | ||
var openapi_generator_1 = require("./openapi-generator"); | ||
@@ -33,0 +35,0 @@ Object.defineProperty(exports, "OpenAPIGenerator", { enumerable: true, get: function () { return openapi_generator_1.OpenAPIGenerator; } }); |
@@ -9,4 +9,4 @@ export declare function isUndefined<T>(value: any): value is undefined; | ||
[K in keyof T]: T[keyof T]; | ||
}>>(object: T, predicate: (val: T[keyof T]) => boolean): Result; | ||
}>>(object: T, predicate: (val: T[keyof T], key: keyof T) => boolean): Result; | ||
export declare function compact<T extends any>(arr: (T | null | undefined)[]): T[]; | ||
export declare function objectEquals(x: any, y: any): boolean; |
@@ -33,3 +33,3 @@ "use strict"; | ||
Object.entries(object).forEach(([key, value]) => { | ||
if (!predicate(value)) { | ||
if (!predicate(value, key)) { | ||
result[key] = value; | ||
@@ -36,0 +36,0 @@ } |
@@ -25,2 +25,3 @@ import type { z } from 'zod'; | ||
ZodVoid: z.ZodVoid; | ||
ZodDate: z.ZodDate; | ||
}; | ||
@@ -27,0 +28,0 @@ export declare function isZodType<TypeName extends keyof ZodTypes>(schema: object, typeName: TypeName): schema is ZodTypes[TypeName]; |
@@ -1,14 +0,9 @@ | ||
import { OpenAPIObject, InfoObject, ServerObject, SecurityRequirementObject, TagObject, ExternalDocumentationObject, ComponentsObject } from 'openapi3-ts'; | ||
import { OpenAPIObject } from 'openapi3-ts'; | ||
import { OpenAPIDefinitions } from './openapi-registry'; | ||
interface OpenAPIObjectConfig { | ||
openapi: string; | ||
info: InfoObject; | ||
servers?: ServerObject[]; | ||
security?: SecurityRequirementObject[]; | ||
tags?: TagObject[]; | ||
externalDocs?: ExternalDocumentationObject; | ||
[key: string]: unknown; | ||
} | ||
declare const openApiVersions: readonly ["3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.1.0"]; | ||
export declare type OpenApiVersion = typeof openApiVersions[number]; | ||
export declare type OpenAPIObjectConfig = Omit<OpenAPIObject, 'paths' | 'components' | 'webhooks' | 'openapi'>; | ||
export declare class OpenAPIGenerator { | ||
private definitions; | ||
private openAPIVersion; | ||
private schemaRefs; | ||
@@ -19,5 +14,5 @@ private paramRefs; | ||
private rawComponents; | ||
constructor(definitions: OpenAPIDefinitions[]); | ||
constructor(definitions: OpenAPIDefinitions[], openAPIVersion: OpenApiVersion); | ||
generateDocument(config: OpenAPIObjectConfig): OpenAPIObject; | ||
generateComponents(): ComponentsObject; | ||
generateComponents(): Pick<OpenAPIObject, 'components'>; | ||
private buildComponents; | ||
@@ -49,4 +44,10 @@ private sortDefinitions; | ||
private mapStringFormat; | ||
private mapDiscriminator; | ||
private openApiVersionSatisfies; | ||
private mapNullableOfArray; | ||
private mapNullableType; | ||
private toOpenAPISchema; | ||
private isOptionalSchema; | ||
private getDefaultValue; | ||
private requiredKeysOf; | ||
private toOpenAPIObjectSchema; | ||
@@ -56,7 +57,12 @@ private flattenUnionTypes; | ||
private unwrapChained; | ||
/** | ||
* A method that omits all custom keys added to the regular OpenAPI | ||
* metadata properties | ||
*/ | ||
private buildSchemaMetadata; | ||
private buildParameterMetadata; | ||
private getMetadata; | ||
private getInternalMetadata; | ||
private applySchemaMetadata; | ||
} | ||
export {}; |
@@ -19,5 +19,8 @@ "use strict"; | ||
const enum_info_1 = require("./lib/enum-info"); | ||
// 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) { | ||
constructor(definitions, openAPIVersion) { | ||
this.definitions = definitions; | ||
this.openAPIVersion = openAPIVersion; | ||
this.schemaRefs = {}; | ||
@@ -28,2 +31,4 @@ this.paramRefs = {}; | ||
this.rawComponents = []; | ||
this.openApiVersionSatisfies = (inputVersion, comparison) => openApiVersions.indexOf(inputVersion) >= | ||
openApiVersions.indexOf(comparison); | ||
this.sortDefinitions(); | ||
@@ -33,3 +38,3 @@ } | ||
this.definitions.forEach(definition => this.generateSingle(definition)); | ||
return Object.assign(Object.assign(Object.assign({}, config), { components: this.buildComponents(), paths: this.pathRefs }), (Object.keys(this.webhookRefs).length && { | ||
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, | ||
@@ -87,3 +92,3 @@ })); | ||
generateParameterDefinition(zodSchema) { | ||
const metadata = this.getMetadata(zodSchema); | ||
const metadata = this.getInternalMetadata(zodSchema); | ||
const result = this.generateParameter(zodSchema); | ||
@@ -96,7 +101,8 @@ if (metadata === null || metadata === void 0 ? void 0 : metadata.refId) { | ||
getParameterRef(schemaMetadata, external) { | ||
const parameterMetadata = schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata.param; | ||
const existingRef = (schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata.refId) | ||
? this.paramRefs[schemaMetadata.refId] | ||
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 (!(schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata.refId) || !existingRef) { | ||
if (!((_d = schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata._internal) === null || _d === void 0 ? void 0 : _d.refId) || !existingRef) { | ||
return undefined; | ||
@@ -127,8 +133,9 @@ } | ||
return { | ||
$ref: `#/components/parameters/${schemaMetadata.refId}`, | ||
$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 = metadata === null || metadata === void 0 ? void 0 : metadata.param; | ||
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 }); | ||
@@ -141,3 +148,3 @@ if (referencedSchema) { | ||
const parameters = Object.entries(propTypes).map(([key, schema]) => { | ||
var _a; | ||
var _a, _b; | ||
const innerMetadata = this.getMetadata(schema); | ||
@@ -151,3 +158,3 @@ const referencedSchema = this.getParameterRef(innerMetadata, { | ||
} | ||
const innerParameterMetadata = innerMetadata === null || innerMetadata === void 0 ? void 0 : innerMetadata.param; | ||
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) && | ||
@@ -162,3 +169,3 @@ innerParameterMetadata.name !== key) { | ||
innerParameterMetadata.in !== location) { | ||
throw new errors_1.ConflictError(`Conflicting location for parameter ${(_a = innerParameterMetadata.name) !== null && _a !== void 0 ? _a : key}`, { | ||
throw new errors_1.ConflictError(`Conflicting location for parameter ${(_b = innerParameterMetadata.name) !== null && _b !== void 0 ? _b : key}`, { | ||
key: 'in', | ||
@@ -183,4 +190,5 @@ values: [location, innerParameterMetadata.in], | ||
generateParameter(zodSchema) { | ||
var _a; | ||
const metadata = this.getMetadata(zodSchema); | ||
const paramMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.param; | ||
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; | ||
@@ -197,3 +205,3 @@ const paramLocation = paramMetadata === null || paramMetadata === void 0 ? void 0 : paramMetadata.in; | ||
} | ||
const required = !zodSchema.isOptional() && !zodSchema.isNullable(); | ||
const required = !this.isOptionalSchema(zodSchema) && !zodSchema.isNullable(); | ||
const schema = this.generateSimpleSchema(zodSchema); | ||
@@ -207,13 +215,24 @@ return Object.assign({ in: paramLocation, name: paramName, schema, | ||
generateSimpleSchema(zodSchema) { | ||
var _a, _b, _c, _d; | ||
const innerSchema = this.unwrapChained(zodSchema); | ||
const metadata = zodSchema._def.openapi | ||
? zodSchema._def.openapi | ||
: innerSchema._def.openapi; | ||
const refId = metadata === null || metadata === void 0 ? void 0 : metadata.refId; | ||
const metadata = (_a = zodSchema._def.openapi) !== null && _a !== void 0 ? _a : innerSchema._def.openapi; | ||
const defaultValue = this.getDefaultValue(zodSchema); | ||
const refId = (_b = metadata === null || metadata === void 0 ? void 0 : metadata._internal) === null || _b === void 0 ? void 0 : _b.refId; | ||
if (refId && this.schemaRefs[refId]) { | ||
const schemaRef = this.schemaRefs[refId]; | ||
const referenceObject = { | ||
$ref: `#/components/schemas/${refId}`, | ||
}; | ||
const nullableMetadata = zodSchema.isNullable() ? { nullable: true } : {}; | ||
const appliedMetadata = this.applySchemaMetadata(nullableMetadata, metadata); | ||
// 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((_c = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) !== null && _c !== void 0 ? _c : {}), (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.toOpenAPISchema(innerSchema, zodSchema.isNullable(), defaultValue), (value, key) => value === undefined || (0, lodash_1.objectEquals)(value, schemaRef[key])) | ||
: {}; | ||
const appliedMetadata = this.applySchemaMetadata(newSchemaMetadata, newMetadata); | ||
if (Object.keys(appliedMetadata).length > 0) { | ||
@@ -226,9 +245,9 @@ return { | ||
} | ||
const result = (metadata === null || metadata === void 0 ? void 0 : metadata.type) | ||
const result = ((_d = metadata === null || metadata === void 0 ? void 0 : metadata.metadata) === null || _d === void 0 ? void 0 : _d.type) | ||
? { | ||
type: metadata === null || metadata === void 0 ? void 0 : metadata.type, | ||
type: metadata === null || metadata === void 0 ? void 0 : metadata.metadata.type, | ||
} | ||
: this.toOpenAPISchema(innerSchema, zodSchema.isNullable()); | ||
return metadata | ||
? this.applySchemaMetadata(result, metadata) | ||
: 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); | ||
@@ -238,3 +257,3 @@ } | ||
const simpleSchema = this.generateSimpleSchema(zodSchema); | ||
if (simpleSchema.$ref) { | ||
if ('$ref' in simpleSchema && simpleSchema.$ref) { | ||
return simpleSchema; | ||
@@ -247,7 +266,8 @@ } | ||
generateSchemaDefinition(zodSchema) { | ||
var _a; | ||
const metadata = this.getMetadata(zodSchema); | ||
const refId = metadata === null || metadata === void 0 ? void 0 : metadata.refId; | ||
const refId = (_a = metadata === null || metadata === void 0 ? void 0 : metadata._internal) === null || _a === void 0 ? void 0 : _a.refId; | ||
const simpleSchema = this.generateSimpleSchema(zodSchema); | ||
const result = metadata | ||
? this.applySchemaMetadata(simpleSchema, metadata) | ||
const result = (metadata === null || metadata === void 0 ? void 0 : metadata.metadata) | ||
? this.applySchemaMetadata(simpleSchema, metadata.metadata) | ||
: simpleSchema; | ||
@@ -340,4 +360,46 @@ if (refId) { | ||
} | ||
toOpenAPISchema(zodSchema, isNullable) { | ||
var _a, _b, _c, _d, _e; | ||
mapDiscriminator(zodObjects, discriminator) { | ||
// All schemas must be registered to use a discriminator | ||
if (zodObjects.some(obj => { var _a, _b; return ((_b = (_a = obj._def.openapi) === null || _a === void 0 ? void 0 : _a._internal) === null || _b === void 0 ? void 0 : _b.refId) === undefined; })) { | ||
return undefined; | ||
} | ||
const mapping = {}; | ||
zodObjects.forEach(obj => { | ||
var _a, _b, _c, _d; | ||
const value = (_b = (_a = obj.shape) === null || _a === void 0 ? void 0 : _a[discriminator]) === null || _b === void 0 ? void 0 : _b._def.value; | ||
// This should never happen because Zod checks the disciminator type but to keep the types happy | ||
if (typeof value !== 'string') { | ||
throw new Error(`Discriminator ${discriminator} could not be found in one of the values of a discriminated union`); | ||
} | ||
mapping[value] = `#/components/schemas/${(_d = (_c = obj._def.openapi) === null || _c === void 0 ? void 0 : _c._internal) === null || _d === void 0 ? void 0 : _d.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, | ||
}; | ||
} | ||
toOpenAPISchema(zodSchema, isNullable, defaultValue) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h; | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodNull')) { | ||
@@ -348,27 +410,16 @@ return { type: 'null' }; | ||
const regexCheck = this.getZodStringCheck(zodSchema, 'regex'); | ||
return { | ||
type: 'string', | ||
nullable: isNullable ? true : undefined, | ||
format: this.mapStringFormat(zodSchema), | ||
pattern: regexCheck === null || regexCheck === void 0 ? void 0 : regexCheck.regex.source, | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType('string', isNullable)), { | ||
// FIXME: https://github.com/colinhacks/zod/commit/d78047e9f44596a96d637abb0ce209cd2732d88c | ||
minLength: Number.isFinite(zodSchema.minLength) | ||
? (_a = zodSchema.minLength) !== null && _a !== void 0 ? _a : undefined | ||
: undefined, maxLength: Number.isFinite(zodSchema.maxLength) | ||
? (_b = zodSchema.maxLength) !== null && _b !== void 0 ? _b : undefined | ||
: undefined, 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 { | ||
type: zodSchema.isInt ? 'integer' : 'number', | ||
minimum: (_a = zodSchema.minValue) !== null && _a !== void 0 ? _a : undefined, | ||
maximum: (_b = zodSchema.maxValue) !== null && _b !== void 0 ? _b : undefined, | ||
nullable: isNullable ? true : undefined, | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType(zodSchema.isInt ? 'integer' : 'number', isNullable)), { minimum: (_c = zodSchema.minValue) !== null && _c !== void 0 ? _c : undefined, maximum: (_d = zodSchema.maxValue) !== null && _d !== void 0 ? _d : undefined, default: defaultValue }); | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodBoolean')) { | ||
return { | ||
type: 'boolean', | ||
nullable: isNullable ? true : undefined, | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType('boolean', isNullable)), { default: defaultValue }); | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDefault')) { | ||
const innerSchema = zodSchema._def.innerType; | ||
return this.generateInnerSchema(innerSchema); | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodEffects') && | ||
@@ -381,15 +432,7 @@ (zodSchema._def.effect.type === 'refinement' || | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodLiteral')) { | ||
return { | ||
type: typeof zodSchema._def.value, | ||
nullable: isNullable ? true : undefined, | ||
enum: [zodSchema._def.value], | ||
}; | ||
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 { | ||
type: 'string', | ||
nullable: isNullable ? true : undefined, | ||
enum: zodSchema._def.values, | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType('string', isNullable)), { enum: zodSchema._def.values, default: defaultValue }); | ||
} | ||
@@ -409,19 +452,10 @@ if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodNativeEnum')) { | ||
} | ||
return { | ||
type: type === 'numeric' ? 'number' : 'string', | ||
nullable: isNullable ? true : undefined, | ||
enum: values, | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType(type === 'numeric' ? 'number' : 'string', isNullable)), { enum: values, default: defaultValue }); | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodObject')) { | ||
return this.toOpenAPIObjectSchema(zodSchema, isNullable); | ||
return this.toOpenAPIObjectSchema(zodSchema, isNullable, defaultValue); | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodArray')) { | ||
const itemType = zodSchema._def.type; | ||
return { | ||
type: 'array', | ||
items: this.generateInnerSchema(itemType), | ||
minItems: (_c = zodSchema._def.minLength) === null || _c === void 0 ? void 0 : _c.value, | ||
maxItems: (_d = zodSchema._def.maxLength) === null || _d === void 0 ? void 0 : _d.value, | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType('array', isNullable)), { items: this.generateInnerSchema(itemType), minItems: (_e = zodSchema._def.minLength) === null || _e === void 0 ? void 0 : _e.value, maxItems: (_f = zodSchema._def.maxLength) === null || _f === void 0 ? void 0 : _f.value, default: defaultValue }); | ||
} | ||
@@ -431,3 +465,4 @@ if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodUnion')) { | ||
return { | ||
anyOf: options.map(schema => this.generateInnerSchema(schema)), | ||
anyOf: this.mapNullableOfArray(options.map(schema => this.generateInnerSchema(schema)), isNullable), | ||
default: defaultValue, | ||
}; | ||
@@ -437,4 +472,13 @@ } | ||
const options = [...zodSchema.options.values()]; | ||
const optionSchema = options.map(schema => this.generateInnerSchema(schema)); | ||
if (isNullable) { | ||
return { | ||
oneOf: this.mapNullableOfArray(optionSchema, isNullable), | ||
default: defaultValue, | ||
}; | ||
} | ||
return { | ||
anyOf: options.map(schema => this.generateInnerSchema(schema)), | ||
oneOf: optionSchema, | ||
discriminator: this.mapDiscriminator(options, zodSchema.discriminator), | ||
default: defaultValue, | ||
}; | ||
@@ -444,12 +488,16 @@ } | ||
const subtypes = this.flattenIntersectionTypes(zodSchema); | ||
return { | ||
const allOfSchema = { | ||
allOf: subtypes.map(schema => this.generateInnerSchema(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 { | ||
type: 'object', | ||
additionalProperties: this.generateInnerSchema(propertiesType), | ||
}; | ||
return Object.assign(Object.assign({}, this.mapNullableType('object', isNullable)), { additionalProperties: this.generateInnerSchema(propertiesType), default: defaultValue }); | ||
} | ||
@@ -459,3 +507,6 @@ if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodUnknown')) { | ||
} | ||
const refId = (_e = this.getMetadata(zodSchema)) === null || _e === void 0 ? void 0 : _e.refId; | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDate')) { | ||
return Object.assign(Object.assign({}, this.mapNullableType('string', isNullable)), { default: defaultValue }); | ||
} | ||
const refId = (_h = (_g = this.getMetadata(zodSchema)) === null || _g === void 0 ? void 0 : _g._internal) === null || _h === void 0 ? void 0 : _h.refId; | ||
throw new errors_1.UnknownZodTypeError({ | ||
@@ -475,27 +526,39 @@ currentSchema: zodSchema._def, | ||
} | ||
toOpenAPIObjectSchema(zodSchema, isNullable) { | ||
var _a, _b, _c; | ||
const extendedFrom = (_a = zodSchema._def.openapi) === null || _a === void 0 ? void 0 : _a.extendedFrom; | ||
const propTypes = zodSchema._def.shape(); | ||
const unknownKeysOption = zodSchema._unknownKeys; | ||
const requiredProperties = Object.entries(propTypes) | ||
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); | ||
const schemaProperties = (0, lodash_1.mapValues)(propTypes, propSchema => this.generateInnerSchema(propSchema)); | ||
let alreadyRegistered = []; | ||
let alreadyRequired = []; | ||
if (extendedFrom) { | ||
const registeredSchema = this.schemaRefs[extendedFrom]; | ||
if (!registeredSchema) { | ||
throw new Error(`Attempt to extend an unregistered schema with id ${extendedFrom}.`); | ||
} | ||
const registeredProperties = (_b = registeredSchema.properties) !== null && _b !== void 0 ? _b : {}; | ||
alreadyRegistered = Object.keys(registeredProperties).filter(propKey => { | ||
return (0, lodash_1.objectEquals)(schemaProperties[propKey], registeredProperties[propKey]); | ||
}); | ||
alreadyRequired = (_c = registeredSchema.required) !== null && _c !== void 0 ? _c : []; | ||
} | ||
const properties = (0, lodash_1.omit)(schemaProperties, alreadyRegistered); | ||
const additionallyRequired = requiredProperties.filter(prop => !alreadyRequired.includes(prop)); | ||
const objectData = Object.assign(Object.assign(Object.assign({ type: 'object', properties }, (isNullable ? { nullable: true } : {})), (additionallyRequired.length > 0 | ||
} | ||
toOpenAPIObjectSchema(zodSchema, isNullable, defaultValue) { | ||
var _a, _b; | ||
const extendedFrom = (_b = (_a = zodSchema._def.openapi) === null || _a === void 0 ? void 0 : _a._internal) === null || _b === void 0 ? void 0 : _b.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.generateInnerSchema(_)) | ||
: {}; | ||
const propsOfChild = (0, lodash_1.mapValues)(childShape, _ => this.generateInnerSchema(_)); | ||
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._unknownKeys; | ||
const objectData = Object.assign(Object.assign(Object.assign(Object.assign({}, this.mapNullableType('object', isNullable)), { default: defaultValue, properties }), (additionallyRequired.length > 0 | ||
? { required: additionallyRequired } | ||
@@ -507,3 +570,6 @@ : {})), (unknownKeysOption === 'passthrough' | ||
return { | ||
allOf: [{ $ref: `#/components/schemas/${extendedFrom}` }, objectData], | ||
allOf: [ | ||
{ $ref: `#/components/schemas/${extendedFrom.refId}` }, | ||
objectData, | ||
], | ||
}; | ||
@@ -541,5 +607,8 @@ } | ||
} | ||
/** | ||
* A method that omits all custom keys added to the regular OpenAPI | ||
* metadata properties | ||
*/ | ||
buildSchemaMetadata(metadata) { | ||
// A place to omit all custom keys added to the openapi | ||
return (0, lodash_1.omitBy)((0, lodash_1.omit)(metadata, ['param', 'refId', 'extendedFrom']), lodash_1.isNil); | ||
return (0, lodash_1.omitBy)((0, lodash_1.omit)(metadata, ['param']), lodash_1.isNil); | ||
} | ||
@@ -556,2 +625,9 @@ buildParameterMetadata(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; | ||
} | ||
applySchemaMetadata(initialData, metadata) { | ||
@@ -558,0 +634,0 @@ return (0, lodash_1.omitBy)(Object.assign(Object.assign({}, initialData), this.buildSchemaMetadata(metadata)), lodash_1.isNil); |
@@ -93,3 +93,4 @@ import { CallbackObject, ComponentsObject, EncodingObject, ExampleObject, ExamplesObject, HeaderObject, HeadersObject, ISpecificationExtension, LinkObject, LinksObject, OperationObject, ParameterObject, ReferenceObject, RequestBodyObject, ResponseObject, SchemaObject, SecuritySchemeObject } from 'openapi3-ts'; | ||
}; | ||
private schemaWithRefId; | ||
} | ||
export {}; |
@@ -18,6 +18,5 @@ "use strict"; | ||
register(refId, zodSchema) { | ||
const currentMetadata = zodSchema._def.openapi; | ||
const schemaWithMetadata = zodSchema.openapi(Object.assign(Object.assign({}, currentMetadata), { refId })); | ||
this._definitions.push({ type: 'schema', schema: schemaWithMetadata }); | ||
return schemaWithMetadata; | ||
const schemaWithRefId = this.schemaWithRefId(refId, zodSchema); | ||
this._definitions.push({ type: 'schema', schema: schemaWithRefId }); | ||
return schemaWithRefId; | ||
} | ||
@@ -28,5 +27,6 @@ /** | ||
registerParameter(refId, zodSchema) { | ||
var _a, _b; | ||
const currentMetadata = zodSchema._def.openapi; | ||
const schemaWithMetadata = zodSchema.openapi(Object.assign(Object.assign({}, currentMetadata), { param: Object.assign(Object.assign({}, currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.param), { name: (_b = (_a = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.param) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : refId }), refId })); | ||
var _a, _b, _c; | ||
const schemaWithRefId = this.schemaWithRefId(refId, zodSchema); | ||
const currentMetadata = (_a = schemaWithRefId._def.openapi) === null || _a === void 0 ? void 0 : _a.metadata; | ||
const schemaWithMetadata = zodSchema.openapi(Object.assign(Object.assign({}, currentMetadata), { param: Object.assign(Object.assign({}, currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.param), { name: (_c = (_b = currentMetadata === null || currentMetadata === void 0 ? void 0 : currentMetadata.param) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : refId }) })); | ||
this._definitions.push({ | ||
@@ -76,3 +76,6 @@ type: 'parameter', | ||
} | ||
schemaWithRefId(refId, zodSchema) { | ||
return zodSchema.internal_openapi({ refId }); | ||
} | ||
} | ||
exports.OpenAPIRegistry = OpenAPIRegistry; |
import { ParameterObject, SchemaObject } from 'openapi3-ts'; | ||
import type { z } from 'zod'; | ||
export interface ZodOpenAPIMetadata<T = any> extends SchemaObject { | ||
refId?: string; | ||
extendedFrom?: string; | ||
import type { z, ZodObject, ZodRawShape } from 'zod'; | ||
declare type ExampleValue<T> = T extends Date ? string : T; | ||
export interface ZodOpenAPIMetadata<T = any, E = ExampleValue<T>> extends SchemaObject { | ||
param?: Partial<ParameterObject> & { | ||
example?: T; | ||
example?: E; | ||
}; | ||
example?: T; | ||
example?: E; | ||
examples?: E[]; | ||
default?: T; | ||
} | ||
export interface ZodOpenAPIInternalMetadata { | ||
refId?: string; | ||
extendedFrom?: { | ||
refId: string; | ||
schema: ZodObject<ZodRawShape>; | ||
}; | ||
} | ||
export interface ZodOpenApiFullMetadata<T = any> { | ||
_internal?: ZodOpenAPIInternalMetadata; | ||
metadata?: ZodOpenAPIMetadata<T>; | ||
} | ||
declare module 'zod' { | ||
interface ZodTypeDef { | ||
openapi?: ZodOpenAPIMetadata; | ||
openapi?: ZodOpenApiFullMetadata; | ||
} | ||
abstract class ZodSchema<Output, Def extends ZodTypeDef, Input = Output> { | ||
openapi<T extends ZodSchema<any>>(this: T, metadata: Partial<ZodOpenAPIMetadata<z.infer<T>>>): T; | ||
/** | ||
* This method should NOT be used outside of @astesolution/zod-to-openapi code! | ||
* Any usage of it can lead to unexpected consequences when generating the | ||
* OpenApi schemas! | ||
* | ||
* @deprecated | ||
*/ | ||
internal_openapi<T extends ZodSchema<any>>(this: T, metadata: Partial<ZodOpenAPIInternalMetadata>): T; | ||
} | ||
} | ||
export declare function extendZodWithOpenApi(zod: typeof z): void; | ||
export {}; |
@@ -24,12 +24,37 @@ "use strict"; | ||
zod.ZodSchema.prototype.openapi = function (openapi) { | ||
var _a; | ||
const _b = openapi !== null && openapi !== void 0 ? openapi : {}, { param } = _b, restOfOpenApi = __rest(_b, ["param"]); | ||
const result = new this.constructor(Object.assign(Object.assign({}, this._def), { openapi: Object.assign(Object.assign(Object.assign({}, this._def.openapi), restOfOpenApi), { param: Object.assign(Object.assign({}, (_a = this._def.openapi) === null || _a === void 0 ? void 0 : _a.param), param) }) })); | ||
var _a, _b, _c, _d, _e, _f; | ||
const _g = openapi !== null && openapi !== void 0 ? openapi : {}, { param } = _g, restOfOpenApi = __rest(_g, ["param"]); | ||
const result = new this.constructor(Object.assign(Object.assign({}, this._def), { openapi: { | ||
_internal: (_a = this._def.openapi) === null || _a === void 0 ? void 0 : _a._internal, | ||
metadata: Object.assign(Object.assign(Object.assign({}, (_b = this._def.openapi) === null || _b === void 0 ? void 0 : _b.metadata), restOfOpenApi), { param: ((_d = (_c = this._def.openapi) === null || _c === void 0 ? void 0 : _c.metadata) === null || _d === void 0 ? void 0 : _d.param) || param | ||
? Object.assign(Object.assign({}, (_f = (_e = this._def.openapi) === null || _e === void 0 ? void 0 : _e.metadata) === null || _f === void 0 ? void 0 : _f.param), param) : undefined }), | ||
} })); | ||
/** | ||
* We want to preserve the behavior created by `internal_openapi` | ||
* for extended objects. Applying metadata does not change | ||
* the parent's `refId` to be used for `extendedFrom` | ||
*/ | ||
if ((0, zod_is_type_1.isZodType)(this, 'ZodObject')) { | ||
const initialExtend = this.extend; | ||
result.extend = this.extend; | ||
} | ||
return result; | ||
}; | ||
zod.ZodSchema.prototype.internal_openapi = function (openapi) { | ||
var _a, _b; | ||
const result = new this.constructor(Object.assign(Object.assign({}, this._def), { openapi: { | ||
_internal: Object.assign(Object.assign({}, (_a = this._def.openapi) === null || _a === void 0 ? void 0 : _a._internal), openapi), | ||
metadata: (_b = this._def.openapi) === null || _b === void 0 ? void 0 : _b.metadata, | ||
} })); | ||
if ((0, zod_is_type_1.isZodType)(this, 'ZodObject')) { | ||
const originalExtend = this.extend; | ||
result.extend = function (...args) { | ||
var _a; | ||
const extendedResult = initialExtend.apply(result, args); | ||
var _a, _b, _c, _d, _e; | ||
const extendedResult = originalExtend.apply(this, args); | ||
extendedResult._def.openapi = { | ||
extendedFrom: (_a = result._def.openapi) === null || _a === void 0 ? void 0 : _a.refId, | ||
_internal: { | ||
extendedFrom: ((_b = (_a = this._def.openapi) === null || _a === void 0 ? void 0 : _a._internal) === null || _b === void 0 ? void 0 : _b.refId) | ||
? { refId: (_d = (_c = this._def.openapi) === null || _c === void 0 ? void 0 : _c._internal) === null || _d === void 0 ? void 0 : _d.refId, schema: this } | ||
: (_e = this._def.openapi) === null || _e === void 0 ? void 0 : _e._internal.extendedFrom, | ||
}, | ||
metadata: {}, | ||
}; | ||
@@ -53,2 +78,8 @@ return extendedResult; | ||
}; | ||
const zodDefault = zod.ZodSchema.prototype.default; | ||
zod.ZodSchema.prototype.default = function (...args) { | ||
const result = zodDefault.apply(this, args); | ||
result._def.openapi = this._def.openapi; | ||
return result; | ||
}; | ||
const zodPick = zod.ZodObject.prototype.pick; | ||
@@ -55,0 +86,0 @@ zod.ZodObject.prototype.pick = function (...args) { |
{ | ||
"name": "@asteasolutions/zod-to-openapi", | ||
"version": "2.3.0", | ||
"version": "3.0.0", | ||
"description": "Builds OpenAPI schemas from Zod schemas", | ||
@@ -33,3 +33,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"openapi3-ts": "^2.0.2" | ||
"openapi3-ts": "^3.1.1" | ||
}, | ||
@@ -36,0 +36,0 @@ "peerDependencies": { |
# Zod to OpenAPI | ||
[![npm version](https://img.shields.io/npm/v/@asteasolutions/zod-to-openapi)](https://www.npmjs.com/package/@asteasolutions/zod-to-openapi) | ||
[![npm downloads](https://img.shields.io/npm/dm/@asteasolutions/zod-to-openapi)](https://www.npmjs.com/package/@asteasolutions/zod-to-openapi) | ||
A library that uses [zod schemas](https://github.com/colinhacks/zod) to generate an Open API Swagger documentation. | ||
@@ -10,7 +13,8 @@ | ||
3. [The Registry](#the-registry) | ||
4. [Defining schemas](#defining-schemas) | ||
5. [Defining routes](#defining-routes) | ||
6. [Defining custom components](#defining-custom-components) | ||
6. [A full example](#a-full-example) | ||
7. [Adding it as part of your build](#adding-it-as-part-of-your-build) | ||
4. [The Generator](#the-generator) | ||
5. [Defining schemas](#defining-schemas) | ||
6. [Defining routes & webhooks](#defining-routes--webhooks) | ||
7. [Defining custom components](#defining-custom-components) | ||
8. [A full example](#a-full-example) | ||
9. [Adding it as part of your build](#adding-it-as-part-of-your-build) | ||
3. [Zod schema types](#zod-schema-types) | ||
@@ -142,3 +146,3 @@ 1. [Supported types](#supported-types) | ||
const generator = new OpenAPIGenerator(registry.definitions); | ||
const generator = new OpenAPIGenerator(registry.definitions, '3.0.0'); | ||
@@ -148,4 +152,30 @@ return generator.generateComponents(); | ||
### The Generator | ||
The generator constructor takes 2 arguments. An array of definitions from the registry and an Open API version. | ||
The Open API version affects how some components are generated. For example: changing the version to `3.1.0` from `3.0.0` will result in following differences: | ||
```ts | ||
z.string().nullable().openapi(refId: 'name'); | ||
``` | ||
```yml | ||
# 3.1.0 | ||
# nullable is invalid in 3.1.0 but type arrays are invalid in previous versions | ||
name: | ||
type: | ||
- 'string' | ||
- 'null' | ||
# 3.0.0 | ||
name: | ||
type: 'string' | ||
nullable: true | ||
``` | ||
`generateComponents` will generate only the `/components` section of an OpenAPI document (e.g. only `schemas` and `parameters`), not generating actual routes. | ||
`generateDocument` will generate the whole OpenAPI document. | ||
### Defining schemas | ||
@@ -195,7 +225,7 @@ | ||
### Defining routes | ||
### Defining routes & webhooks | ||
#### Registering a path | ||
#### Registering a path or webhook | ||
An OpenAPI path is registered using the `registerPath` method of an `OpenAPIRegistry` instance. | ||
An OpenAPI path is registered using the `registerPath` method of an `OpenAPIRegistry` instance. An OpenAPI webhook is registered using the `registerWebhook` method and takes the same parameters as `registerPath`. | ||
@@ -324,3 +354,2 @@ ```ts | ||
return generator.generateDocument({ | ||
openapi: '3.0.0', | ||
info: { | ||
@@ -373,3 +402,5 @@ version: '1.0.0', | ||
- `ZodDefault` | ||
- `ZodEffects` - only for `.refine()` | ||
- `ZodNullable` | ||
- `ZodOptional` | ||
- `ZodEffects` - only for `.refine()`, `.preprocess()` | ||
- `ZodLiteral` | ||
@@ -380,2 +411,4 @@ - `ZodEnum` | ||
- `ZodArray` | ||
- `ZodDiscriminatedUnion` | ||
- including `discriminator` mapping when all Zod objects in the union are registered with `.register()` or contain a `refId`. | ||
- `ZodUnion` | ||
@@ -385,2 +418,3 @@ - `ZodIntersection` | ||
- `ZodUnknown` | ||
- `ZodDate` | ||
@@ -387,0 +421,0 @@ Extending an instance of `ZodObject` is also supported and results in an OpenApi definition with `allOf` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72567
21
1292
428
+ Addedopenapi3-ts@3.2.0(transitive)
+ Addedyaml@2.6.1(transitive)
- Removedopenapi3-ts@2.0.2(transitive)
- Removedyaml@1.10.2(transitive)
Updatedopenapi3-ts@^3.1.1