Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@asteasolutions/zod-to-openapi

Package Overview
Dependencies
Maintainers
5
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@asteasolutions/zod-to-openapi - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

dist/errors.d.ts

1

dist/lib/lodash.d.ts

@@ -10,1 +10,2 @@ export declare function isUndefined<T>(value: any): value is undefined;

}>>(object: T, predicate: (val: T[keyof T]) => boolean): Result;
export declare function compact<T extends any>(arr: (T | null | undefined)[]): T[];
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.omitBy = exports.omit = exports.mapValues = exports.isNil = exports.isUndefined = void 0;
exports.compact = exports.omitBy = exports.omit = exports.mapValues = exports.isNil = exports.isUndefined = void 0;
function isUndefined(value) {

@@ -40,1 +40,5 @@ return value === undefined;

exports.omitBy = omitBy;
function compact(arr) {
return arr.filter((elem) => !isNil(elem));
}
exports.compact = compact;

@@ -22,2 +22,3 @@ import { OpenAPIObject, InfoObject, ServerObject, SecurityRequirementObject, TagObject, ExternalDocumentationObject, ComponentsObject } from 'openapi3-ts';

private generateParameterDefinition;
private getParameterRef;
private generateInlineParameters;

@@ -24,0 +25,0 @@ private generateParameter;

94

dist/openapi-generator.js

@@ -18,2 +18,3 @@ "use strict";

const zod_2 = require("zod");
const errors_1 = require("./errors");
class OpenAPIGenerator {

@@ -65,3 +66,3 @@ constructor(definitions) {

}
throw new Error('Invalid definition type');
throw new errors_1.ZodToOpenAPIError('Invalid definition type');
}

@@ -76,17 +77,42 @@ generateParameterDefinition(zodSchema) {

}
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]
: undefined;
if (!(schemaMetadata === null || schemaMetadata === void 0 ? void 0 : schemaMetadata.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/${schemaMetadata.refId}`,
};
}
generateInlineParameters(zodSchema, location) {
const metadata = this.getMetadata(zodSchema);
const parameterMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.param;
const existingRef = (metadata === null || metadata === void 0 ? void 0 : metadata.refId)
? this.paramRefs[metadata.refId]
: undefined;
if ((metadata === null || metadata === void 0 ? void 0 : metadata.refId) && existingRef) {
if (existingRef.in !== location) {
throw new Error(`The parameter ${existingRef.name} was created with \`in: ${existingRef.in}\` but was used as ${location} parameter`);
}
return [
{
$ref: `#/components/parameters/${metadata.refId}`,
},
];
const referencedSchema = this.getParameterRef(metadata, { in: location });
if (referencedSchema) {
return [referencedSchema];
}

