Giter VIP home page Giter VIP logo

openapi-zod-client's Introduction

npm version

openapi-zod-client

Screenshot 2022-11-12 at 18 52 25

Generates a zodios (typescript http client with zod validation) from a (json/yaml) OpenAPI spec (or just use the generated schemas/endpoints/etc !)

  • can be used programmatically (do w/e you want with the computed schemas/endpoints)

  • or used as a CLI (generates a prettier .ts file with deduplicated variables when pointing to the same schema/$ref)

  • client typesafety and runtime validation using zodios

  • tested (using vitest) against official OpenAPI specs samples

Why this exists

Sometimes you don't have control on your API, maybe you need to consume APIs from other teams (who might each use a different language/framework), you only have their Open API spec as source of truth, then this might help ๐Ÿ˜‡

You could use openapi-zod-client to automate the API integration part (doesn't matter if you consume it in your front or back-end, zodios is agnostic) on your CI and just import the generated api client

Comparison vs tRPC zodios ts-rest etc

If you do have control on your API/back-end, you should probably use a RPC-like solution like tRPC, zodios or ts-rest instead of this.

Comparison vs typed-openapi

  • openapi-zod-client is a CLI that generates a zodios API client (typescript http client with zod validation), currently using axios as http client
  • typed-openapi is a CLI/library that generates a headless (bring your own fetcher : fetch, axios, ky, etc...) Typescript API client from an OpenAPI spec, that can output schemas as either just TS types (providing instant suggestions in your IDE) or different runtime validation schemas (zod, typebox, arktype, valibot, io-ts, yup)

Usage

with local install:

  • pnpm i -D openapi-zod-client
  • pnpm openapi-zod-client "./input/file.json" -o "./output/client.ts"

or directly (no install)

  • pnpx openapi-zod-client "./input/file.yaml" -o "./output/client.ts"

auto-generated doc

https://paka.dev/npm/openapi-zod-client

CLI

openapi-zod-client/1.15.0

Usage:
  $ openapi-zod-client <input>

Commands:
  <input>  path/url to OpenAPI/Swagger document as json/yaml

For more info, run any command with the `--help` flag:
  $ openapi-zod-client --help

Options:
  -o, --output <path>               Output path for the zodios api client ts file (defaults to `<input>.client.ts`)
  -t, --template <path>             Template path for the handlebars template that will be used to generate the output
  -p, --prettier <path>             Prettier config path that will be used to format the output client file
  -b, --base-url <url>              Base url for the api
  --no-with-alias                   With alias as api client methods (default: true)
  -a, --with-alias                  With alias as api client methods (default: true)
  --api-client-name <name>          when using the default `template.hbs`, allow customizing the `export const {apiClientName}`
  --error-expr <expr>               Pass an expression to determine if a response status is an error
  --success-expr <expr>             Pass an expression to determine which response status is the main success status
  --media-type-expr <expr>          Pass an expression to determine which response content should be allowed
  --export-schemas                  When true, will export all `#/components/schemas`
  --implicit-required               When true, will make all properties of an object required by default (rather than the current opposite), unless an explicitly `required` array is set
  --with-deprecated                 when true, will keep deprecated endpoints in the api output
  --with-description                when true, will add z.describe(xxx)
  --with-docs                       when true, will add jsdoc comments to generated types 
  --group-strategy                  groups endpoints by a given strategy, possible values are: 'none' | 'tag' | 'method' | 'tag-file' | 'method-file'
  --complexity-threshold            schema complexity threshold to determine which one (using less than `<` operator) should be assigned to a variable
  --default-status                  when defined as `auto-correct`, will automatically use `default` as fallback for `response` when no status code was declared
  --all-readonly                    when true, all generated objects and arrays will be readonly
  --export-types                    When true, will defined types for all object schemas in `#/components/schemas`
  --additional-props-default-value  Set default value when additionalProperties is not provided. Default to true. (default: true)
  --strict-objects                  Use strict validation for objects so we don't allow unknown keys. Defaults to false. (default: false)
  -v, --version                     Display version number
  -h, --help                        Display this message

Customization

You can pass a custom handlebars template and/or a custom prettier config with something like:

pnpm openapi-zod-client ./example/petstore.yaml -o ./example/petstore-schemas.ts -t ./example/schemas-only.hbs -p ./example/prettier-custom.json --export-schemas, there is an example of the output here

When using the CLI

You can pass an expression that will be safely evaluted (thanks to whence) and works like validateStatus from axios to determine which OpenAPI ResponseItem should be picked as the main one for the ZodiosEndpoint["response"] and which ones will be added to the ZodiosEndpoint["errors"] array.

Exemple: --success-expr "status >= 200 && status < 300"

Tips

  • You can omit the -o (output path) argument if you want and it will default to the input path with a .ts extension: pnpm openapi-zod-client ./input.yaml will generate a ./input.yaml.ts file

  • Since internally we're using swagger-parser, you should be able to use an URL as input like this: pnpx openapi-zod-client https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml -o ./petstore.ts

  • Also, multiple-files-documents ($ref pointing to another file) should work out-of-the-box as well, but if it doesn't, maybe dereferencing your document before passing it to openapi-zod-client could help

  • If you only need a few portions of your OpenAPI spec (i.e. only using a few endpoints from the GitHub REST API OpenAPI Spec), consider using openapi-endpoint-trimmer to trim unneeded paths from your spec first. It supports prefix-based omitting of paths, helping significantly cut down on the length of your output types file, which generally improves editor speed and compilation times.

Example

tl;dr

input:

openapi: "3.0.0"
info:
    version: 1.0.0
    title: Swagger Petstore
    license:
        name: MIT
servers:
    - url: http://petstore.swagger.io/v1
paths:
    /pets:
        get:
            summary: List all pets
            operationId: listPets
            tags:
                - pets
            parameters:
                - name: limit
                  in: query
                  description: How many items to return at one time (max 100)
                  required: false
                  schema:
                      type: integer
                      format: int32
            responses:
                "200":
                    description: A paged array of pets
                    headers:
                        x-next:
                            description: A link to the next page of responses
                            schema:
                                type: string
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Pets"
                default:
                    description: unexpected error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
        post:
            summary: Create a pet
            operationId: createPets
            tags:
                - pets
            responses:
                "201":
                    description: Null response
                default:
                    description: unexpected error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
    /pets/{petId}:
        get:
            summary: Info for a specific pet
            operationId: showPetById
            tags:
                - pets
            parameters:
                - name: petId
                  in: path
                  required: true
                  description: The id of the pet to retrieve
                  schema:
                      type: string
            responses:
                "200":
                    description: Expected response to a valid request
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Pet"
                default:
                    description: unexpected error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
components:
    schemas:
        Pet:
            type: object
            required:
                - id
                - name
            properties:
                id:
                    type: integer
                    format: int64
                name:
                    type: string
                tag:
                    type: string
        Pets:
            type: array
            items:
                $ref: "#/components/schemas/Pet"
        Error:
            type: object
            required:
                - code
                - message
            properties:
                code:
                    type: integer
                    format: int32
                message:
                    type: string

output:

import { makeApi, Zodios } from "@zodios/core";
import { z } from "zod";

const Pet = z.object({ id: z.number().int(), name: z.string(), tag: z.string().optional() });
const Pets = z.array(Pet);
const Error = z.object({ code: z.number().int(), message: z.string() });

export const schemas = {
    Pet,
    Pets,
    Error,
};

const endpoints = makeApi([
    {
        method: "get",
        path: "/pets",
        requestFormat: "json",
        parameters: [
            {
                name: "limit",
                type: "Query",
                schema: z.number().int().optional(),
            },
        ],
        response: z.array(Pet),
    },
    {
        method: "post",
        path: "/pets",
        requestFormat: "json",
        response: z.void(),
    },
    {
        method: "get",
        path: "/pets/:petId",
        requestFormat: "json",
        parameters: [
            {
                name: "petId",
                type: "Path",
                schema: z.string(),
            },
        ],
        response: Pet,
    },
]);

export const api = new Zodios(endpoints);

export function createApiClient(baseUrl: string) {
    return new Zodios(baseUrl, endpoints);
}

TODO

  • handle OA prefixItems -> output z.tuple
  • rm unused (=never referenced) variables from output

Caveats

NOT tested/expected to work with OpenAPI before v3, please migrate your specs to v3+ if you want to use this

You can do so by using the official Swagger Editor: https://editor.swagger.io/ using the Edit -> Convert to OpenAPI 3.0 menu

Contributing:

  • A .node-version file has been provided in the repository root, use your preferred Node.js manager which supports the standard to manage the development Node.js environment
  • The monorepo supports corepack, follow the linked instructions to locally install the development package manager (i.e. pnpm)
> pnpm install
> pnpm test

Assuming no issue were raised by the tests, you may use pnpm dev to watch for code changes during development.

If you fix an edge case please make a dedicated minimal reproduction test in the tests folder so that it doesn't break in future versions

Make sure to generate a changeset before submitting your PR.

openapi-zod-client's People

Contributors

andenacitelli avatar arthurgoupil avatar astahmer avatar c-ra-zy avatar codingmatty avatar craigmiller160 avatar dgadelha avatar dominik-parkopedia avatar dosco avatar dqbd avatar ecyrbe avatar ezze avatar fakegodd avatar feychenie avatar filipbekic01 avatar github-actions[bot] avatar iceydee avatar imballinst avatar jmiller14 avatar macropygia avatar mjperrone avatar nmcdaines avatar petermk85 avatar robert-wysocki-sparkbit avatar robotkutya avatar scarf005 avatar simonbinwang avatar tankers746 avatar tillschweneker avatar wickynilliams avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

openapi-zod-client's Issues

Add option to export `endpoints` by default.

Hi,

I would like to configure the axiosConfig, but I'm not able to do that without touching the generated file, which I don't want to. I want to keep it as source of truth to prevent future overrides.

This the solution that I came up with, but requires exporting the endpoints variable. Is there any better solution?

import { Zodios } from '@zodios/core';
import { ZodiosHooks } from '@zodios/react';
import { endpoints } from './generated'; // Not possible

const baseUrl = 'url';

const zodios = new Zodios(baseUrl, endpoints, {
  axiosConfig: {  ...  }
});

export const api = new ZodiosHooks('api', zodios);

zodios path parameters format

Nice work here.

When looking at the generated code from the documentation, i see this :

{
        method: "get",
        path: "/pets/{petId}",
        requestFormat: "json",
        response: variables["Pet"],
 },

But zodios will not be able to infer petId and ask for the parameter.

The correct generated format should be :

{
        method: "get",
        path: "/pets/:petId",
        requestFormat: "json",
        response: variables["Pet"],
 },

I did not check yet if this is just a typo in the docs.

Combination of `enum` and `minLength` leads to invalid zod schemas

While using enum and minLength / maxLength is nonsensical, it is still allowed by the JSON Schema spec, see also OAI/OpenAPI-Specification#2153 . With openapi-zod-client this combination leads to invalid code however, as z.Enum does not have a min attribute. This is illustrated by the folllowing spec

openapi: 3.0.3
info:
  title: Swagger Petstore - OpenAPI 3.0
  description: |-
    This is a sample Pet Store Server based on the OpenAPI 3.0 specification.  You can find out more about
    Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
    You can now help us improve the API whether it's by making changes to the definition itself or to the code.
    That way, with time, we can improve the API in general, and expose some of the new features in OAS3.

    _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_

    Some useful links:
    - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
    - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
  termsOfService: http://swagger.io/terms/
  contact:
    email: [email protected]
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.11
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
  - url: https://petstore3.swagger.io/api/v3
tags:
  - name: pet
    description: Everything about your Pets
    externalDocs:
      description: Find out more
      url: http://swagger.io
  - name: store
    description: Access to Petstore orders
    externalDocs:
      description: Find out more about our store
      url: http://swagger.io
  - name: user
    description: Operations about user
paths:
  /pet:
    put:
      tags:
        - pet
      summary: Update an existing pet
      description: Update an existing pet by Id
      operationId: updatePet
      requestBody:
        description: Update an existent pet in the store
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
          application/xml:
            schema:
              $ref: "#/components/schemas/Pet"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/Pet"
        required: true
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid ID supplied
        "404":
          description: Pet not found
        "405":
          description: Validation exception
      security:
        - petstore_auth:
            - write:pets
            - read:pets
    post:
      tags:
        - pet
      summary: Add a new pet to the store
      description: Add a new pet to the store
      operationId: addPet
      requestBody:
        description: Create a new pet in the store
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
          application/xml:
            schema:
              $ref: "#/components/schemas/Pet"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/Pet"
        required: true
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
        "405":
          description: Invalid input
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/findByStatus:
    get:
      tags:
        - pet
      summary: Finds Pets by status
      description: Multiple status values can be provided with comma separated strings
      operationId: findPetsByStatus
      parameters:
        - name: status
          in: query
          description: Status values that need to be considered for filter
          required: false
          explode: true
          schema:
            type: string
            default: available
            enum:
              - available
              - pending
              - sold
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid status value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/findByTags:
    get:
      tags:
        - pet
      summary: Finds Pets by tags
      description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
      operationId: findPetsByTags
      parameters:
        - name: tags
          in: query
          description: Tags to filter by
          required: false
          explode: true
          schema:
            type: array
            items:
              type: string
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid tag value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/{petId}:
    get:
      tags:
        - pet
      summary: Find pet by ID
      description: Returns a single pet
      operationId: getPetById
      parameters:
        - name: petId
          in: path
          description: ID of pet to return
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid ID supplied
        "404":
          description: Pet not found
      security:
        - api_key: []
        - petstore_auth:
            - write:pets
            - read:pets
    post:
      tags:
        - pet
      summary: Updates a pet in the store with form data
      description: ""
      operationId: updatePetWithForm
      parameters:
        - name: petId
          in: path
          description: ID of pet that needs to be updated
          required: true
          schema:
            type: integer
            format: int64
        - name: name
          in: query
          description: Name of pet that needs to be updated
          schema:
            type: string
        - name: status
          in: query
          description: Status of pet that needs to be updated
          schema:
            type: string
      responses:
        "405":
          description: Invalid input
      security:
        - petstore_auth:
            - write:pets
            - read:pets
    delete:
      tags:
        - pet
      summary: Deletes a pet
      description: delete a pet
      operationId: deletePet
      parameters:
        - name: api_key
          in: header
          description: ""
          required: false
          schema:
            type: string
        - name: petId
          in: path
          description: Pet id to delete
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "400":
          description: Invalid pet value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/{petId}/uploadImage:
    post:
      tags:
        - pet
      summary: uploads an image
      description: ""
      operationId: uploadFile
      parameters:
        - name: petId
          in: path
          description: ID of pet to update
          required: true
          schema:
            type: integer
            format: int64
        - name: additionalMetadata
          in: query
          description: Additional Metadata
          required: false
          schema:
            type: string
      requestBody:
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse"
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /store/inventory:
    get:
      tags:
        - store
      summary: Returns pet inventories by status
      description: Returns a map of status codes to quantities
      operationId: getInventory
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: integer
                  format: int32
      security:
        - api_key: []
  /store/order:
    post:
      tags:
        - store
      summary: Place an order for a pet
      description: Place a new order in the store
      operationId: placeOrder
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Order"
          application/xml:
            schema:
              $ref: "#/components/schemas/Order"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/Order"
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "405":
          description: Invalid input
  /store/order/{orderId}:
    get:
      tags:
        - store
      summary: Find purchase order by ID
      description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
      operationId: getOrderById
      parameters:
        - name: orderId
          in: path
          description: ID of order that needs to be fetched
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
            application/xml:
              schema:
                $ref: "#/components/schemas/Order"
        "400":
          description: Invalid ID supplied
        "404":
          description: Order not found
    delete:
      tags:
        - store
      summary: Delete purchase order by ID
      description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
      operationId: deleteOrder
      parameters:
        - name: orderId
          in: path
          description: ID of the order that needs to be deleted
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "400":
          description: Invalid ID supplied
        "404":
          description: Order not found
  /user:
    post:
      tags:
        - user
      summary: Create user
      description: This can only be done by the logged in user.
      operationId: createUser
      requestBody:
        description: Created user object
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/User"
          application/xml:
            schema:
              $ref: "#/components/schemas/User"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/User"
      responses:
        default:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
            application/xml:
              schema:
                $ref: "#/components/schemas/User"
  /user/createWithList:
    post:
      tags:
        - user
      summary: Creates list of users with given input array
      description: Creates list of users with given input array
      operationId: createUsersWithListInput
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/User"
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
            application/xml:
              schema:
                $ref: "#/components/schemas/User"
        default:
          description: successful operation
  /user/login:
    get:
      tags:
        - user
      summary: Logs user into the system
      description: ""
      operationId: loginUser
      parameters:
        - name: username
          in: query
          description: The user name for login
          required: false
          schema:
            type: string
        - name: password
          in: query
          description: The password for login in clear text
          required: false
          schema:
            type: string
      responses:
        "200":
          description: successful operation
          headers:
            X-Rate-Limit:
              description: calls per hour allowed by the user
              schema:
                type: integer
                format: int32
            X-Expires-After:
              description: date in UTC when token expires
              schema:
                type: string
                format: date-time
          content:
            application/xml:
              schema:
                type: string
            application/json:
              schema:
                type: string
        "400":
          description: Invalid username/password supplied
  /user/logout:
    get:
      tags:
        - user
      summary: Logs out current logged in user session
      description: ""
      operationId: logoutUser
      parameters: []
      responses:
        default:
          description: successful operation
  /user/{username}:
    get:
      tags:
        - user
      summary: Get user by user name
      description: ""
      operationId: getUserByName
      parameters:
        - name: username
          in: path
          description: "The name that needs to be fetched. Use user1 for testing. "
          required: true
          schema:
            type: string
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
            application/xml:
              schema:
                $ref: "#/components/schemas/User"
        "400":
          description: Invalid username supplied
        "404":
          description: User not found
    put:
      tags:
        - user
      summary: Update user
      description: This can only be done by the logged in user.
      operationId: updateUser
      parameters:
        - name: username
          in: path
          description: name that need to be deleted
          required: true
          schema:
            type: string
      requestBody:
        description: Update an existent user in the store
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/User"
          application/xml:
            schema:
              $ref: "#/components/schemas/User"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/User"
      responses:
        default:
          description: successful operation
    delete:
      tags:
        - user
      summary: Delete user
      description: This can only be done by the logged in user.
      operationId: deleteUser
      parameters:
        - name: username
          in: path
          description: The name that needs to be deleted
          required: true
          schema:
            type: string
      responses:
        "400":
          description: Invalid username supplied
        "404":
          description: User not found
components:
  schemas:
    Order:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        petId:
          type: integer
          format: int64
          example: 198772
        quantity:
          type: integer
          format: int32
          example: 7
        shipDate:
          type: string
          format: date-time
        status:
          type: string
          description: Order Status
          example: approved
          enum:
            - placed
            - approved
            - delivered
        complete:
          type: boolean
      xml:
        name: order
    Customer:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 100000
        username:
          type: string
          example: fehguy
        address:
          type: array
          xml:
            name: addresses
            wrapped: true
          items:
            $ref: "#/components/schemas/Address"
      xml:
        name: customer
    Address:
      type: object
      properties:
        street:
          type: string
          example: 437 Lytton
        city:
          type: string
          example: Palo Alto
        state:
          type: string
          example: CA
        zip:
          type: string
          example: "94301"
      xml:
        name: address
    Category:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 1
        name:
          type: string
          enum: ["Dogs", "Cats", "Mice"]
          minLength: 4
          example: Dogs
      xml:
        name: category
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        username:
          type: string
          example: theUser
        firstName:
          type: string
          example: John
        lastName:
          type: string
          example: James
        email:
          type: string
          example: [email protected]
        password:
          type: string
          example: "12345"
        phone:
          type: string
          example: "12345"
        userStatus:
          type: integer
          description: User Status
          format: int32
          example: 1
      xml:
        name: user
    Tag:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
      xml:
        name: tag
    Pet:
      required:
        - name
        - photoUrls
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        name:
          type: string
          example: doggie
        category:
          $ref: "#/components/schemas/Category"
        photoUrls:
          type: array
          xml:
            wrapped: true
          items:
            type: string
            xml:
              name: photoUrl
        tags:
          type: array
          xml:
            wrapped: true
          items:
            $ref: "#/components/schemas/Tag"
        status:
          type: string
          description: pet status in the store
          enum:
            - available
            - pending
            - sold
      xml:
        name: pet
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string
      xml:
        name: "##default"
  requestBodies:
    Pet:
      description: Pet object that needs to be added to the store
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Pet"
        application/xml:
          schema:
            $ref: "#/components/schemas/Pet"
    UserArray:
      description: List of user object
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: "#/components/schemas/User"
  securitySchemes:
    petstore_auth:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: https://petstore3.swagger.io/oauth/authorize
          scopes:
            write:pets: modify pets in your account
            read:pets: read your pets
    api_key:
      type: apiKey
      name: api_key
      in: header

which generates the invalid

const Category = z.object({ id: z.number().int(), name: z.enum(["Dogs", "Cats", "Mice"]).min(4) }).partial();

Maybe the best solution would be to catch combinations like these and just generate a z.enum when enum is used. At best the generator should throw an error when encountering situations like these.

Missing endpoints when parsing OpenAPI 3.0.3 schema

Hi, first of thanks for this really nifty library.

I've been using openapi-typescript to transform an OpenAPI 3.0.3 schema into Typescript types, and am working on migrating to using your library instead to generate a Zodios client. Unfortunately, when I do this most of the API endpoints in the OpenAPI schema are not added to Zodios. Same thing happens when I use the playground. I'm guessing there's a part of the OpenAPI schema that openapi-typescript supports but this library doesn't support yet?

Here are the files I'm working with, generated from the Sortly REST API. I appended .txt so Github would let me upload them.
https://sortlyapi.docs.apiary.io/

Thanks!

Invalid generated makeApi endpoints object, if path specific parameters are defined

With the release of v1.5.x a change has been added, which will create an invalid makeApi endpoints object.
The previous version (1.4.20) ignored the parameters object under a PathItem.

Within the OpenAPI Spec, a parameters entry can exist in a PathItem, which then has to be respected for each operation in that path.
OpenAPI spec

If an OpenAPI has multiple PathItem's, each with own path-specific parameters, the generated endpoints object also has multiple wrong method definitions in it.

This is a currently (v1.5.1) generated makeApi endpoint which contains (multiple) method: "parameters" objects:

const endpoints = makeApi([
    {
        method: "parameters",
        path: "/otherPets/:petId",
        requestFormat: "json",
        response: z.void(),
    },
    {
        method: "get",
        path: "/otherPets/:petId",
        requestFormat: "json",
        response: z.void(),
    },
    {
        method: "parameters",
        path: "/pet/:petId",
        requestFormat: "json",
        response: z.void(),
    },
    {
        method: "get",
        path: "/pet/:petId",
        requestFormat: "json",
        response: z.void(),
    },
    {
        method: "put",
        path: "/pet/:petId",
        description: `Update an existing pet by Id`,
        requestFormat: "json",
        parameters: [
            {
                name: "body",
                type: "Body",
                schema: Pet,
            },
        ],
        response: Pet,
    },
]);

I would like, that the path-specific parameters would be added to each operation.parameters object in that path.

const endpoints = makeApi([
    {
        method: "get",
        path: "/otherPets/:petId",
        requestFormat: "json",
        parameters: [
            {
                name: "petId", 
                type: "Path",
                schema: z.string().min(1).max(10).required()
            }
        ],
        response: Pet,
    },
    {
        method: "get",
        path: "/pet/:petId",
        requestFormat: "json",
        parameters: [
            {
                name: "petId", 
                type: "Path",
                schema: z.string().min(1).max(10).required()
            }, {
                name: "XCorrelationID",
                type: "Header",
                schema: z.string().min(36).max(256).required()
            }
        ],
        response: Pet,
    },
    {
        method: "put",
        path: "/pet/:petId",
        description: `Update an existing pet by Id`,
        requestFormat: "json",
        parameters: [
            {
                name: "petId", 
                type: "Path",
                schema: z.string().min(1).max(10)
            }, {
                name: "XCorrelationID",
                type: "Header",
                schema: z.string().min(36).max(256)
            },
            {
                name: "body",
                type: "Body",
                schema: Pet,
            },
        ],
        response: Pet,
    },
]);

Here is a reduced OpenAPI to reproduce the behaviour via https://openapi-zod-client.vercel.app

openapi: 3.0.3
info:
  title: Swagger Petstore - OpenAPI 3.0
  description: |-
    This is a sample Pet Store Server based on the OpenAPI 3.0 specification.  You can find out more about
    Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
    You can now help us improve the API whether it's by making changes to the definition itself or to the code.
    That way, with time, we can improve the API in general, and expose some of the new features in OAS3.

    _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_

    Some useful links:
    - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
    - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
  termsOfService: http://swagger.io/terms/
  contact:
    email: [email protected]
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.11
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
  - url: https://petstore3.swagger.io/api/v3
tags:
  - name: pet
    description: Everything about your Pets
    externalDocs:
      description: Find out more
      url: http://swagger.io
  - name: store
    description: Access to Petstore orders
    externalDocs:
      description: Find out more about our store
      url: http://swagger.io
  - name: user
    description: Operations about user
paths:
  /pet/:petId:
    parameters:
    - name: petId
      in: path
      description: |-
        Identifier of the pet
      required: true
      schema:
        maxLength: 10
        minLength: 1
        type: string
    - name: X-Correlation-ID
      in: header
      schema:
        minLength: 36
        maxLength: 256
        type: string
      required: true
    get:
      operationId: getPetById
      responses:
        200:
          application/json:
            schema:
                $ref: "#/components/schemas/Pet"
    put:
      operationId: updatePetById
      summary: Update an existing pet
      description: Update an existing pet by Id
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
  /otherPets/:petId:
    parameters:
    - name: petId
      in: path
      description: |-
        Identifier of the pet
      required: true
      schema:
        maxLength: 10
        minLength: 1
        type: string
    get:
      operationId: getOtherPetById
      responses:
        200:
          application/json:
            schema:
                $ref: "#/components/schemas/Pet"
components:
  schemas:
    Pet:
      required:
        - name
        - photoUrls
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        name:
          type: string
          example: doggie
        category:
          $ref: "#/components/schemas/Category"
        photoUrls:
          type: array
          xml:
            wrapped: true
          items:
            type: string
            xml:
              name: photoUrl
        tags:
          type: array
          xml:
            wrapped: true
          items:
            $ref: "#/components/schemas/Tag"
        status:
          type: string
          description: pet status in the store
          enum:
            - available
            - pending
            - sold
      xml:
        name: pet
    Category:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 1
        name:
          type: string
          example: Dogs
      xml:
        name: category
    Tag:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
      xml:
        name: tag

Error: Invalid ref: #/components/schemas/...

Hi,

just tried to used it on a existing service and got the error

Error: Invalid ref: #/components/schemas/NameResolutionType2

{
  ref: '#/components/schemas/NameResolutionType2',
  fallbackName: 'nameResolutionType',
  result: 'NameResolutionType2.optional().default("AssetName")'
}

but don't get it, what is wrong.

minimal reproduction example:

{
  "x-generator": "NSwag v13.17.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))",
  "openapi": "3.0.0",
  "info": {
    "title": "title",
    "version": "1.5.0.0"
  },
  "paths": {
    "/api/v2/name-resolution/via-id/{assetId}": {
      "get": {
        "tags": [
          "NameResolutionApi"
        ],
        "operationId": "api_v2_name-resolution_via-id_{assetid}_get_ResolveNamesForAssetV2",
        "parameters": [
          {
            "name": "nameResolutionType",
            "in": "query",
            "schema": {
              "default": "AssetName",
              "oneOf": [
                {
                  "$ref": "#/components/schemas/NameResolutionType2"
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "400": {
            "description": ""
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": ""
          },
          "500": {
            "description": ""
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "NameResolutionType2": {
        "type": "string",
        "description": "",
        "x-enumNames": [
          "AssetName",
          "FullName"
        ],
        "enum": [
          "AssetName",
          "FullName"
        ]
      }
    }
  }
}

basePath should be configurable via environment variable

tl;dr

As an operational guy
I would like to set the baseURL API parameter via environment variables
so that the developer does not have to build multiple API's for different server addresses.

Motivation

Within my company we are using lot's of different baseURL's for different environments (development, test, acceptance, reference & production).
All these environments have a different baseURL with the same OpenAPI spec.

And because I want only provide one container for all environments, I need a possibility to change the baseURL from "outside".

possible solutions

Currently I do have some possible solutions in my mind and would be glad to hear what you think about them.

  • integrate dotenv npm package
  • integrate config npm package
  • add new cli param to just export const endpoints & do not create a new Zodios(endpoints) instance in the generated file
  • leave it as it is and use the workaround described below

workaround

You can access the readonly api property of the generated Zodios instance and create an own Zodios instance where you can configure the baseURL.
But with this, we do have two Zodios Instances, which might confuse and let the developer import the "wrong" one.

src/generated/api.ts:

export const api = new Zodios(endpoints);

src/client/ApiClient.ts:

import { api } from '../generated/api';

const endpoints = api.api;
export const apiClient = new Zodios(process.env.API_BASE_PATH, endpoints);

[Question] json-schema-to-zod

Hello,

Sorry to create an issue for a question (you can activate discussions if you want).

So after taking a look at the code, i see you are implementing a parser yourself to convert to zodSchema

So my question is, did you take a look at json-schema-to-zod ?

  • if not, would you agree on a PR that migrates to using it (less maintainance) ?
  • if yes, what was the issue you encountered with it ? maybe it improved since last time you tried, or we could contribute to it ?

For example, i'm already using in @zodios/openapi the inverse library from the same author zod-to-json-schema, and he is doing a good job at maintaining thoses.

Road to V1

v1 should have :

(maybe) after v1 :

  • splitting 1 OpenAPI spec input into multiple files output (could be by tags ? by path ? by method ?)
    idea from this article

However, since the generated files are currently combined into one file, in
the case of a system configuration in which API requests are made directly from the front end,
there is a concern that the bundle size of each page that uses the API client will increase.

  • support swagger v2 by auto-converting it to v3 (if possible ?) before parsing it
  • provide a schemas template for exporting the schemas only: no zodios import/usage, just openapi -> zod (+ typescript if there are recursive schemas) -> related to this
  • internal implementation rewrite ? current state of the repo is a quite a mess, what started as a quick PoC somehow got a bit of attention (and also needed it at work) so I tried to deliver ASAP

[Question] Inline Enums?

Hi! I've just recently started to work with this library, and I'm loving it so far. I have a question about enums.

I have an endpoint with two GET params:

      - in: query
        name: type
        schema:
          type: string
          enum:
          - One
          - Two
          - Three
      - in: query
        name: o
        schema:
          type: array
          items:
            type: string
            enum:
            - (A - Z)
            - (Z - A)

The type param comes from a Django model with an enum that gets exported to the schema as well:

    TypeEnum:
      enum:
      - ONE
      - TWO
      - THREE
      type: string

As you can see, the enum in typed with the DB values, which are all caps, while the values the API will accept are capitalized.
I'm trying to set up a mostly code generated codebase, so I'd love to somehow get api.thatEndPoint.parameters.type.schema.options, but AFAIK this is not a trivial matter.

I've been somewhat successful .finding my way around api.api to get to the type.schema.options, but this is not typed, so my beautifully typed requests complain of [string, ...string[]] not being of type One | Two | Three, so this is a no go.

My question is: Is it possible to somehow get the inline enum schema defined in the makeAPi function while retaining typings? And if not, would it be possible to generate and export a zod type for inline enums in the schema?

Thanks!

oneOf creates invalid zod object.

When we create a zod client via openapi-zod-client spec/openapi.json -o src/validation.ts for the following openapi.json, it generates a wrong zod definition (const TestObject = z.union([z.unknown(), z.unknown()])).

Using oneOf in the openapi.json leads to that error.

Numerical enum support (and some others)

Hi,

In v1.4.5, numerical enums convert to a union of string literals.
However, Zod officially says that numerical enums should be a union of numeric literals. (ref: colinhacks/zod#338)
And an unnecessary .int() is added if the type is an integer.

openapi: 3.0.0
info:
  version: 1.0.0
  title: Numerical enums
paths:
  /sample:
    get:
      parameters:
        - in: query
          name: foo
          schema:
            type: integer
            enum:
              - 1
              - -2
              - 3
        - in: query
          name: bar
          schema:
            type: number
            enum:
              - 1.2
              - 34
              - -56.789
      responses:
        "200":
          description: resoponse
parameters: [
    {
        name: "foo",
        type: "Query",
        schema: z
            .union([z.literal("1"), z.literal("-2"), z.literal("3")])
            .int() // should remove
            .optional(),
    },
    {
        name: "bar",
        type: "Query",
        schema: z.union([z.literal("1.2"), z.literal("34"), z.literal("-56.789")]).optional(),
    },
],

I expected as following:

parameters: [
    {
        name: "foo",
        type: "Query",
        schema: z.union([z.literal(1), z.literal(-2), z.literal(3)]).optional(),
    },
    {
        name: "bar",
        type: "Query",
        schema: z.union([z.literal(1.2), z.literal(34), z.literal(-56.789)]).optional(),
    },
],

[Question] Why is response z.void() ?

When i run: pnpx openapi-zod-client 'https://petstore.swagger.io/v2/swagger.json' -o './src/schemas/test.ts'

It gives
{
method: 'get',
path: '/pet/:petId',
description: Returns a single pet,
requestFormat: 'json',
parameters: [
{
name: 'petId',
type: 'Path',
schema: z.unknown(),
},
],
response: z.void(),
errors: [
{
status: 400,
description: Invalid ID supplied,
schema: z.void(),
},
{
status: 404,
description: Pet not found,
schema: z.void(),
},
],
},

Why is response: z.void() ?

Combination of `enum` and `type` list leads to invalid literal union

Follow up on ##60 When enums and type lists are combined they currenty generate a union of invalid literals. Consider the following spec:

openapi: 3.0.3
info:
  title: Swagger Petstore - OpenAPI 3.0
  description: |-
    This is a sample Pet Store Server based on the OpenAPI 3.0 specification.  You can find out more about
    Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
    You can now help us improve the API whether it's by making changes to the definition itself or to the code.
    That way, with time, we can improve the API in general, and expose some of the new features in OAS3.

    _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_

    Some useful links:
    - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
    - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
  termsOfService: http://swagger.io/terms/
  contact:
    email: [email protected]
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.11
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
  - url: https://petstore3.swagger.io/api/v3
tags:
  - name: pet
    description: Everything about your Pets
    externalDocs:
      description: Find out more
      url: http://swagger.io
  - name: store
    description: Access to Petstore orders
    externalDocs:
      description: Find out more about our store
      url: http://swagger.io
  - name: user
    description: Operations about user
paths:
  /pet:
    put:
      tags:
        - pet
      summary: Update an existing pet
      description: Update an existing pet by Id
      operationId: updatePet
      requestBody:
        description: Update an existent pet in the store
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
          application/xml:
            schema:
              $ref: "#/components/schemas/Pet"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/Pet"
        required: true
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid ID supplied
        "404":
          description: Pet not found
        "405":
          description: Validation exception
      security:
        - petstore_auth:
            - write:pets
            - read:pets
    post:
      tags:
        - pet
      summary: Add a new pet to the store
      description: Add a new pet to the store
      operationId: addPet
      requestBody:
        description: Create a new pet in the store
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
          application/xml:
            schema:
              $ref: "#/components/schemas/Pet"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/Pet"
        required: true
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
        "405":
          description: Invalid input
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/findByStatus:
    get:
      tags:
        - pet
      summary: Finds Pets by status
      description: Multiple status values can be provided with comma separated strings
      operationId: findPetsByStatus
      parameters:
        - name: status
          in: query
          description: Status values that need to be considered for filter
          required: false
          explode: true
          schema:
            type: string
            default: available
            enum:
              - available
              - pending
              - sold
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid status value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/findByTags:
    get:
      tags:
        - pet
      summary: Finds Pets by tags
      description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
      operationId: findPetsByTags
      parameters:
        - name: tags
          in: query
          description: Tags to filter by
          required: false
          explode: true
          schema:
            type: array
            items:
              type: string
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid tag value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/{petId}:
    get:
      tags:
        - pet
      summary: Find pet by ID
      description: Returns a single pet
      operationId: getPetById
      parameters:
        - name: petId
          in: path
          description: ID of pet to return
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
        "400":
          description: Invalid ID supplied
        "404":
          description: Pet not found
      security:
        - api_key: []
        - petstore_auth:
            - write:pets
            - read:pets
    post:
      tags:
        - pet
      summary: Updates a pet in the store with form data
      description: ""
      operationId: updatePetWithForm
      parameters:
        - name: petId
          in: path
          description: ID of pet that needs to be updated
          required: true
          schema:
            type: integer
            format: int64
        - name: name
          in: query
          description: Name of pet that needs to be updated
          schema:
            type: string
        - name: status
          in: query
          description: Status of pet that needs to be updated
          schema:
            type: string
      responses:
        "405":
          description: Invalid input
      security:
        - petstore_auth:
            - write:pets
            - read:pets
    delete:
      tags:
        - pet
      summary: Deletes a pet
      description: delete a pet
      operationId: deletePet
      parameters:
        - name: api_key
          in: header
          description: ""
          required: false
          schema:
            type: string
        - name: petId
          in: path
          description: Pet id to delete
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "400":
          description: Invalid pet value
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /pet/{petId}/uploadImage:
    post:
      tags:
        - pet
      summary: uploads an image
      description: ""
      operationId: uploadFile
      parameters:
        - name: petId
          in: path
          description: ID of pet to update
          required: true
          schema:
            type: integer
            format: int64
        - name: additionalMetadata
          in: query
          description: Additional Metadata
          required: false
          schema:
            type: string
      requestBody:
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse"
      security:
        - petstore_auth:
            - write:pets
            - read:pets
  /store/inventory:
    get:
      tags:
        - store
      summary: Returns pet inventories by status
      description: Returns a map of status codes to quantities
      operationId: getInventory
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: integer
                  format: int32
      security:
        - api_key: []
  /store/order:
    post:
      tags:
        - store
      summary: Place an order for a pet
      description: Place a new order in the store
      operationId: placeOrder
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Order"
          application/xml:
            schema:
              $ref: "#/components/schemas/Order"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/Order"
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "405":
          description: Invalid input
  /store/order/{orderId}:
    get:
      tags:
        - store
      summary: Find purchase order by ID
      description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
      operationId: getOrderById
      parameters:
        - name: orderId
          in: path
          description: ID of order that needs to be fetched
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
            application/xml:
              schema:
                $ref: "#/components/schemas/Order"
        "400":
          description: Invalid ID supplied
        "404":
          description: Order not found
    delete:
      tags:
        - store
      summary: Delete purchase order by ID
      description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
      operationId: deleteOrder
      parameters:
        - name: orderId
          in: path
          description: ID of the order that needs to be deleted
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "400":
          description: Invalid ID supplied
        "404":
          description: Order not found
  /user:
    post:
      tags:
        - user
      summary: Create user
      description: This can only be done by the logged in user.
      operationId: createUser
      requestBody:
        description: Created user object
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/User"
          application/xml:
            schema:
              $ref: "#/components/schemas/User"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/User"
      responses:
        default:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
            application/xml:
              schema:
                $ref: "#/components/schemas/User"
  /user/createWithList:
    post:
      tags:
        - user
      summary: Creates list of users with given input array
      description: Creates list of users with given input array
      operationId: createUsersWithListInput
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/User"
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
            application/xml:
              schema:
                $ref: "#/components/schemas/User"
        default:
          description: successful operation
  /user/login:
    get:
      tags:
        - user
      summary: Logs user into the system
      description: ""
      operationId: loginUser
      parameters:
        - name: username
          in: query
          description: The user name for login
          required: false
          schema:
            type: string
        - name: password
          in: query
          description: The password for login in clear text
          required: false
          schema:
            type: string
      responses:
        "200":
          description: successful operation
          headers:
            X-Rate-Limit:
              description: calls per hour allowed by the user
              schema:
                type: integer
                format: int32
            X-Expires-After:
              description: date in UTC when token expires
              schema:
                type: string
                format: date-time
          content:
            application/xml:
              schema:
                type: string
            application/json:
              schema:
                type: string
        "400":
          description: Invalid username/password supplied
  /user/logout:
    get:
      tags:
        - user
      summary: Logs out current logged in user session
      description: ""
      operationId: logoutUser
      parameters: []
      responses:
        default:
          description: successful operation
  /user/{username}:
    get:
      tags:
        - user
      summary: Get user by user name
      description: ""
      operationId: getUserByName
      parameters:
        - name: username
          in: path
          description: "The name that needs to be fetched. Use user1 for testing. "
          required: true
          schema:
            type: string
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
            application/xml:
              schema:
                $ref: "#/components/schemas/User"
        "400":
          description: Invalid username supplied
        "404":
          description: User not found
    put:
      tags:
        - user
      summary: Update user
      description: This can only be done by the logged in user.
      operationId: updateUser
      parameters:
        - name: username
          in: path
          description: name that need to be deleted
          required: true
          schema:
            type: string
      requestBody:
        description: Update an existent user in the store
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/User"
          application/xml:
            schema:
              $ref: "#/components/schemas/User"
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/User"
      responses:
        default:
          description: successful operation
    delete:
      tags:
        - user
      summary: Delete user
      description: This can only be done by the logged in user.
      operationId: deleteUser
      parameters:
        - name: username
          in: path
          description: The name that needs to be deleted
          required: true
          schema:
            type: string
      responses:
        "400":
          description: Invalid username supplied
        "404":
          description: User not found
components:
  schemas:
    Order:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        petId:
          type: integer
          format: int64
          example: 198772
        quantity:
          type: integer
          format: int32
          example: 7
        shipDate:
          type: string
          format: date-time
        status:
          type: string
          description: Order Status
          example: approved
          enum:
            - placed
            - approved
            - delivered
        complete:
          type: boolean
      xml:
        name: order
    Customer:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 100000
        username:
          type: string
          example: fehguy
        address:
          type: array
          xml:
            name: addresses
            wrapped: true
          items:
            $ref: "#/components/schemas/Address"
      xml:
        name: customer
    Address:
      type: object
      properties:
        street:
          type: string
          example: 437 Lytton
        city:
          type: string
          example: Palo Alto
        state:
          type: string
          example: CA
        zip:
          type: string
          example: "94301"
      xml:
        name: address
    Category:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 1
        name:
          type: ["string", "null"]
          enum: ["Dogs", "Cats", "Mice"]
          example: Dogs
      xml:
        name: category
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        username:
          type: string
          example: theUser
        firstName:
          type: string
          example: John
        lastName:
          type: string
          example: James
        email:
          type: string
          example: [email protected]
        password:
          type: string
          example: "12345"
        phone:
          type: string
          example: "12345"
        userStatus:
          type: integer
          description: User Status
          format: int32
          example: 1
      xml:
        name: user
    Tag:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
      xml:
        name: tag
    Pet:
      required:
        - name
        - photoUrls
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        name:
          type: string
          example: doggie
        category:
          $ref: "#/components/schemas/Category"
        photoUrls:
          type: array
          xml:
            wrapped: true
          items:
            type: string
            xml:
              name: photoUrl
        tags:
          type: array
          xml:
            wrapped: true
          items:
            $ref: "#/components/schemas/Tag"
        status:
          type: string
          description: pet status in the store
          enum:
            - available
            - pending
            - sold
      xml:
        name: pet
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string
      xml:
        name: "##default"
  requestBodies:
    Pet:
      description: Pet object that needs to be added to the store
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Pet"
        application/xml:
          schema:
            $ref: "#/components/schemas/Pet"
    UserArray:
      description: List of user object
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: "#/components/schemas/User"
  securitySchemes:
    petstore_auth:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: https://petstore3.swagger.io/oauth/authorize
          scopes:
            write:pets: modify pets in your account
            read:pets: read your pets
    api_key:
      type: apiKey
      name: api_key
      in: header

which generates

const Category = z
    .object({
        id: z.number().int(),
        name: z.union([z.enum(["Dogs", "Cats", "Mice"]), z.union([z.literal(Dogs), z.literal(Cats), z.literal(Mice)])]),
    })
    .partial();

Instead it should generate

const Category = z
    .object({
        id: z.number().int(),
        name: z.union([z.enum(["Dogs", "Cats", "Mice"]), z.null()]),
    })
    .partial();

In retrospect, I should have tested my spec with new version before publishing. Sorry for the double work.

TODO - include errors even if content object is empty (auto-infer as z.void())

this doesn't include errors 400/401/500 cause no MediaTypeObject is set in the ContentObject
this should be included nonetheless as if there was one matching mediaType with a schema: z.void()

paths:
  /v1/stores/{store_id}:
    get:
      tags:
        - store_warehouse
      summary: Get the warehouse linked to a store.
      parameters:
        - name: store_id
          in: path
          description: Please fill the store id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/d"
        "400":
          description: Ressource Not Found
          content: {}
        "401":
          description: Not Authorized
          content: {}
        "500":
          description: Methods Not Implemented
          content: {}

TODO - validateStatus fn to determine which path[xxx].responses[yyy] should be the main success

the problem:

after trying lots of OpenAPI specs, I noticed that sometimes there would be no ResponseObject defined with "200" or "default" (in the responses map of an OperationObject), but rather return a 201, 202 (or basically anything else than 200/default)

when this happens, openapi-zod-client would therefore not output anything for that endpoint

example / reproduction

using the petstore example as base, here is a minimal reproduction

input:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    post:
      summary: Create a pet
      operationId: createPets
      tags:
        - pets
      responses:
        "201":
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string

output:

import { Zodios } from "@zodios/core";
import { z } from "zod";

const v7LgRCMpuZ0 = z.object({ id: z.bigint(), name: z.string(), tag: z.string().optional() }).optional();

const variables = {
    createPets: v7LgRCMpuZ0,
};

const endpoints = [] as const;

export const api = new Zodios(endpoints);

whereas if we just replace "201" with "200", the output becomes:

import { Zodios } from "@zodios/core";
import { z } from "zod";

const v7LgRCMpuZ0 = z.object({ id: z.bigint(), name: z.string(), tag: z.string().optional() }).optional();

const variables = {
    Pet: v7LgRCMpuZ0,
    createPets: v7LgRCMpuZ0,
};

const endpoints = [
    {
        method: "post",
        path: "/pets",
        requestFormat: "json",
        response: variables["Pet"],
    },
] as const;

export const api = new Zodios(endpoints);

solution

basically do it just like axios

default behavior: if "200" or "default" are not listed in the ResponsesObject map, then try each status status >= 200 && status < 300 ?
controlled behaviour: let the user determine that by passing a validateStatus (still just like axios) if needed in the TemplateContext.options

Transform alias to camelCase

Some API specs provide operationIds in something else than camel case, which can lead to eslint warnings for the generated aliases. Would it make sense to provide an option to directly camelCase the operationId either here or via a handlebars template? I would find this change less intrusive than #46 as the operationId is not directly part of the API.

TODO - handle OpenAPI `format`

zod implementation: https://github.com/colinhacks/zod#dates

To write a schema that accepts either a Date or a date string, use z.preprocess.

const dateSchema = z.preprocess((arg) => {
  if (typeof arg == "string" || arg instanceof Date) return new Date(arg);
}, z.date());
type DateSchema = z.infer<typeof dateSchema>;
// type DateSchema = Date

dateSchema.safeParse(new Date("1/12/22")); // success: true
dateSchema.safeParse("2022-01-12T00:00:00.000Z"); // success: true

open api spec description: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types

OpenAPI 3.1 support

Hi, does this package officially support OpenAPI 3.1 specs?
I haven't seen any info on this.

Thanks in advance!

[Question] Whitelist / Blacklist endpoints?

Hi! I was wondering if there was a way to whitelist / blacklist some endpoints / endpoint aliases using regular expressions, and if it made sense in this project. My use case would be the following:

Say you have to build a small webpage that connects to a really big, old, monolithic API with hundreds of endpoints, but you only had to consume a small subsection of those, say, auth* and analytics. If we had the ability to whitelist and/blacklist, we could run:

yarn run openapi-zod-client --w "^\/api\/v1\/auth.*$" --w "^\/api\/v1\/analytics.*$"
yarn run openapi-zod-client --wa "^apiV1Auth.*$" --w "^apiV1Analytics.*$"

(Just a proposal of syntax, regex expresiones may not work)

To generate a 1000 lines of code instead of 50000 lines, thus making the typescript language server run way faster and reducing out bundle size

regex on enum?

I have a strange issue.
When I try to generate zod client from an openapi v3 json file the enums does not seem to work well. It adds a regex to them, but z.enum does not seem to have a 'regex' method.

Property 'regex' does not exist on type 'ZodEnum<["CSV", "PDF"]>'.

image

Unable to run with yarn

Hi, am I doing anything wrong here?

$ yarn openapi-zod-client plugin-redoc-0.yaml

yarn run v1.22.19

Retrieving OpenAPI document from plugin-redoc-0.yaml
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Error: ENOENT: no such file or directory, open './src/template.hbs'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: './src/template.hbs'
}

Node.js v18.0.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

TODO - Better readme

this was done very quickly when the project was at the proof of concept stage, it hasn't been updated much since and it could benefit from a bit of a presentation effort

TODO - handle missing operationId for requestBody var name

this

// ...
"/media-objects/{id}":
    put:
      tags:
        - MediaObjects
      parameters:
        - $ref: "#/components/parameters/id"
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FileUpload"
       responses:
        "200":
          description: "Ok"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaObject"
// ...

currently outputs

{
        method: "put",
        path: "/media-objects/:id",
        requestFormat: "json",
        parameters: [
            {
                name: "body",
                type: "Body",
                schema: variables["undefined_Body"],
            },
        ],
        response: variables["MediaObject"],
    }

maybe fallback to :

  • method + the $ref schema name, with this example putFileUpload_Body ? ?
  • method + path as PascalCase, with this example putMediaObjectsId_Body ?

Potential wrong generated type

I have an optional and nullable enum property, openapi-zod-client infer it as ENUM.optional() instead of ENUM.nullish(). is this expected behavior?

image

## Partial Definition ```json { "openapi": "3.0.0", "paths": { "/v1/auth/me": { "get": { "operationId": "me", "parameters": [], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserModel" } } } } }, "tags": ["auth"] } } }, "info": {}, "tags": [], "servers": [], "components": { "securitySchemes": { "bearer": { "scheme": "bearer", "bearerFormat": "JWT", "type": "http" } }, "schemas": { "UserType": { "type": "string", "enum": ["employee", "customer"] }, "UserRole": { "type": "string", "enum": ["admin", "agent"] }, "UserStatus": { "type": "string", "enum": ["invited", "enabled", "disabled", "declined"] }, "Currency": { "type": "string", "enum": ["USD"] }, "UserModel": { "type": "object", "properties": { "id": { "type": "string" }, "createdAt": { "format": "date-time", "type": "string" }, "updatedAt": { "format": "date-time", "type": "string" }, "organizationId": { "type": "object", "nullable": true }, "name": { "type": "string" }, "type": { "$ref": "#/components/schemas/UserType" }, "role": { "$ref": "#/components/schemas/UserRole" }, "status": { "$ref": "#/components/schemas/UserStatus" }, "avatar": { "type": "string", "nullable": true }, "handle": { "type": "string" }, "phone": { "type": "string", "nullable": true }, "phoneVerifiedAt": { "format": "date-time", "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true }, "emailVerifiedAt": { "format": "date-time", "type": "string", "nullable": true }, "password": { "type": "string", "nullable": true }, "country": { "type": "string", "nullable": true }, "currency": { "nullable": true, "$ref": "#/components/schemas/Currency" } }, "required": ["id", "createdAt", "updatedAt", "name", "type", "role", "status", "handle"] } } } } ```

(maybe) TODO ? provide an option flag to make all properties required by default (rather than the current opposite)

problem:

currently if a property (in type: object, properties: { xxx }) isnt explicitly specified in the required, this property will be inferred as optional. this is actually good and the right way / the openapi way of doing things.

sadly, it seems, from my experience at least, that a LOT of APIs openapi spec just forget about this part, so we end up having zod schemas followed by .partial() in a lot of places where it's actually not the API's intended behavior.

yes in a perfect world, those APIs definitely SHOULD make proper use of the required array and explicitly set every needed property.

solution:

provide an option implicitRequired for generateZodClientFromOpenAPI, in TemplateContext["options"], that would infer every properties as required UNLESS a required array is actually set

example:

components:
  schemas:
    ExplicitRequired: 
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        tag:
          type: string
      required:
        - id
        - name
    NoRequiredSet: 
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        tag:
          type: string

will output something like (using the implicitRequired option):

const ExplicitRequired = z.object({ id: z.string(), name: z.string(), tag: z.string().optional() });
const NoRequiredSet = z.object({ id: z.string(), name: z.string(), tag: z.string() }); // no more `partial.()` here !

whereas currently it would output something like:

const ExplicitRequired = z.object({ id: z.string(), name: z.string(), tag: z.string().optional() });
const NoRequiredSet = z.object({ id: z.string(), name: z.string(), tag: z.string() }).partial();

URL param types

Hi, the current yaml will generate an api client expecting a '/pets/:petId' endpoint

    /pets/{petId}:
        get:
            summary: Info for a specific pet
            operationId: showPetById
            tags:
                - pets
            parameters:
                - name: petId
                  in: path
                  required: true
                  description: The id of the pet to retrieve
                  schema:
                      type: string

Here's the function

getPet = (petId: string) => api.get('/pets/:petId', { params: { petId } });

The problem when I call getPet("my-pet-id") is that it will make a GET request as /pets/:petId?petId=my-pet-id. Axios is not going to replace the :petId with the parameter value. I think the solution is that api.get should expect a url of type /pets/${string} instead of /pets/:petId and without params

TypeError: Cannot read properties of undefined (reading 'startsWith')

Huhu,

getting the following error on openapi-zod-client <file> -a --export-schemas --group-strategy 'tag'.
(any group-strategy seems to fail)

if (schemaName.startsWith("z.")) return;
                             ^

TypeError: Cannot read properties of undefined (reading 'startsWith')
    at addDependencyIfNeeded (node_modules/openapi-zod-client/dist/generateZodClientFromOpenAPI-8e9ca210.cjs.dev.js:1967:24)
    at node_modules/openapi-zod-client/dist/generateZodClientFromOpenAPI-8e9ca210.cjs.dev.js:1972:16
    at Array.forEach (<anonymous>)

for the same payload as here

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.