@asteasolutions/zod-to-openapi
Advanced tools
Comparing version 1.2.3 to 1.3.0
@@ -23,3 +23,3 @@ "use strict"; | ||
Object.entries(object).forEach(([key, value]) => { | ||
if (!keys.some((keyToOmit) => keyToOmit === key)) { | ||
if (!keys.some(keyToOmit => keyToOmit === key)) { | ||
result[key] = value; | ||
@@ -26,0 +26,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import type { z } from "zod"; | ||
import type { z } from 'zod'; | ||
declare type ZodTypes = { | ||
@@ -22,2 +22,3 @@ ZodArray: z.ZodArray<any>; | ||
ZodUnion: z.ZodUnion<any>; | ||
ZodDiscriminatedUnion: z.ZodDiscriminatedUnion<any, any, any>; | ||
ZodUnknown: z.ZodUnknown; | ||
@@ -24,0 +25,0 @@ ZodVoid: z.ZodVoid; |
@@ -16,5 +16,7 @@ import { OpenAPIObject, InfoObject, ServerObject, SecurityRequirementObject, TagObject, ExternalDocumentationObject, ComponentsObject } from 'openapi3-ts'; | ||
private pathRefs; | ||
private rawComponents; | ||
constructor(definitions: OpenAPIDefinitions[]); | ||
generateDocument(config: OpenAPIObjectConfig): OpenAPIObject; | ||
generateComponents(): ComponentsObject; | ||
private buildComponents; | ||
private sortDefinitions; | ||
@@ -36,2 +38,3 @@ private generateSingle; | ||
private getResponse; | ||
private descriptionFromResponseConfig; | ||
private toOpenAPISchema; | ||
@@ -38,0 +41,0 @@ private isOptionalSchema; |
@@ -24,20 +24,25 @@ "use strict"; | ||
this.pathRefs = {}; | ||
this.rawComponents = []; | ||
this.sortDefinitions(); | ||
} | ||
generateDocument(config) { | ||
this.definitions.forEach((definition) => this.generateSingle(definition)); | ||
return Object.assign(Object.assign({}, config), { components: { | ||
schemas: this.schemaRefs, | ||
parameters: this.paramRefs, | ||
}, paths: this.pathRefs }); | ||
this.definitions.forEach(definition => this.generateSingle(definition)); | ||
return Object.assign(Object.assign({}, config), { components: this.buildComponents(), paths: this.pathRefs }); | ||
} | ||
generateComponents() { | ||
this.definitions.forEach((definition) => this.generateSingle(definition)); | ||
this.definitions.forEach(definition => this.generateSingle(definition)); | ||
return { | ||
components: { | ||
schemas: this.schemaRefs, | ||
parameters: this.paramRefs, | ||
}, | ||
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() { | ||
@@ -50,4 +55,4 @@ const generationOrder = [ | ||
this.definitions.sort((left, right) => { | ||
const leftIndex = generationOrder.findIndex((type) => type === left.type); | ||
const rightIndex = generationOrder.findIndex((type) => type === right.type); | ||
const leftIndex = generationOrder.findIndex(type => type === left.type); | ||
const rightIndex = generationOrder.findIndex(type => type === right.type); | ||
return leftIndex - rightIndex; | ||
@@ -57,12 +62,16 @@ }); | ||
generateSingle(definition) { | ||
if (definition.type === 'parameter') { | ||
return this.generateParameterDefinition(definition.schema); | ||
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 'component': | ||
this.rawComponents.push(definition); | ||
return; | ||
} | ||
if (definition.type === 'schema') { | ||
return this.generateSchemaDefinition(definition.schema); | ||
} | ||
if (definition.type === 'route') { | ||
return this.generateSingleRoute(definition.route); | ||
} | ||
throw new errors_1.ZodToOpenAPIError('Invalid definition type'); | ||
} | ||
@@ -250,3 +259,3 @@ generateParameterDefinition(zodSchema) { | ||
: []; | ||
const headerParameters = (_b = (_a = request.headers) === null || _a === void 0 ? void 0 : _a.flatMap((header) => this.generateInlineParameters(header, 'header'))) !== null && _b !== void 0 ? _b : []; | ||
const headerParameters = (_b = (_a = request.headers) === null || _a === void 0 ? void 0 : _a.flatMap(header => this.generateInlineParameters(header, 'header'))) !== null && _b !== void 0 ? _b : []; | ||
return [...pathParameters, ...queryParameters, ...headerParameters]; | ||
@@ -256,3 +265,3 @@ } | ||
const { method, path, request, responses } = route, pathItemConfig = __rest(route, ["method", "path", "request", "responses"]); | ||
const generatedResponses = (0, lodash_1.mapValues)(responses, (response) => { | ||
const generatedResponses = (0, lodash_1.mapValues)(responses, response => { | ||
return this.getResponse(response); | ||
@@ -269,18 +278,11 @@ }); | ||
getResponse(response) { | ||
const description = this.descriptionFromResponseConfig(response); | ||
if ((0, zod_is_type_1.isZodType)(response, 'ZodVoid')) { | ||
const metadata = this.getMetadata(response); | ||
if (!(metadata === null || metadata === void 0 ? void 0 : metadata.description)) { | ||
throw new errors_1.MissingResponseDescriptionError(); | ||
} | ||
return { | ||
description: metadata.description, | ||
}; | ||
return { description }; | ||
} | ||
const metadata = this.getMetadata(response.schema); | ||
const responseSchema = this.generateInnerSchema(response.schema); | ||
if (!(metadata === null || metadata === void 0 ? void 0 : metadata.description)) { | ||
throw new errors_1.MissingResponseDescriptionError(); | ||
} | ||
return { | ||
description: metadata.description, | ||
description, | ||
headers: response.headers, | ||
links: response.links, | ||
content: { | ||
@@ -293,2 +295,19 @@ [response.mediaType]: { | ||
} | ||
descriptionFromResponseConfig(response) { | ||
if ((0, zod_is_type_1.isZodType)(response, 'ZodVoid')) { | ||
const metadata = this.getMetadata(response); | ||
if (!(metadata === null || metadata === void 0 ? void 0 : metadata.description)) { | ||
throw new errors_1.MissingResponseDescriptionError(); | ||
} | ||
return metadata.description; | ||
} | ||
if (response.description) { | ||
return response.description; | ||
} | ||
const metadata = this.getMetadata(response.schema); | ||
if (!(metadata === null || metadata === void 0 ? void 0 : metadata.description)) { | ||
throw new errors_1.MissingResponseDescriptionError(); | ||
} | ||
return metadata.description; | ||
} | ||
toOpenAPISchema(zodSchema, isNullable) { | ||
@@ -368,9 +387,15 @@ var _a, _b, _c, _d, _e; | ||
return { | ||
anyOf: options.map((schema) => this.generateInnerSchema(schema)), | ||
anyOf: options.map(schema => this.generateInnerSchema(schema)), | ||
}; | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodDiscriminatedUnion')) { | ||
const options = [...zodSchema.options.values()]; | ||
return { | ||
anyOf: options.map(schema => this.generateInnerSchema(schema)), | ||
}; | ||
} | ||
if ((0, zod_is_type_1.isZodType)(zodSchema, 'ZodIntersection')) { | ||
const subtypes = this.flattenIntersectionTypes(zodSchema); | ||
return { | ||
allOf: subtypes.map((schema) => this.generateInnerSchema(schema)), | ||
allOf: subtypes.map(schema => this.generateInnerSchema(schema)), | ||
}; | ||
@@ -411,3 +436,3 @@ } | ||
type: 'object', | ||
properties: (0, lodash_1.mapValues)(propTypes, (propSchema) => this.generateInnerSchema(propSchema)), | ||
properties: (0, lodash_1.mapValues)(propTypes, propSchema => this.generateInnerSchema(propSchema)), | ||
required: requiredProperties.length > 0 ? requiredProperties : undefined, | ||
@@ -423,3 +448,3 @@ additionalProperties: unknownKeysOption === 'passthrough' || undefined, | ||
const options = schema._def.options; | ||
return options.flatMap((option) => this.flattenUnionTypes(option)); | ||
return options.flatMap(option => this.flattenUnionTypes(option)); | ||
} | ||
@@ -426,0 +451,0 @@ flattenIntersectionTypes(schema) { |
@@ -1,2 +0,2 @@ | ||
import { OperationObject } from 'openapi3-ts'; | ||
import { CallbackObject, ComponentsObject, ExampleObject, HeaderObject, HeadersObject, ISpecificationExtension, LinkObject, LinksObject, OperationObject, ParameterObject, RequestBodyObject, ResponseObject, SchemaObject, SecuritySchemeObject } from 'openapi3-ts'; | ||
import type { ZodVoid, ZodObject, ZodSchema, ZodType } from 'zod'; | ||
@@ -7,2 +7,5 @@ declare type Method = 'get' | 'post' | 'put' | 'delete' | 'patch'; | ||
schema: ZodType<unknown>; | ||
description?: string; | ||
headers?: HeadersObject; | ||
links?: LinksObject; | ||
} | ZodVoid; | ||
@@ -22,3 +25,11 @@ export interface RouteConfig extends OperationObject { | ||
} | ||
export declare type OpenAPIComponentObject = SchemaObject | ResponseObject | ParameterObject | ExampleObject | RequestBodyObject | HeaderObject | SecuritySchemeObject | LinkObject | CallbackObject | ISpecificationExtension; | ||
export declare type ComponentTypeKey = Exclude<keyof ComponentsObject, number>; | ||
export declare type ComponentTypeOf<K extends ComponentTypeKey> = NonNullable<ComponentsObject[K]>[string]; | ||
export declare type OpenAPIDefinitions = { | ||
type: 'component'; | ||
componentType: ComponentTypeKey; | ||
name: string; | ||
component: OpenAPIComponentObject; | ||
} | { | ||
type: 'schema'; | ||
@@ -50,3 +61,17 @@ schema: ZodSchema<any>; | ||
registerPath(route: RouteConfig): void; | ||
/** | ||
* Registers a raw OpenAPI component. Use this if you have a simple object instead of a Zod schema. | ||
* | ||
* @param type The component type, e.g. `schemas`, `responses`, `securitySchemes`, etc. | ||
* @param name The name of the object, it is the key under the component | ||
* type in the resulting OpenAPI document | ||
* @param component The actual object to put there | ||
*/ | ||
registerComponent<K extends ComponentTypeKey>(type: K, name: string, component: ComponentTypeOf<K>): { | ||
name: string; | ||
ref: { | ||
$ref: string; | ||
}; | ||
}; | ||
} | ||
export {}; |
@@ -11,3 +11,3 @@ "use strict"; | ||
var _a, _b; | ||
const parentDefinitions = (_b = (_a = this.parents) === null || _a === void 0 ? void 0 : _a.flatMap((par) => par.definitions)) !== null && _b !== void 0 ? _b : []; | ||
const parentDefinitions = (_b = (_a = this.parents) === null || _a === void 0 ? void 0 : _a.flatMap(par => par.definitions)) !== null && _b !== void 0 ? _b : []; | ||
return [...parentDefinitions, ...this._definitions]; | ||
@@ -46,3 +46,23 @@ } | ||
} | ||
/** | ||
* Registers a raw OpenAPI component. Use this if you have a simple object instead of a Zod schema. | ||
* | ||
* @param type The component type, e.g. `schemas`, `responses`, `securitySchemes`, etc. | ||
* @param name The name of the object, it is the key under the component | ||
* type in the resulting OpenAPI document | ||
* @param component The actual object to put there | ||
*/ | ||
registerComponent(type, name, component) { | ||
this._definitions.push({ | ||
type: 'component', | ||
componentType: type, | ||
name, | ||
component, | ||
}); | ||
return { | ||
name, | ||
ref: { $ref: `#/components/${type}/${name}` }, | ||
}; | ||
} | ||
} | ||
exports.OpenAPIRegistry = OpenAPIRegistry; |
@@ -16,2 +16,8 @@ "use strict"; | ||
function extendZodWithOpenApi(zod) { | ||
if (typeof zod.ZodSchema.prototype.openapi !== 'undefined') { | ||
// This zod instance is already extended with the required methods, | ||
// doing it again will just result in multiple wrapper methods for | ||
// `optional` and `nullable` | ||
return; | ||
} | ||
zod.ZodSchema.prototype.openapi = function (openapi) { | ||
@@ -18,0 +24,0 @@ var _a; |
{ | ||
"name": "@asteasolutions/zod-to-openapi", | ||
"version": "1.2.3", | ||
"version": "1.3.0", | ||
"description": "Builds OpenAPI schemas from Zod schemas", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
@@ -27,2 +28,4 @@ "dist", | ||
"test": "jest", | ||
"prettier": "prettier --write .", | ||
"lint": "prettier --check .", | ||
"prepublishOnly": "npm run build" | ||
@@ -39,2 +42,3 @@ }, | ||
"jest": "^27.5.1", | ||
"prettier": "^2.7.1", | ||
"ts-jest": "^27.1.4", | ||
@@ -41,0 +45,0 @@ "typescript": "^4.6.3", |
@@ -12,2 +12,3 @@ # Zod to OpenAPI | ||
5. [Defining routes](#defining-routes) | ||
6. [Defining custom components](#defining-custom-components) | ||
6. [A full example](#a-full-example) | ||
@@ -321,2 +322,6 @@ 7. [Adding it as part of your build](#adding-it-as-part-of-your-build) | ||
### Defining custom components | ||
You can define components that are not OpenAPI schemas, including security schemes, response headers and others. See [this test file](spec/custom-components.spec.ts) for examples. | ||
### A full example | ||
@@ -323,0 +328,0 @@ |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
51478
910
381
7
1