@@ -96,11 +122,25 @@ if (zodSchema instanceof zod_1.ZodObject) {

const parameters = Object.entries(propTypes).map(([key, schema]) => {
var _a;
const innerMetadata = this.getMetadata(schema);
const referencedSchema = this.getParameterRef(innerMetadata, {
in: location,
name: key,
});
if (referencedSchema) {
return referencedSchema;
}
const innerParameterMetadata = innerMetadata === null || innerMetadata === void 0 ? void 0 : innerMetadata.param;
if ((innerParameterMetadata === null || innerParameterMetadata === void 0 ? void 0 : innerParameterMetadata.name) &&
innerParameterMetadata.name !== key) {
throw new Error(`Conflicting name - a parameter was created with the key "${key}" in ${location} but has a name "${innerParameterMetadata.name}" defined with \`.openapi()\`. Please use only one.`);
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 Error(`Conflicting location - the parameter "${innerParameterMetadata.name}" was created within "${location}" but has a in: "${innerParameterMetadata.in}" property defined with \`.openapi()\`. Please use only one.`);
throw new errors_1.ConflictError(`Conflicting location for parameter ${(_a = innerParameterMetadata.name) !== null && _a !== void 0 ? _a : key}`, {
key: 'in',
values: [location, innerParameterMetadata.in],
});
}

@@ -112,3 +152,6 @@ return this.generateParameter(schema.openapi({ param: { name: key, in: location } }));

if ((parameterMetadata === null || parameterMetadata === void 0 ? void 0 : parameterMetadata.in) && parameterMetadata.in !== location) {
throw new Error(`Conflicting location - the parameter "${parameterMetadata.name}" was created within "${location}" but has a in: "${parameterMetadata.in}" property defined with \`.openapi()\`. Please use only one.`);
throw new errors_1.ConflictError(`Conflicting location for parameter ${parameterMetadata.name}`, {
key: 'in',
values: [location, parameterMetadata.in],
});
}

@@ -125,7 +168,9 @@ return [

if (!paramName) {
throw new Error('Missing parameter name, please specify `name` and other OpenAPI props using `ZodSchema.openapi`');
throw new errors_1.MissingParameterDataError({ missingField: 'name' });
}
// TODO: Might add custom errors.
if (!paramLocation) {
throw new Error(`Missing parameter location for parameter ${paramName}, please specify \`in\` and other OpenAPI props using \`ZodSchema.openapi\``);
throw new errors_1.MissingParameterDataError({
missingField: 'in',
paramName,
});
}

@@ -223,3 +268,3 @@ const required = !zodSchema.isOptional() && !zodSchema.isNullable();

if (!(metadata === null || metadata === void 0 ? void 0 : metadata.description)) {
throw new Error('Missing response description. Please specify `description` and using `ZodSchema.openapi`.');
throw new errors_1.MissingResponseDescriptionError();
}

@@ -233,3 +278,3 @@ return {

if (!(metadata === null || metadata === void 0 ? void 0 : metadata.description)) {
throw new Error('Missing response description. Please specify `description` and using `ZodSchema.openapi`.');
throw new errors_1.MissingResponseDescriptionError();
}

@@ -323,5 +368,6 @@ return {

const refId = (_e = this.getMetadata(zodSchema)) === null || _e === void 0 ? void 0 : _e.refId;
const errorFor = refId ? ` for ${refId}` : '';
throw new Error(`Unknown zod object type${errorFor}, please specify \`type\` and other OpenAPI props using \`ZodSchema.openapi\`. The current schema is: ` +
JSON.stringify(zodSchema._def));
throw new errors_1.UnknownZodTypeError({
currentSchema: zodSchema._def,
schemaName: refId,
});
}

@@ -328,0 +374,0 @@ toOpenAPIObjectSchema(zodSchema, isNullable) {

{
"name": "@asteasolutions/zod-to-openapi",
"version": "1.0.0",
"version": "1.0.1",
"description": "Builds OpenAPI schemas from Zod schemas",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

# Zod to OpenAPI
A library that uses zod schemas to generate an Open API Swagger documentation.
A library that uses [zod schemas](https://github.com/colinhacks/zod) to generate an Open API Swagger documentation.
1. [Purpose](#purpose)
1. [Purpose and quick example](#purpose-and-quick-example)
2. [Usage](#usage)
1. [Installation](#installation)
2. [Expanding the zod functionalities](#expanding-the-zod-functionalities)
3. [Generating components](#generating-components)
4. [Registering schema definitions](#registering-schema-definitions)
5. [Registering parameter definitions](#registering-parameter-definitions)
6. [Generating a full OpenAPI document](#generating-a-full-openapi-document)
- [Registering a path](#registering-a-path)
2. [The `openapi` method](#the-openapi-method)
3. [The Registry](#the-registry)
4. [Defining schemas](#defining-schemas)
6. [Defining routes](#defining-routes)
7. [A full example](#a-full-example)

@@ -18,8 +16,80 @@ 8. [Adding it as part of your build](#adding-it-as-part-of-your-build)

<!-- TODO: Something about a CHANGELOG -->
We keep a changelog as part of the [GitHub releases](https://github.com/asteasolutions/zod-to-openapi/releases).
## Purpose
## Purpose and quick example
We at [Astea Solutions](https://asteasolutions.com/) made this library because of the duplication of work when creating a documentation for an API that uses `zod` to validate request input and output.
We at [Astea Solutions](https://asteasolutions.com/) made this library because we use [zod](https://github.com/colinhacks/zod) for validation in our APIs and are tired of the duplication to also support a separate OpenAPI definition that must be kept in sync. Using `zod-to-openapi`, we generate OpenAPI definitions directly from our zod schemas, this having single source of truth.
Simply put, it turns this:
```ts
const UserSchema = registry.register(
'User',
z.object({
id: z.string().openapi({ example: '1212121' }),
name: z.string().openapi({ example: 'John Doe' }),
age: z.number().openapi({ example: 42 }),
})
);
registry.registerPath({
method: 'get',
path: '/users/{id}',
summary: 'Get a single user',
request: {
params: z.object({ id: z.string() }),
},
responses: {
200: {
mediaType: 'application/json',
schema: UserSchema.openapi({
description: 'Object with user data',
}),
}
},
});
```
into this:
```yaml
components:
schemas:
User:
type: object
properties:
id:
type: string
example: '1212121'
name:
type: string
example: John Doe
age:
type: number
example: 42
required:
- id
- name
- age
/users/{id}:
get:
summary: Get a single user
parameters:
- in: path
name: id
schema:
type: string
required: true
responses:
'200':
description: Object with user data
content:
application/json:
schema:
$ref: '#/components/schemas/User'
```
and you can still use `UserSchema` and the `request.params` object to validate the input of your API.
## Usage

@@ -35,8 +105,7 @@

### Expanding the zod functionalities
### The `openapi` method
In order to specify some OpenAPI specific metadata you should use the exported `extendZodWithOpenApi`
function with your own instance of `zod`.
To keep openapi definitions natural, we add an `openapi` method to all Zod objects. For this to work, you need to call `extendZodWithOpenApi` once in your project.
Note: This should be done only once in a common-entrypoint file of your project (for example an `index.ts`/`app.ts`)
Note: This should be done only once in a common-entrypoint file of your project (for example an `index.ts`/`app.ts`). If you're using tree-shaking with Webpack, mark that file as having side-effects.

@@ -53,6 +122,5 @@ ```ts

### Generating components
### The Registry
The `OpenAPIRegistry` class is used as a utility for creating definitions that are then to be used to
generate the OpenAPI document using the `OpenAPIGenerator` class. In order to generate components the `generateComponents` method should be used.
The `OpenAPIRegistry` is used to track definitions which are later generated using the `OpenAPIGenerator` class.

@@ -74,4 +142,6 @@ ```ts

### Registering schema definitions
`generateComponents` will generate only the `/components` section of an OpenAPI document (e.g. only `schemas` and `parameters`), not generating actual routes.
### Defining schemas
An OpenAPI schema should be registered using the `register` method of an `OpenAPIRegistry` instance.

@@ -83,11 +153,5 @@

z.object({
id: z.string().openapi({
example: '1212121',
}),
name: z.string().openapi({
example: 'John Doe',
}),
age: z.number().openapi({
example: 42,
}),
id: z.string().openapi({ example: '1212121' }),
name: z.string().openapi({ example: 'John Doe' }),
age: z.number().openapi({ example: 42 }),
})

@@ -97,83 +161,36 @@ );

The YAML equivalent of the schema above would be:
If run now, `generateComponents` will generate the following structure:
```yaml
User:
type: object
properties:
id:
type: string
example: '1212121'
name:
type: string
example: John Doe
age:
type: number
example: 42
required:
- id
- name
- age
components:
schemas:
User:
type: object
properties:
id:
type: string
example: '1212121'
name:
type: string
example: John Doe
age:
type: number
example: 42
required:
- id
- name
- age
```
Note: All properties defined inside `.openapi` of a single zod schema are applied at their appropriate schema level.
The key for the schema in the output is the first argument passed to `.register` (in this case - `User`).
The result would be an object like `{ components: { schemas: { User: {...} } } }`. The key for the object is the value of the first argument passed to `.register` (in this case - `User`).
Note that `generateComponents` does not return YAML but a JS object - you can then serialize that object into YAML or JSON depending on your use-case.
The resulting schema can then be referenced by using `$ref: #/components/schemas/User` in an existing OpenAPI JSON.
The resulting schema can then be referenced by using `$ref: #/components/schemas/User` in an existing OpenAPI JSON. This will be done automatically for Routes defined through the registry.
### Registering parameter definitions
### Defining routes
An OpenAPI parameter (query/path/header) should be registered using the `registerParameter` method of an `OpenAPIRegistry` instance.
```ts
const UserIdSchema = registry.registerParameter(
'UserId',
z.string().openapi({
param: {
name: 'id',
in: 'path',
},
example: '1212121',
})
);
```
Note: Parameter properties are more specific to those of an OpenAPI schema. In order to define properties that apply to the parameter itself, use the `param` property of `.openapi`. Any properties provided outside of `param` would be applied to the schema for this parameter.
The YAML equivalent of the schema above would be:
```yaml
UserId:
in: path
name: id
schema:
type: string
example: '1212121'
required: true
```
The result would be an object like `{ components: { parameters: { UserId: {...} } } }`. The key for the object is the value of the first argument passed to `.registerParameter` (in this case - `UserId`).
The resulting schema can then be referenced by using `$ref: #/components/parameters/UserId` in an existing OpenAPI JSON.
### Generating a full OpenAPI document
A full OpenAPI document can be generated using the `generateDocument` method of an `OpenAPIGenerator` instance. It takes one argument - the document config. It may look something like this:
```ts
return generator.generateDocument({
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'My API',
description: 'This is the API',
},
servers: [{ url: 'v1' }],
});
```
#### Registering a path
An OpenAPI path should be registered using the `registerPath` method of an `OpenAPIRegistry` instance.
An OpenAPI path is registered using the `registerPath` method of an `OpenAPIRegistry` instance.

@@ -187,3 +204,5 @@ ```ts

request: {
params: z.object({ id: UserIdSchema }),
params: z.object({
id: z.string().openapi({ example: '1212121' })
}),
},

@@ -239,2 +258,68 @@ responses: {

#### Defining route parameters
If you don't want to inline all parameter definitions, you can define them separately with `registerParameter` and then reference them:
```ts
const UserIdParam = registry.registerParameter(
'UserId',
z.string().openapi({
param: {
name: 'id',
in: 'path',
},
example: '1212121',
})
);
registry.registerPath({
...
request: {
params: z.object({
id: UserIdParam
}),
},
responses: ...
});
```
The YAML equivalent would be:
```yaml
components:
parameters:
UserId:
in: path
name: id
schema:
type: string
example: '1212121'
required: true
'/users/{id}':
get:
...
parameters:
- $ref: '#/components/parameters/UserId'
responses: ...
```
Note: In order to define properties that apply to the parameter itself, use the `param` property of `.openapi`. Any properties provided outside of `param` would be applied to the schema for this parameter.
#### Generating the full document
A full OpenAPI document can be generated using the `generateDocument` method of an `OpenAPIGenerator` instance. It takes one argument - the document config. It may look something like this:
```ts
return generator.generateDocument({
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'My API',
description: 'This is the API',
},
servers: [{ url: 'v1' }],
});
```
### A full example

@@ -260,3 +345,3 @@

Then you can create a script that can execute the exported `generateOpenAPI` function. This script can be executed as a part of your build step so that it can write the result to some file like `openapi-docs.json`.
Then you can create a script that executes the exported `generateOpenAPI` function. This script can be executed as a part of your build step so that it can write the result to some file like `openapi-docs.json`.

@@ -267,2 +352,2 @@ ## Technologies

- [Zod 3.x](https://github.com/colinhacks/zod)
- [OpenAPI 3.x](https://github.com/metadevpro/openapi3-ts)
- [OpenAPI 3.x TS](https://github.com/metadevpro/openapi3-ts)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc