orval icon indicating copy to clipboard operation
orval copied to clipboard

Zod schemas generated with undefined min and max variables

Open CBell045 opened this issue 1 year ago • 6 comments

What are the steps to reproduce this issue?

  1. I am using FastAPI (v. 0.111.0) to generate my OpenAPI Schema (v. 3.1.0).
  2. Then I am using Orval to generate my Zod schemas.

What happens?

When I give a field a min and max length together with optional and nullable, Orval sets the min and max to undefined variables.

What were you expecting to happen?

I would rather have these be the numbers themselves instead of variables, or defined variables :)

Minimal Reproducible Example:

Example yaml:

openapi: 3.1.0
info:
  title: FastAPI
  version: 0.1.0
paths:
  /api/v1/users/:
    post:
      tags:
        - users
      summary: Create User
      operationId: create_user
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserRead'
components:
  schemas:
    UserCreate:
      properties:
        first_name:
          type: string
          maxLength: 50
          minLength: 1
          title: First Name
        last_name:
          type: string
          maxLength: 50
          minLength: 1
          title: Last Name
        email:
          anyOf:
            - type: string
              maxLength: 99
              minLength: 5
              format: email
            - type: 'null'
          title: Email
          nullable: true
        phone:
          anyOf:
            - type: string
              maxLength: 15
              minLength: 10
            - type: 'null'
          title: Phone
        date_of_birth:
          type: string
          format: date
          title: Date Of Birth
      type: object
      required:
        - first_name
        - last_name
        - date_of_birth
      title: UserCreate
      description: User create schema
    UserRead:
      properties:
        first_name:
          type: string
          maxLength: 50
          minLength: 1
          title: First Name
        last_name:
          type: string
          maxLength: 50
          minLength: 1
          title: Last Name
        email:
          anyOf:
            - type: string
              maxLength: 99
              minLength: 5
              format: email
            - type: 'null'
          title: Email
          nullable: true
        phone:
          anyOf:
            - type: string
              maxLength: 15
              minLength: 10
            - type: 'null'
          title: Phone
        date_of_birth:
          type: string
          format: date
          title: Date Of Birth
        user_id:
          type: integer
          title: User Id
      type: object
      required:
        - first_name
        - last_name
        - date_of_birth
        - user_id
      title: UserRead
      description: User read schema

Example config:

import { defineConfig } from "orval";

const target = 'test.yaml'

export default defineConfig({
  Zod: {
    input: {
      target: target,
    },
    output: {
      mode: "tags",
      target: './src/gen/zod',
      client: "zod",
    },
  }
});

Example Output:

/**
 * Generated by orval v6.31.0 🍺
 * Do not edit manually.
 * FastAPI
 * OpenAPI spec version: 0.1.0
 */
import {
  z as zod
} from 'zod'


/**
 * @summary Create User
 */
export const createUserBodyFirstNameMax = 50;
export const createUserBodyLastNameMax = 50;


export const createUserBody = zod.object({
  "first_name": zod.string().min(1).max(createUserBodyFirstNameMax),
  "last_name": zod.string().min(1).max(createUserBodyLastNameMax),
  "email": zod.string().email().min(createUserBodyEmailMinOne).max(createUserBodyEmailMaxOne).or(zod.null()).nullish(),
  "phone": zod.string().min(createUserBodyPhoneMinOne).max(createUserBodyPhoneMaxOne).or(zod.null()).optional(),
  "date_of_birth": zod.string().date()
})

export const createUserResponseFirstNameMax = 50;
export const createUserResponseLastNameMax = 50;


export const createUserResponse = zod.object({
  "first_name": zod.string().min(1).max(createUserResponseFirstNameMax),
  "last_name": zod.string().min(1).max(createUserResponseLastNameMax),
  "email": zod.string().email().min(createUserResponseEmailMinOne).max(createUserResponseEmailMaxOne).or(zod.null()).nullish(),
  "phone": zod.string().min(createUserResponsePhoneMinOne).max(createUserResponsePhoneMaxOne).or(zod.null()).optional(),
  "date_of_birth": zod.string().date(),
  "user_id": zod.number()
})

  • createUserBodyEmailMinOne and createUserResponseEmailMaxOne are not defined.

Any other comments?

Great library, thank you very much! Let me know how I can help out. And sorry if there is something small I am missing.

What versions are you using?

Operating System: MacOS (M1) Package Version: 6.31.0

CBell045 avatar Jun 25 '24 23:06 CBell045

PR is welcome if you want to look at the zod generating source.

melloware avatar Jun 26 '24 00:06 melloware

Any ideas on where in the zod file to start?

CBell045 avatar Jun 28 '24 03:06 CBell045

Start here maybe: https://github.com/anymaniax/orval/blob/e038704a13176379086feb71212157f65ca35353/packages/zod/src/index.ts#L111-L112

melloware avatar Jun 28 '24 12:06 melloware

I'm getting similar.

openapi version: "3.1.0"

openapi json:

 "/entity": {
      "get": {
        "tags": [
          "Entities"
        ],
        "summary": "List Entities",
        "description": "List entities with optional query parameter filters.",
        "operationId": "list_entities_entity_get",
        "parameters": [
...
 {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 100,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10,
              "title": "Page Size"
            }
          }
        ],
...

gives me: ** partial zod output**:

  page_size: zod
    .number()
    .min(1)
    .max(listEntitiesEntityGetQueryPageSizeMaxOne)
    .or(zod.null())
    .min(1)
    .max(listEntitiesEntityGetQueryPageSizeMax)
    .optional(),
});

image

image

I don't foresee myself having time to look into this soon, but if you find anything that may be helpful, let me know and I'll try to see.

stephan-noel avatar Jul 01 '24 22:07 stephan-noel

I'm also having issues with regexes. The following happens occurs several times in the api and the result is the same for all of them:

openapi doc

"phone_number": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(\\+?[1|0])?( )*([ \\[\\(])?\\d{3}([\\)\\]\\. \\-])?( )*\\d{3}([\\. \\-])?( )*\\d{4}$",
                "examples": [
                  "+15555555555",
                  "(555)555-55555",
                  "555.555.5555",
                  "(555) 555-5555",
                  "15555555555",
                  "[555]555-5555"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone Number"
          }

Resulting zod:

  phone_number: zod
    .string()
    .regex(getUserUserSubGetResponsePhoneNumberRegExpOne)
    .or(zod.null())
    .optional(),

image

Let me know if I should open a separate issue for this.

stephan-noel avatar Jul 01 '24 22:07 stephan-noel

@stephan-noel probably best to open a separate issue

melloware avatar Aug 18 '24 12:08 melloware

can someone re-test with 7.8.0 and let me know if this is still an issue?

melloware avatar Apr 04 '25 22:04 melloware

Looks like it is working now, thank you very much!

CBell045 avatar Apr 07 '25 17:04 CBell045