Skip to content

Incompatible Zod schema shape for oRPC when using orpc + zod plugins #3641

@adamsondavid

Description

@adamsondavid

Description

When generating an oRPC contract using @hey-api/openapi-ts with both the orpc and zod plugins enabled, the generated Zod input schema does not match the structure expected by oRPC for putStructure: 'detailed'.

Specifically, path parameters are generated under a path key, while oRPC expects them under params. This mismatch causes the generated contract to be incompatible with oRPC without manual adjustments.

Usecase: I am generating a oRPC contract for a server implementation.

// openapi-ts.config.ts
import { defineConfig } from "@hey-api/openapi-ts";

export default defineConfig({
  input: "https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml",
  output: {
    path: "./src/client",
  },
  plugins: ["orpc", "zod"],
});

This currently generates the following code:

// orpc.gen.ts
// ...
export const getPetById = base.route({
    description: 'Returns a single pet.',
    method: 'GET',
    operationId: 'getPetById',
    path: '/pet/{petId}',
    summary: 'Find pet by ID.',
    tags: ['pet']
}).input(zGetPetByIdData).output(zGetPetByIdResponse);
// ...
// zod.gen.ts
// ...
export const zGetPetByIdData = z.object({
    body: z.never().optional(),
    path: z.object({
        petId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' })
    }),
    query: z.never().optional()
});
// ...

The generated orpc.gen.ts looks good, however, it is not compatible with the generated zod.gen.ts.
My oRPC server implementation raises a validation error, when I send a request:

{"defined":false,"code":"BAD_REQUEST","status":400,"message":"Input validation failed","data":{"issues":[{"expected":"object","code":"invalid_type","path":["path"],"message":"Invalid input: expected object, received undefined"},{"expected":"never","code":"invalid_type","path":["query"],"message":"Invalid input: expected never, received object"}]}}

Expected behavior:
a) orpc.gen.ts does not use zod.gen.ts to import schemas for input validation or b) zod.gen.ts is generated slightly different when used together with the orpc plugin:

  • query should be an z.object({}) without members, if the endpoint has no query params.
  • path needs to be called params.

When I manually change zod.gen.ts to look like this, oRPC Server stops to complain:

// zod.gen.ts
// ...
export const zGetPetByIdData = z.object({
  body: z.never().optional(),
  /*path*/ params: z.object({
    petId: z.coerce
      .bigint()
      .min(BigInt("-9223372036854775808"), { error: "Invalid value: Expected int64 to be >= -9223372036854775808" })
      .max(BigInt("9223372036854775807"), { error: "Invalid value: Expected int64 to be <= 9223372036854775807" }),
  }),
  query: /*z.never().optional()*/ z.object({}),
});
// ...

Thanks for your work on this project!

Reproducible example or configuration

https://stackblitz.com/edit/hey-api-example-h1wa7hud

OpenAPI specification (optional)

No response

System information (optional)

@hey-api/openapi-ts: 0.94.4
@orpc/contract: 1.13.10
@orpc/openapi: 1.13.10
@orpc/server: 1.13.10
zod: 4.3.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🔥Broken or incorrect behavior.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions