anatine / zod-plugins

Plugins and utilities for Zod
640 stars 89 forks source link

[zod-openapi] z.custom() field is incorrectly marked as nullable #177

Closed kheyse-oqton closed 9 months ago

kheyse-oqton commented 9 months ago

I want to define a field in an object as a

extendApi(
        z.custom<Type>(typeValidator),
        typeSchema
      )

with external validation function, external type definition, and external schema definition. I like that generateSchema seems to correctly determine if the field is optional or required based on the validation function. However for some reason the nullable property in the openapi schema remains true, even if the field is listed in the required fields. I would expect a required field to have nullable=false.

describe('zod custom', () => {
  test('custom field should be nullable and optional', () => {
    type Type = string;
    const schema = z.object({
      item: extendApi(
        z.custom<Type>(data => true), // This determines nullable
        generateSchema(z.string())
      )
    });
    expect(schema.safeParse({ item: 'test' }).success).toBeTruthy();
    expect(schema.safeParse({ item: null }).success).toBeTruthy();
    expect(schema.safeParse({}).success).toBeTruthy();
    expect(schema.shape.item.isOptional()).toBeTruthy();
    expect(schema.shape.item.isNullable()).toBeTruthy();
    expect(generateSchema(schema)).toMatchInlineSnapshot(`
      {
        "properties": {
          "item": {
            "nullable": true,
            "type": "string",
          },
        },
        "type": "object",
      }
    `);
  });

  test('custom field should be non-nullable and required', () => {
    type Type = string;
    const schema = z.object({
      item: extendApi(
        z.custom<Type>(data => !!data), // This determines non-nullable
        generateSchema(z.string())
      )
    });
    expect(schema.safeParse({ item: 'test' }).success).toBeTruthy();
    expect(schema.safeParse({ item: null }).success).toBeFalsy();
    expect(schema.safeParse({}).success).toBeFalsy();
    expect(schema.shape.item.isOptional()).toBeFalsy();
    expect(schema.shape.item.isNullable()).toBeFalsy();
    expect(generateSchema(schema)).toMatchInlineSnapshot(`
      {
        "properties": {
          "item": {
            "nullable": true,
            "type": "string",
          },
        },
        "required": [
          "item",
        ],
        "type": "object",
      }
    `);
  });
});
kheyse-oqton commented 9 months ago

Nullable seems to be removed in v3.1 and probably needs to be removed in the generated schemas. https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0

kheyse-oqton commented 9 months ago

Related to https://github.com/anatine/zod-plugins/pull/173