npm.io
0.2.7 • Published 3 years agoCLI

openapi-zod

Licence
MIT
Version
0.2.7
Deps
9
Size
272 kB
Vulns
9
Weekly
0

version(scoped) npm downloads (weekly)

All Contributors

️ openapi-zod

Convert static OpenAPI schemas to Zod schemas quickly using pure Node.js. Fast, lightweight, (almost) dependency-free, and no Java/node-gyp/running OpenAPI servers necessary.

This started as a fork of openapi-typescript

Dislcaimer

Use at your own risk! Any changes between this and the fork source openapi-typescript should be considered un-tested, undocumented.

Features

  • Supports YAML and JSON formats
  • Supports advanced OpenAPI 3.1 features like discriminators
  • Supports loading via remote URL (simple authentication supported with the --auth flag)
  • Supports remote references: $ref: "external.yaml#components/schemas/User"
  • Fetches remote schemas quickly using undici

Examples

See examples

Usage

Note:️ openapi-zod requires VALID OpenAPI 3.x schemas to work, and this library does not handle validation. There are several quality tools that handle this like @apidevtools/swagger-parser. Make sure to validate your schemas first!

Secondly, because of circular references within external $ref fields, the corresponding external namespace rarely works as intended. Hence we recommend using a tool like redocly cli (their bundle and join scripts) to merge all your api specs into one, and then using openapi-zod on the resulting file.

Also, since javascript has two types of optionals, i.e. null and undefined, and undefined gets converted to nulls for some serializers, you should ensure that anytime you are parsing a result, that you have deleted any nulls and undefined fields first. To do that, you can use the following helper type and function

export type RecursivelyReplaceNullWithUndefined<T> = T extends null
  ? undefined
  : T extends (infer U)[]
  ? RecursivelyReplaceNullWithUndefined<U>[]
  : T extends Record<string, unknown>
  ? { [K in keyof T]: RecursivelyReplaceNullWithUndefined<T[K]> }
  : T;

/**
 * Recursively deletes any fields that are null and undefined. That way when serializing the json, undefineds wont turn into nulls
 */
export const deleteNullsAndUndefined = <T>(obj: T): RecursivelyReplaceNullWithUndefined<T> => {
  if (obj == null) return undefined as any;

  if (Array.isArray(obj)) {
    return obj.map((o) => deleteNullsAndUndefined(0)) as any;
  }
  let newObj: any = {};
  Object.keys(obj).forEach((key) => {
    if ((obj as any)[key] === Object((obj as any)[key])) newObj[key] = deleteNullsAndUndefined((obj as any)[key]);
    else if ((obj as any)[key] != null) newObj[key] = (obj as any)[key];
  });
  return newObj;
};

CLI

Reading a local schema
npx openapi-zod schema.yaml --output schema.ts

# 🔭 Loading spec from schema.yaml…
# 🚀 schema.yaml -> schema.ts [250ms]
Globbing local schemas
npx openapi-zod "specs/**/*.yaml" --output schemas/

# 🔭 Loading spec from specs/one.yaml…
# 🔭 Loading spec from specs/two.yaml…
# 🔭 Loading spec from specs/three.yaml…
# 🚀 specs/one.yaml -> schemas/specs/one.ts [250ms]
# 🚀 specs/two.yaml -> schemas/specs/two.ts [250ms]
# 🚀 specs/three.yaml -> schemas/specs/three.ts [250ms]

Thanks, @sharmarajdaksh!

Reading remote schemas
npx openapi-zod https://petstore3.swagger.io/api/v3/openapi.yaml --output petstore.d.ts

# 🔭 Loading spec from https://petstore3.swagger.io/api/v3/openapi.yaml…
# 🚀 https://petstore3.swagger.io/api/v3/openapi.yaml -> petstore.d.ts [650ms]

Thanks, @psmyrdek!

Fetching data
Simple example

Any fetch call can be typed from the paths like so:

TODO

Options

The following flags can be appended to the CLI command.

Option Alias Default Description
--help Display inline help message and exit
--version Display this library’s version and exit
--output [location] -o (stdout) Where should the output file be saved?
--auth [token] Provide an auth token to be passed along in the request (only if accessing a private schema)
--header -x Provide an array of or singular headers as an alternative to a JSON object. Each header must follow the key: value pattern
--headers-object="{…}" -h Provide a JSON object as string of HTTP headers for remote schema request. This will take priority over --header
--http-method -m GET Provide the HTTP Verb/Method for fetching a schema from a remote URL
--additional-properties false Allow arbitrary properties for all schema objects without additionalProperties: false
--default-non-nullable false Treat schema objects with default values as non-nullable
--alphabetize false Sort types alphabetically
Node
npm i --save-dev openapi-zod
import fs from "node:fs";
import openapiZod from "openapi-zod";

// example 1: load [object] as schema (JSON only)
const schema = await fs.promises.readFile("spec.json", "utf8"); // must be OpenAPI JSON
const output = await openapiZod(JSON.parse(schema));

// example 2: load [string] as local file (YAML or JSON; released in v4.0)
const localPath = new URL("./spec.yaml", import.meta.url); // may be YAML or JSON format
const output = await openapiZod(localPath);

