Skip to content

fix(mock): propagate required from $ref allOf members to overriding branches - #3801

Merged
melloware merged 4 commits into
orval-labs:masterfrom
federicobartoli:fix/mock-allof-required-union
Aug 4, 2026
Merged

fix(mock): propagate required from $ref allOf members to overriding branches#3801
melloware merged 4 commits into
orval-labs:masterfrom
federicobartoli:fix/mock-allof-required-union

Conversation

@federicobartoli

@federicobartoli federicobartoli commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

When an allOf member narrows an inherited enum property to a single literal without re-listing it in its own required, the mock generator emitted faker.helpers.arrayElement([value, undefined]) for that property. The property stays required through the $ref'd base schema, so the generated mock fails to typecheck (Type 'undefined' is not assignable to the enum union).

Root cause: combineSchemasMock collected the allOf required union only from inline members and never dereferenced $ref members, so required fields declared on a referenced base were invisible to sibling override branches.

The fix dereferences $ref members when collecting the union, following $ref alias chains and nested allOf compositions with a cycle guard. Names collected from a nested composition cross its boundary only when that composition also declares the property, keeping mock requiredness aligned with the generated types (#3663) and preventing the #910 cyclic reference guard from flipping property omission to null. The union now also applies to sibling properties declared next to allOf, and constraint-only members ({ required: [...] }) count as well (#3750 semantics). With faker schemas: true, factory delegation is skipped when it would drop the composition-wide required union; the schema body is inlined instead.

Covered by 7 new unit tests in packages/mock and an e2e regression spec (tests/specifications/allof-narrowed-enum.yaml) exercising both the msw inline path and the faker schema-factory path, with the generated output part of the typechecked corpus. All pre-existing snapshots are unchanged.

Summary by CodeRabbit

  • Bug Fixes

    • Improved mock generation for OpenAPI schemas using composed definitions and references.
    • Required properties are now preserved across nested compositions, aliases, sibling schemas, and cyclic references.
    • Prevented undeclared nested fields from being incorrectly marked as required.
    • Preserved optional properties when no composition branch requires them.
    • Improved handling of narrowed enum properties in generated mock responses.
  • Tests

    • Added comprehensive regression coverage for composed schemas, cyclic references, and validation-error responses.

…ranches

The faker/msw mock generator collected allOf required fields only from
inline members. A property required by a $ref'd base schema, such as an
enum narrowed to a single literal in an override branch, was mocked as
optional and wrapped in faker.helpers.arrayElement([..., undefined]),
producing mocks that fail to typecheck against the generated
intersection type.

combineSchemasMock now dereferences $ref members when collecting the
required union, following $ref alias chains and nested allOf
compositions with a cycle guard. Required names collected from a nested
composition cross its boundary only when that composition also declares
the property, matching the model generator (orval-labs#3663) and keeping the orval-labs#910
cyclic reference guard from flipping property omission to null.

The union is also applied to sibling properties declared next to allOf,
and constraint-only members ({ required: [...] }) now count (orval-labs#3750
semantics). With faker schemas: true, delegation to a shared factory is
skipped when it would drop the composition-wide required union; the
schema body is inlined instead.
Copilot AI review requested due to automatic review settings August 3, 2026 16:04
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: a1aa17d8-b667-423a-bf40-c82cd6b8623f

📥 Commits

Reviewing files that changed from the base of the PR and between 379bad9 and 533d552.

📒 Files selected for processing (2)
  • packages/mock/src/faker/getters/all-of-required.ts
  • packages/mock/src/faker/getters/combine.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/mock/src/faker/getters/all-of-required.ts
  • packages/mock/src/faker/getters/combine.test.ts

📝 Walkthrough

Walkthrough

The mock generator now recursively propagates required fields through $ref and allOf compositions. Schema-factory delegation preserves composition-level requirements. New OpenAPI fixtures and tests cover aliases, nested compositions, narrowed enums, boundaries, and optional properties.

Changes

Required-field propagation

Layer / File(s) Summary
Recursive required-field collection
packages/mock/src/faker/getters/all-of-required.ts, packages/mock/src/faker/getters/combine.test.ts
collectAllOfRequired resolves $ref aliases and nested allOf members with cycle protection and declared-property filtering.
Schema combination with merged requirements
packages/mock/src/faker/getters/combine.ts, packages/mock/src/faker/getters/combine.test.ts
combineSchemasMock applies recursively collected required fields to sibling properties and nested composition members.
Schema-factory delegation preservation
packages/mock/src/faker/resolvers/value.ts, packages/mock/src/faker/getters/combine.test.ts
Referenced schemas are not delegated when surrounding composition requirements would become optional.
Composition regression coverage
tests/specifications/allof-narrowed-enum.yaml, tests/configs/mock.config.ts, packages/mock/src/faker/getters/combine.test.ts
Fixtures and tests cover aliases, narrowed enums, constraint-only members, composition boundaries, cycles, delegation, and optional properties.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant combineSchemasMock
  participant resolveMockValue
  participant SharedMockFactory
  combineSchemasMock->>resolveMockValue: resolve composed property with merged required fields
  resolveMockValue->>resolveMockValue: compare composition requirements with referenced schema
  resolveMockValue->>SharedMockFactory: delegate only when requirements are preserved
Loading

Possibly related issues

  • orval-labs/orval#3802: Both changes address recursive $ref handling, cycle-safe traversal, and required-field propagation in Faker mock generation.

Possibly related PRs

Suggested labels: bug, openapi

Suggested reviewers: melloware

🚥 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 summarizes the main change: propagating required properties from referenced allOf members to overriding 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.

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.

🟡 Not ready to approve

The new schema-factory delegation guard in packages/mock/src/faker/resolvers/value.ts only checks top-level properties/required and can still incorrectly delegate (dropping requiredness) when the referenced schema declares the affected property only via its own allOf members.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR fixes faker/MSW mock generation for allOf compositions so that required fields declared on $ref’d members (including $ref-to-$ref aliases and nested allOf) correctly propagate to sibling override branches, preventing mocks that include undefined for still-required properties.

Changes:

  • Enhance combineSchemasMock to union required across allOf members by dereferencing $ref members (with cycle-guarding) and applying the union to sibling properties declared next to allOf.
  • Adjust faker schema-factory delegation logic to avoid delegating when it would drop composition-wide requiredness.
  • Add a new regression OpenAPI spec plus unit/e2e snapshot coverage for narrowed enums, $ref aliases, sibling properties, and constraint-only required overlays.
File summaries
File Description
tests/specifications/allof-narrowed-enum.yaml New regression spec covering narrowed-enum + $ref/alias/allOf required propagation cases.
tests/configs/mock.config.ts Adds a new test config entry to generate mocks for the regression spec.
tests/snapshots/mock/allof-narrowed-enum/model/validationErrorViolationsItem.ts New generated type snapshot for regression corpus.
tests/snapshots/mock/allof-narrowed-enum/model/validationErrorErrorType.ts New generated enum/type snapshot for regression corpus.
tests/snapshots/mock/allof-narrowed-enum/model/validationError.ts New generated composed type snapshot validating requiredness via intersection.
tests/snapshots/mock/allof-narrowed-enum/model/siblingValidationErrorErrorType.ts New generated enum/type snapshot for sibling-properties case.
tests/snapshots/mock/allof-narrowed-enum/model/siblingValidationError.ts New generated composed type snapshot for sibling-properties case.
tests/snapshots/mock/allof-narrowed-enum/model/itemDetail.ts New generated type snapshot for constraint-only required overlay case.
tests/snapshots/mock/allof-narrowed-enum/model/item.ts New generated base type snapshot for overlay case.
tests/snapshots/mock/allof-narrowed-enum/model/index.ts New barrel snapshot for generated model exports.
tests/snapshots/mock/allof-narrowed-enum/model/index.faker.ts New faker factory snapshot validating required vs optional output branches.
tests/snapshots/mock/allof-narrowed-enum/model/baseErrorErrorType.ts New generated enum/type snapshot for base schema.
tests/snapshots/mock/allof-narrowed-enum/model/baseErrorAlias.ts New generated alias snapshot for $ref-to-$ref propagation case.
tests/snapshots/mock/allof-narrowed-enum/model/baseError.ts New generated base type snapshot ensuring required keys exist.
tests/snapshots/mock/allof-narrowed-enum/model/aliasedValidationErrorErrorType.ts New generated enum/type snapshot for aliased composition case.
tests/snapshots/mock/allof-narrowed-enum/model/aliasedValidationError.ts New generated composed type snapshot for aliased base case.
tests/snapshots/mock/allof-narrowed-enum/endpoints.ts New MSW endpoints snapshot exercising the inline generation path.
packages/mock/src/faker/resolvers/value.ts Prevents schema-factory delegation when it would drop composition-wide requiredness.
packages/mock/src/faker/getters/combine.ts Dereferences $ref allOf members to collect required unions (incl. nested allOf/aliases) and applies union to sibling properties.
packages/mock/src/faker/getters/combine.test.ts Adds unit tests covering required union propagation across $ref, nested allOf, aliases, sibling properties, and constraint-only members.
Review details
  • Files reviewed: 20/20 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +357 to +366
const targetProperties = schemaRef?.properties as
| Record<string, unknown>
| undefined;
const targetOwnRequired = (schemaRef?.required as string[]) ?? [];
const delegationDropsRequired = (schemaReference.required ?? []).some(
(requiredName) =>
targetProperties &&
requiredName in targetProperties &&
!targetOwnRequired.includes(requiredName),
);
@pkg-pr-new

pkg-pr-new Bot commented Aug 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@orval/angular

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

@orval/axios

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

@orval/core

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

@orval/effect

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

@orval/fetch

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

@orval/hono

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

@orval/mcp

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

@orval/mock

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

orval

bun add https://pkg.pr.new/orval@533d552

@orval/query

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

@orval/solid-start

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

@orval/swr

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

@orval/zod

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

commit: 533d552

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

🧹 Nitpick comments (1)
packages/mock/src/faker/getters/combine.ts (1)

23-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a direct test for the allOf-only reference cycle guard.

The recursive required-field collection is correct against all seven added tests. derefAllOfMember's seen guard is new infinite-loop protection, but no test exercises a pure allOf cycle where two component schemas reference each other directly in allOf (for example, A: allOf: [$ref: B], B: allOf: [$ref: A]). The existing "does not propagate nested required names" test exercises a different case: Node's self-reference happens through a properties.parent value, which collectAllOfRequiredWithDeclared never walks into, so it does not exercise this new cycle guard.

Add a test with two schemas whose allOf arrays reference each other directly to confirm collectAllOfRequired terminates instead of looping.

🤖 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/mock/src/faker/getters/combine.ts` around lines 23 - 119, Add a
focused test for collectAllOfRequired using two component schemas whose allOf
entries reference each other directly, such as A referencing B and B referencing
A. Assert that required-field collection terminates and returns the expected
result, exercising derefAllOfMember’s seen cycle guard rather than a
self-reference nested under properties.
🤖 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.

Nitpick comments:
In `@packages/mock/src/faker/getters/combine.ts`:
- Around line 23-119: Add a focused test for collectAllOfRequired using two
component schemas whose allOf entries reference each other directly, such as A
referencing B and B referencing A. Assert that required-field collection
terminates and returns the expected result, exercising derefAllOfMember’s seen
cycle guard rather than a self-reference nested under properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 819a2459-7d4b-41e6-9021-17721d1e26b7

📥 Commits

Reviewing files that changed from the base of the PR and between 992a525 and f182a50.

⛔ Files ignored due to path filters (15)
  • tests/__snapshots__/mock/allof-narrowed-enum/endpoints.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/aliasedValidationError.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/aliasedValidationErrorErrorType.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/baseError.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/baseErrorAlias.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/baseErrorErrorType.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/index.faker.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/index.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/item.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/itemDetail.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/siblingValidationError.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/siblingValidationErrorErrorType.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/validationError.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/validationErrorErrorType.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/validationErrorViolationsItem.ts is excluded by !**/__snapshots__/**
📒 Files selected for processing (5)
  • packages/mock/src/faker/getters/combine.test.ts
  • packages/mock/src/faker/getters/combine.ts
  • packages/mock/src/faker/resolvers/value.ts
  • tests/configs/mock.config.ts
  • tests/specifications/allof-narrowed-enum.yaml

…tion guard

The delegation guard for composition-wide required unions read only the
referenced schema's direct properties and required. When the target
declares its properties behind its own allOf (overlay pattern), the
guard saw nothing, delegation went ahead, and the shared factory kept
the arrayElement([..., undefined]) branch for a property the composition
requires, so the generated mock failed to typecheck.

The guard now computes the target's effective declared and required sets
with the same recursive collector used by combineSchemasMock, moved to
getters/all-of-required.ts and shared by both call sites.

Addresses the review finding on orval-labs#3801.
…tual $ref cycles

Focused tests for the collector itself: a pair of component schemas
whose allOf entries reference each other directly must terminate through
the seen guard while still collecting the required fields declared on
each side, and a cyclic $ref alias chain must resolve to nothing.
Previously the guard was only exercised transitively through
combineSchemasMock cycle tests that carry no required fields.

Addresses a review nitpick on orval-labs#3801.

@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/mock/src/faker/getters/all-of-required.ts`:
- Around line 63-96: Update collectAllOfRequiredWithDeclared so each schema
member is traversed with a branch-local copy of seen, preventing nested
traversal of one sibling from suppressing a later direct reference to the same
target. Preserve cycle protection within each traversal path and keep the
existing declared/required boundary filtering 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: a7c849ae-feaa-4d07-b9fc-29f1243ef471

📥 Commits

Reviewing files that changed from the base of the PR and between f182a50 and 379bad9.

⛔ Files ignored due to path filters (4)
  • tests/__snapshots__/mock/allof-narrowed-enum/model/index.faker.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/index.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/overlayBase.ts is excluded by !**/__snapshots__/**
  • tests/__snapshots__/mock/allof-narrowed-enum/model/overlayDetail.ts is excluded by !**/__snapshots__/**
📒 Files selected for processing (5)
  • packages/mock/src/faker/getters/all-of-required.ts
  • packages/mock/src/faker/getters/combine.test.ts
  • packages/mock/src/faker/getters/combine.ts
  • packages/mock/src/faker/resolvers/value.ts
  • tests/specifications/allof-narrowed-enum.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/mock/src/faker/getters/combine.ts
  • packages/mock/src/faker/resolvers/value.ts

Comment thread packages/mock/src/faker/getters/all-of-required.ts
@melloware melloware added this to the 8.24.0 milestone Aug 3, 2026
@melloware melloware added the mock Related to mock generation label Aug 3, 2026

@melloware melloware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you address all the AI feedback?

The seen set in collectAllOfRequiredWithDeclared was shared across
sibling members and nesting levels, so the first visit of a target
consumed the guard for the whole traversal. A schema referenced both
inside one member's nested chain and directly as a later sibling was
skipped on the direct visit, losing its required list and defeating the
direct-member exemption:

  Detail:
    allOf:
      - $ref: Wrapper      # Wrapper.allOf: [$ref Constraint]
      - $ref: Constraint   # { required: [id] }

Each member now traverses with a branch-local copy of the set, so the
guard only blocks refs repeating within a single path. Termination is
preserved, and the collected union no longer depends on visit order.
Same pattern as resolvesToObjectLike in resolvers/value.ts.

Addresses the CodeRabbit review on orval-labs#3801.
@federicobartoli

Copy link
Copy Markdown
Contributor Author

Can you address all the AI feedback?

All AI feedback addressed. Copilot's point about the shallow delegation guard was fixed in 2e53962 (recursive target resolution), and CodeRabbit's shared-seen finding is fixed in 533d552 (path-scoped cycle guard, with regression tests for the reported shape). All checks pass locally including the generated-output typecheck.

@melloware
melloware merged commit 92836cf into orval-labs:master Aug 4, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mock Related to mock generation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants