Skip to content

fix(zod): emit reusable schemas inline when operations exist + use PascalCase identifiers - #3465

Merged
melloware merged 3 commits into
orval-labs:masterfrom
z4o4z:fix/reusable-schemas-inline-with-operations
May 27, 2026
Merged

fix(zod): emit reusable schemas inline when operations exist + use PascalCase identifiers#3465
melloware merged 3 commits into
orval-labs:masterfrom
z4o4z:fix/reusable-schemas-inline-with-operations

Conversation

@z4o4z

@z4o4z z4o4z commented May 27, 2026

Copy link
Copy Markdown
Contributor

Two related fixes for override.zod.generateReusableSchemas (both surfaced on the same real-world spec). Happy to split if preferred.

1. Component schemas not emitted inline when operations exist

With client: 'zod' + generateReusableSchemas: true and no separate schemas: dir (e.g. mode: 'single'), operations reference component schemas by name but the component definitions were never emitted — the output didn't compile.

shouldGenerateZodSchemasInline only returned true when there were no operations. Without the reusable flag that's correct (operations inline their own schemas), but with it, operations reference the component schemas by name, so the definitions must be emitted inline alongside them.

  • shouldGenerateZodSchemasInline now also returns true when the flag is on.
  • Thread an includeZodImport flag through generateZodSchemasInline / generateZodSchemasInlineReusable / generateZodSchemaFileContent: when operations are present the zod client already emits import * as zod from 'zod', so the inline block must not add a second import { z as zod } (which would redeclare zod). With no operations, the inline block stays the sole zod import.

2. Reusable schema identifiers were camelCase (inconsistent)

Reusable schema exports used conventionName(name, namingConvention) for the identifier, defaulting to camelCase (authorizeResponse). That clashed with operation wrappers (AuthorizeAuthorizeBody, via pascal()) and the TS model types (AuthorizeResponse) generated for the same spec.

Both the generator's namedRef emission and the orchestrator's resolveSchemaName now use getRefInfo(ref, context).name — the exact identifier orval already emits for the TS model type (pascal + sanitize + component suffix). namingConvention continues to govern file names only (e.g. authorizeResponse.zod.ts), consistent with the rest of orval; identifiers are always PascalCase.

Before / after (mode: 'single'):

// before
export const AuthorizeAuthorize200Response = authorizeResponse   // ❌ undefined + wrong case
// after
export const authorizeResponse =                               // defined inline... wait, now:
export const AuthorizeResponse = zod.object({});
export const AuthorizeAuthorize200Response = AuthorizeResponse    // ✅ defined, PascalCase

Test plan

  • New end-to-end test (generate-spec.test.ts): single-mode + reusable + an operation referencing a component schema → component defined inline (before use), referenced by name, exactly one zod import, no __REF_ sentinels.
  • Updated zod + orchestrator unit tests for PascalCase identifiers.
  • Verified end-to-end against a real spec (single-mode, ~20 operations, 4 component schemas): all component schemas emitted, single import, PascalCase identifiers matching the operation wrappers + TS types, compiles.
  • swr-with-zod reusable snapshots regenerated (identifiers now PascalCase; file names unchanged — still camelCase).
  • Full typecheck (13 packages) + @orval/zod (183), @orval/orval (117), @orval/core (1933) suites pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Avoid duplicate Zod imports, ensure inline/reusable schemas are emitted in correct order, and remove unresolved reference sentinels.
  • Refactor

    • Generated reusable schema exports now use PascalCase identifiers (e.g., Pet instead of pet) for consistent naming.
  • Tests

    • New/updated regression tests validating import handling, inline emission, schema ordering, PascalCase refs, and complete reference resolution.

Review Change Stack

With `client: 'zod'` + `generateReusableSchemas: true` and no `schemas:`
dir (e.g. `mode: 'single'`), operations reference component schemas by
name but the component definitions were never emitted. `shouldGenerateZodSchemasInline`
only returned true when there were no operations, so the references
dangled and the output didn't compile (e.g. `zod.array(authorizeResource)`
with no `authorizeResource` definition).

- `shouldGenerateZodSchemasInline` now also returns true when the flag is
  on, so component schemas are emitted inline alongside operations.
- Thread an `includeZodImport` flag through `generateZodSchemasInline` /
  `generateZodSchemasInlineReusable` / `generateZodSchemaFileContent`:
  when operations are present the zod client already emits
  `import * as zod from 'zod'`, so the inline block must NOT add a second
  `import { z as zod }` (which would redeclare `zod`). When there are no
  operations the inline block remains the sole zod import.

Test: end-to-end single-mode + reusable + operations now defines the
referenced component schema inline (before its use), references it by
name, emits exactly one zod import, and leaves no `__REF_` sentinels.
Copilot AI review requested due to automatic review settings May 27, 2026 15:10
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0bb77b51-4630-45d6-ad0e-642c41405837

📥 Commits

Reviewing files that changed from the base of the PR and between 1f00ec8 and 9952fa6.