// example 3: load [string] as remote URL (YAML or JSON; released in v4.0)
const output = await openapiZod("https://myurl.com/v1/openapi.yaml");

The Node API may be useful if dealing with dynamically-created schemas, or you’re using within context of a larger application. Pass in either a JSON-friendly object to load a schema from memory, or a string to load a schema from a local file or remote URL (it will load the file quickly using built-in Node methods). Note that a YAML string isn’t supported in the Node.js API; either use the CLI or convert to JSON using js-yaml first.

Node options

The Node API supports all the CLI flags above in camelCase format, plus the following additional options:

Name Type Default Description
commentHeader string Override the default “This file was auto-generated …” file heading
inject string Inject arbitrary zod schemas into the start of the file
transform Function Override the default Schema Object ➝ zod transformer in certain scenarios
postTransform Function Same as transform but runs after the zod transformation
transform / postTransform

If using the Node.js API, you can override the normal Schema Object transformer with your own. This is useful for providing enhanced functionality for specific parts of your schema.

For example, say your schema has the following property:

properties:
  updated_at:
    type: string
    format: date-time

By default, openapiZod will generate updated_at?: string; because it’s not sure which format you want by "date-time" (formats are nonstandard and can be whatever you’d like). But we can enhance this by providing our own custom formatter, like so:

const types = openapiZod(mySchema, {
  transform(schemaObject, metadata): string {
    if ("format" in schemaObject && schemaObject.format === "date-time") {
      return "z.date()";
    }
  },
});

That would result in the following change:

-  updated_at: z.string(),
+  updated_at: z.date(),

Any Schema Object present in your schema will be run through this formatter (even remote ones!). Also be sure to check the metadata parameter for additional context that may be helpful.

There are many other uses for this besides checking format. Because this must return a string you can produce any arbitrary TypeScript code you’d like (even your own custom types).

Don’t forget about postTransform() as well! It works the same way, but runs after the zod transformation so you can extend/modify types as-needed.

Project Goals

  1. Support converting any valid OpenAPI schema to zod schemas, no matter how complicated.
  2. This library does NOT validate your schema, there are other libraries for that.
  3. The generated zod schemas must match your schema as closely as possible (e.g. no renaming to PascalCase)
  4. This library should never require Java, node-gyp, or some other complex environment to work. This should require Node.js and nothing else.
  5. This library will never require a running OpenAPI server to work.

Contributing

PRs are welcome! Please see our CONTRIBUTING.md guide.

Contributors (From the original openapi-typescript package)

Thanks goes to these wonderful people (emoji key):

Drew Powers
Drew Powers

Przemek Smyrdek
Przemek Smyrdek

Dan Enman
Dan Enman

Atle Frenvik Sveen
Atle Frenvik Sveen

Tim de Wolf
Tim de Wolf

Tom Barton
Tom Barton

Sven Nicolai Viig
Sven Nicolai Viig

Sorin Davidoi
Sorin Davidoi

Nathan Schneirov
Nathan Schneirov

Lucien Bénié
Lucien Bénié

Boris K
Boris K

Anton
Anton

Tim Shelburne
Tim Shelburne

Michał Miszczyszyn
Michał Miszczyszyn

Sam K Hall
Sam K Hall

Matt Jeanes
Matt Jeanes

Kristofer Giltvedt Selbekk
Kristofer Giltvedt Selbekk

Elliana May
Elliana May

Henrik Hall
Henrik Hall

Gregor Martynus
Gregor Martynus

Sam Mesterton-Gibbons
Sam Mesterton-Gibbons

Rendall
Rendall

Robert Massaioli
Robert Massaioli

Jan Kuča
Jan Kuča

Thomas Valadez
Thomas Valadez

Asitha de Silva
Asitha de Silva

Mikhail Yermolayev
Mikhail Yermolayev

Alex Batalov
Alex Batalov

Federico Bevione
Federico Bevione

Daisuke Yamamoto
Daisuke Yamamoto

dnalborczyk
dnalborczyk

FabioWanner
FabioWanner

Ash Smith
Ash Smith

Micah Halter
Micah Halter

Yuto Yoshihara
Yuto Yoshihara

Dakshraj Sharma
Dakshraj Sharma

Shaosu Liu
Shaosu Liu

Vytenis
Vytenis

Eric Zorn
Eric Zorn

Max Belsky
Max Belsky

Peter Bech
Peter Bech

Rusty Conover
Rusty Conover

Dave Carlson
Dave Carlson

ottomated
ottomated

Artem Shuvaev
Artem Shuvaev

ajaishankar
ajaishankar

Dominik Dosoudil
Dominik Dosoudil

tkr
tkr

berzi
berzi

Philip Trauner
Philip Trauner

Pavel Yermolin
Pavel Yermolin

Duncan Beevers
Duncan Beevers

Timofey Kukushkin
Timofey Kukushkin

Dmitry Semigradsky
Dmitry Semigradsky

Jeremy Liberman
Jeremy Liberman

This project follows the all-contributors specification. Contributions of any kind welcome!