Skip to content

fix(zod): keep required on constraint-only oneOf/anyOf branches - #3783

Merged
melloware merged 2 commits into
orval-labs:masterfrom
ostapondo:fix/zod-constraint-only-oneof-branches
Jul 29, 2026
Merged

fix(zod): keep required on constraint-only oneOf/anyOf branches#3783
melloware merged 2 commits into
orval-labs:masterfrom
ostapondo:fix/zod-constraint-only-oneof-branches

Conversation

@ostapondo

@ostapondo ostapondo commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Addresses the zod.unknown() half of #3780.

A oneOf/anyOf branch is allowed to declare no shape of its own and only list
required, leaving the property types on the composing schema — the two branches
in #3780 are exactly that. Each branch is rendered on its own, where there is no
type to resolve, so it falls through to zod.unknown() and its required is
dropped. The result is zod.union([zod.unknown(), zod.unknown()]).and(...), which
accepts everything the union was supposed to constrain.

Since JSON Schema applies every branch to the same instance, those branches are
now rendered against the composing schema's properties. The allOf path already
gets the same effect via additionalRequired (#3171); this is the oneOf/anyOf
counterpart. Branches that describe a shape of their own are untouched.

A member only counts as shape-less when required is all it says, aside from
title/description (annotations, carried over so .describe() survives) and
not, which this generator does not translate into anything today. Members
carrying enum, const, additionalProperties, nullable or default already
render to something meaningful and are left untouched — I compared every such
keyword against master to make sure nothing else changes.

Against the repro spec, empty and incompleteXY now fail as they should, and
the two valid payloads still pass. invalidMixed still passes, because that one
needs the not in the AB branch — orval has no not support anywhere today, so
that felt like a separate change rather than something to fold in here.

tests/specifications/one-of-required.yaml already has this schema shape, but it
is only wired into default.config.ts, so no generated output changes. I checked
the other 349 specs in the repo for the pattern; none are affected. Verified end to end by
running the CLI over the spec from the issue and parsing the five payloads with
zod: 4 of 5 now match, against 2 of 5 before. Regression tests added, full suite
and typecheck pass.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Zod schema generation for OpenAPI oneOf/anyOf branches that specify only required constraints, applying them to sibling properties instead of producing fallback unknown branches.
    • Preserves branch description and supports branches that include not, while treating $ref branches as non-constraint-only.
    • Maintains existing behavior when required keys can’t be matched to sibling schemas and keeps allOf behavior unchanged.
  • Tests
    • Added regression coverage for constraint-only oneOf/anyOf cases, including required/description handling, sibling-absence behavior, and constraint-carrying members within branches.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e1b9f67e-b2c0-4d49-b99f-706d4a70b276

📥 Commits

Reviewing files that changed from the base of the PR and between 75d7ffd and 2758509.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts

📝 Walkthrough

Walkthrough

Zod generation now applies required constraints from eligible shape-less oneOf and anyOf members to sibling properties. Regression tests cover annotations, descriptions, missing properties, shaped branches, standalone constraints, and unchanged allOf behavior.

Changes

Zod union constraint handling

Layer / File(s) Summary
Constraint-only union rendering
packages/zod/src/index.ts
Constraint-only oneOf/anyOf members are detected, excluding $ref members, and rendered with the composing schema’s sibling properties while preserving required and description.
Constraint-only union regression coverage
packages/zod/src/zod.test.ts
Tests cover oneOf/anyOf, not, standalone constraints, descriptions, shaped branches, missing or absent sibling properties, and unchanged allOf behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • orval-labs/orval#3780: Directly tracks handling constraint-only oneOf/anyOf branches.

Possibly related PRs

Suggested labels: bug

Suggested reviewers: melloware, z4o4z

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preserving required constraints on constraint-only oneOf/anyOf branches.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zod/src/index.ts`:
- Around line 303-317: Update isConstraintOnlyMember to reject schemas
containing additionalProperties, so members are classified as constraint-only
only when they contain required constraints without additional schema behavior.
Preserve the existing handling for other schema keywords and add a regression
case proving additionalProperties reaches the normal generator path instead of
being discarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 136d7cfe-959e-43ef-81fd-5ef1cb550922

📥 Commits

Reviewing files that changed from the base of the PR and between 4917ea0 and 9a1f0f7.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts

Comment thread packages/zod/src/index.ts Outdated
@ostapondo
ostapondo force-pushed the fix/zod-constraint-only-oneof-branches branch from 9a1f0f7 to b014b00 Compare July 29, 2026 12:52

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zod/src/zod.test.ts`:
- Around line 12212-12224: The regression assertions are too weak to detect the
reported behavior. In packages/zod/src/zod.test.ts lines 12212-12224, update the
test around “leaves branches that declare their own shape untouched” to match
the actual integer output, zod.number().int(), and assert the shaped branch
contains only its own properties. In packages/zod/src/zod.test.ts lines
12281-12289, strengthen the allOf test to require A to be non-optional and,
ideally, verify B remains optional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aa9c7b8c-8c57-49ea-8c09-5670afdf8f99

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1f0f7 and b014b00.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/zod/src/index.ts

Comment thread packages/zod/src/zod.test.ts
@melloware melloware added the zod Zod schema client related issue label Jul 29, 2026
@melloware melloware added this to the 8.24.0 milestone Jul 29, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@orval/angular

bun add https://pkg.pr.new/@orval/angular@2758509

@orval/axios

bun add https://pkg.pr.new/@orval/axios@2758509

@orval/core

bun add https://pkg.pr.new/@orval/core@2758509

@orval/effect

bun add https://pkg.pr.new/@orval/effect@2758509

@orval/fetch

bun add https://pkg.pr.new/@orval/fetch@2758509

@orval/hono

bun add https://pkg.pr.new/@orval/hono@2758509

@orval/mcp

bun add https://pkg.pr.new/@orval/mcp@2758509

@orval/mock

bun add https://pkg.pr.new/@orval/mock@2758509

orval

bun add https://pkg.pr.new/orval@2758509

@orval/query

bun add https://pkg.pr.new/@orval/query@2758509

@orval/solid-start

bun add https://pkg.pr.new/@orval/solid-start@2758509

@orval/swr

bun add https://pkg.pr.new/@orval/swr@2758509

@orval/zod

bun add https://pkg.pr.new/@orval/zod@2758509

commit: 2758509

@ostapondo
ostapondo force-pushed the fix/zod-constraint-only-oneof-branches branch from b014b00 to 327bbe4 Compare July 29, 2026 13:08
@ostapondo
ostapondo force-pushed the fix/zod-constraint-only-oneof-branches branch from 327bbe4 to 75d7ffd Compare July 29, 2026 13:49
A branch that declares no shape of its own and only lists `required` was
rendered in isolation, where it has no type to resolve and falls through
to `zod.unknown()`. The constraint was silently dropped, so the generated
validator accepted payloads the spec rejects.

JSON Schema applies every branch to the same instance, so the property
types live on the composing schema. Render such branches against those
sibling properties instead, which is what the `allOf` path already
achieves through `additionalRequired`.

A member only counts as shape-less when `required` is all it says, aside
from `title`/`description` (annotations, carried over) and `not` (not
translated by this generator today). Members carrying `enum`, `const`,
`additionalProperties`, `nullable` or `default` already render to
something meaningful and are left untouched.
@ostapondo
ostapondo force-pushed the fix/zod-constraint-only-oneof-branches branch from 75d7ffd to 1053abc Compare July 29, 2026 13:51

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/zod/src/zod.test.ts (1)

12229-12238: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover default as a standalone branch constraint.

default is intentionally excluded from constraint-only rewriting, but this matrix does not test it. Add a case that verifies the default-bearing member remains standalone rather than receiving sibling properties.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/zod.test.ts` around lines 12229 - 12238, Add a `default`
case to the `it.each` matrix for “leaves a member carrying %s alone,” using a
representative default constraint and expected output that confirms the member
remains standalone without sibling properties. Keep the existing constraint-only
cases unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zod/src/zod.test.ts`:
- Around line 12187-12199: Strengthen the requiredness assertions in the three
test cases: in the branch test at packages/zod/src/zod.test.ts lines
12187-12199, assert Y is present and optional in the AB branch and required in
the XY branch; at lines 12201-12210, assert the complete A/B and X/Y
requiredness for both anyOf branches; and at lines 12249-12271, assert the
not-bearing branch requires A and B rather than only checking it is not
zod.unknown().

---

Nitpick comments:
In `@packages/zod/src/zod.test.ts`:
- Around line 12229-12238: Add a `default` case to the `it.each` matrix for
“leaves a member carrying %s alone,” using a representative default constraint
and expected output that confirms the member remains standalone without sibling
properties. Keep the existing constraint-only cases unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 53784afc-96c3-4c97-8489-cc44f9a39070

📥 Commits

Reviewing files that changed from the base of the PR and between 327bbe4 and 75d7ffd.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/zod/src/index.ts

Comment thread packages/zod/src/zod.test.ts

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zod/src/index.ts`:
- Around line 792-798: Update the synthesized OpenApiSchemaObject branch in the
relevant schema conversion logic to retain every key listed in required,
including keys absent from schema.properties; add those missing keys with the
applicable additional-properties schema where available so an empty object
cannot match. Preserve the existing description and declared-property handling,
and add a regression test for required: ['kind'] without properties.kind.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 235980ca-2e89-47a9-ab80-155b52b7f3a0

📥 Commits

Reviewing files that changed from the base of the PR and between 75d7ffd and 1053abc.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/zod/src/zod.test.ts

Comment thread packages/zod/src/index.ts Outdated
A branch may require a key the composing schema never declares. Rewriting
it produced an object that omitted the key entirely, so an empty payload
matched a branch that should have rejected it.

zod cannot express `present, type unspecified` — `unknown` and `any` are
both optional inside an object — so such a branch now keeps the existing
behaviour instead of an object that only looks like it enforces the
constraint.

Also pin both branch bodies whole in the regression tests; the previous
assertions stopped short of the last property and would have passed had
it gone missing.
@ostapondo

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@melloware
melloware merged commit 5add964 into orval-labs:master Jul 29, 2026
7 checks passed
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.

Zod: constraint-only oneOf branches with sibling properties generate zod.unknown() and lose validation

2 participants