📒 Files selected for processing (2)
  • packages/orval/src/generate-spec.test.ts
  • packages/orval/src/write-specs.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/orval/src/write-specs.ts
  • packages/orval/src/generate-spec.test.ts

📝 Walkthrough

Walkthrough

Refactors reusable-schema naming to use Orval ref info (PascalCase exports) and makes Zod inline-schema emission conditionally include the Zod import when operations are absent; tests and snapshots updated to assert single Zod import, correct named exports, and no unresolved _REF sentinels.

Changes

Zod Inline Schema Generation with Conditional Imports & Reusable-schema Naming

Layer / File(s) Summary
Refactor reusable-schema name resolution
packages/orval/src/reusable-schemas.ts, packages/zod/src/index.ts, packages/zod/src/zod.test.ts
Resolve reusable export names using getRefInfo(ref, context).name; update resolveSchemaName/resolveSchemaNames to accept context and adapt downstream usage and tests to PascalCase names.
Conditional Zod import in file-content generation
packages/orval/src/write-zod-specs.ts
generateZodSchemaFileContent gains includeZodImport and conditionally builds the import block; reusable and non-reusable inline generation paths thread this option.
Inline generation decision and parameter threading
packages/orval/src/write-specs.ts, packages/orval/src/write-zod-specs.ts, packages/orval/src/generate-spec.test.ts
shouldGenerateZodSchemasInline now respects client type and output.schemas; generateZodSchemasInline is called with !hasOperations to avoid emitting duplicate zod imports; regression tests added/updated to assert ordering, single import, and no __REF_ sentinels.
Tests & sample snapshots updated
packages/orval/src/reusable-schemas.test.ts, packages/orval/src/write-zod-specs.test.ts, samples/swr-with-zod/__snapshots__/*
Unit tests and generated sample snapshots updated to expect PascalCase exported schema identifiers and to reflect updated import/export and sentinel naming.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • melloware

🐰 I hopped through refs and tamed imports fair,
PascalNames now bloom, no lowercase to spare.
One Zod line stands, no duplicates to find,
Sentinels vanish, tidy output aligned.
Cheers from a rabbit, code neat and kind.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main fixes: inline emission of reusable schemas when operations exist, and adoption of PascalCase identifiers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adjusts zod schema emission in single-mode generation to support generateReusableSchemas when operations are present, while preventing duplicate zod imports in the concatenated output.

Changes:

  • Emit component schemas inline even when operations exist if override.zod.generateReusableSchemas is enabled.
  • Add an includeZodImport toggle so inline schema generation can omit import { z as zod } when the client already imports zod.
  • Add a regression test covering single-mode + reusable schemas + operations inline emission and import deduping.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/orval/src/write-zod-specs.ts Adds includeZodImport plumbing and avoids emitting an empty import block / duplicate zod import in inline output.
packages/orval/src/write-specs.ts Updates inline-generation decision logic for reusable schemas and disables schema-side zod import when operations are present.
packages/orval/src/generate-spec.test.ts Adds regression test for inline reusable schemas + operations in single mode (including single zod import check).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/orval/src/write-specs.ts
Comment thread packages/orval/src/write-specs.ts Outdated
Comment thread packages/orval/src/generate-spec.test.ts Outdated
@melloware melloware added the zod Zod schema client related issue label May 27, 2026
@melloware melloware added this to the 8.14.0 milestone May 27, 2026
@melloware

Copy link
Copy Markdown
Collaborator

@z4o4z i noticed this too yesterday when i tried it. Thanks for fixing

Reusable schema exports used `conventionName(name, namingConvention)`
for the identifier, defaulting to camelCase (`authorizeResponse`), which
was inconsistent with operation wrappers (`AuthorizeAuthorizeBody` via
`pascal()`) and the TS model types (`AuthorizeResponse`) in the same
project.

Both the generator's `namedRef` emission and the orchestrator's
`resolveSchemaName` now use `getRefInfo(ref, context).name` — the exact
identifier orval already emits for the TS model type (`pascal` +
sanitize + component suffix). `namingConvention` continues to govern
file names only (e.g. `authorizeResponse.zod.ts`), consistent with the
rest of orval; identifiers are always PascalCase.

- Drop the now-moot kebab-case validation (sanitized identifiers are
  always valid) and the bespoke `lastRefSegment`/`conventionName` paths.
- Update zod + orchestrator tests for PascalCase names.
- Regenerate swr-with-zod reusable snapshots.
@z4o4z z4o4z changed the title fix(zod): emit reusable component schemas inline when operations exist fix(zod): emit reusable schemas inline when operations exist + use PascalCase identifiers May 27, 2026
- Extract `includeZodImport = !hasOperations` local at the
  generateSchemasInline call site so it reads with the comment.
- Anchor the inline-ordering test on the operation export name
  (`export const ListPets`) instead of the incidental `Item = Pet`
  substring.

Note: kept the direct boolean use in `shouldGenerateZodSchemasInline`
(not `=== true`): `NormalizedOutputOptions` types the field as a
required boolean, so `=== true` trips no-unnecessary-boolean-literal-compare.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

zod Zod schema client related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants