Skip to content

feat: Ability to limit combinations#3344

Open
RobinTail wants to merge 49 commits intomasterfrom
limit-combinations
Open

feat: Ability to limit combinations#3344
RobinTail wants to merge 49 commits intomasterfrom
limit-combinations

Conversation

@RobinTail
Copy link
Copy Markdown
Owner

@RobinTail RobinTail commented Apr 24, 2026

Summary by CodeRabbit

  • New Features

    • Adds a helper to extract security header names; familiar/security inputs now use Sets.
    • Documentation generator gains a configurable maxCombinations option to bound example generation.
  • Refactor

    • Reworked combination and example-merging to support a limit and stop early to avoid unbounded cross-products.
    • Flattening/depiction flows now propagate the combinations limit.
  • API Changes

    • Endpoint/documentation surfaces accept and forward a maxCombinations/limit option.
  • Tests

    • Expanded coverage for bounded combinations, zero/negative/NaN limits, callback signature change, and related snapshots.
  • Changelog

    • New v27.3.0 entry describing maxCombinations behavior.

@RobinTail RobinTail added the enhancement New feature or request label Apr 24, 2026
@coveralls-official
Copy link
Copy Markdown

coveralls-official Bot commented Apr 24, 2026

Coverage Status

coverage: 100.0%. remained the same — limit-combinations into master

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c8f33506-378d-42c6-8988-0a3592391a3e

📥 Commits

Reviewing files that changed from the base of the PR and between ba2f6dd and 9251a67.

📒 Files selected for processing (1)
  • CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

Threads an optional maxCombinations/limit cap through combination generation, example merging, schema flattening, logical-container processing, endpoint APIs, documentation, integration, and diagnostics; reworks combinations to use a two-arg merge callback and an optional limit to bound Cartesian outputs.

Changes

Combination + Example Bounding

Layer / File(s) Summary
Data Shape / Options
express-zod-api/src/documentation.ts, express-zod-api/src/documentation-helpers.ts
Add optional maxCombinations?: number to DocumentationParams and ReqResCommons and thread it into depiction/context.
Core Combiner
express-zod-api/src/common-helpers.ts
Reimplemented combinations signature to <T>(left: T[], right: T[], merge: (a: T, b: T) => T, limit = Infinity) => T[]; handles empty sides, non-positive/NaN limits, and enforces limit during nested-loop Cartesian generation.
Schema Flattening / Example Merge
express-zod-api/src/json-schema-helpers.ts, express-zod-api/src/result-helpers.ts
Replace MergeMode with options objects; processAllOf, flattenIO, mergeExamples, pullRequestExamples, and pullResponseExamples accept/forward { isStrict?, maxCombinations? } or limit and call combinations(..., limit) to bound outputs.
Logical Containers
express-zod-api/src/logical-container.ts
processContainers<T>(..., maxCombinations?) forwards maxCombinations into combinations, uses R.concat as merge, and types the accumulated combinations.
Security helpers / Header detection
express-zod-api/src/security.ts
Add export const getSecurityHeaders(containers: LogicalContainer<Security>[]): Set<string> to extract header-named security schemes from logical containers.
Endpoint API & Examples
express-zod-api/src/endpoint.ts
Extend AbstractEndpoint.getResponses / Endpoint.getResponses to accept { maxCombinations?: number }; refactor output-example generation to #ensureOutputExamples(limit?) that forwards the limit into example pulling.
Documentation Flow
express-zod-api/src/documentation-helpers.ts, express-zod-api/src/documentation.ts
Thread maxCombinations into depiction/flattening calls; defaultIsHeader now accepts a Set<string>; request security now sources header names via getSecurityHeaders.
Integration & Diagnostics
express-zod-api/src/integration.ts, express-zod-api/src/diagnostics.ts
Call endpoint.getResponses(..., { maxCombinations: 0 }) and pass { maxCombinations: 0 } into flattenIO in diagnostics to avoid expanding example combinations during those flows.
Tests & Changelog
express-zod-api/tests/*.spec.ts, CHANGELOG.md
Update tests for combinations new callback shape and limit behavior; add tests for maxCombinations in logical containers, result helpers, and documentation helpers; add v27.3.0 changelog entry documenting maxCombinations behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Docs as "Documentation"  rect rgba(63,81,181,0.5)
    participant Endpoint as "Endpoint"  rect rgba(3,169,244,0.5)
    participant JSON as "json-schema-helpers"  rect rgba(0,150,136,0.5)
    participant Comb as "combinations"  rect rgba(255,193,7,0.5)

    Docs->>Endpoint: getResponses(variant, { maxCombinations })
    Endpoint->>Endpoint: ensureOutputExamples(limit = maxCombinations)
    Endpoint->>JSON: pullResponseExamples(..., limit)
    JSON->>Comb: combinations(left, right, merge(a, b), limit)
    Comb-->>JSON: merged (possibly truncated) examples
    JSON-->>Endpoint: examples
    Endpoint-->>Docs: responses with bounded examples
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

documentation, breaking

Poem

🐇 I hopped through nested loops with care,
Paired bits and trimmed the output spare,
When combos ballooned, I set a cap,
Now docs and tests avoid the trap,
A tiny hop saves runtime air.

🚥 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 accurately describes the main feature added: introducing the ability to limit combinations in cartesian product generation throughout the codebase.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch limit-combinations

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@RobinTail RobinTail force-pushed the limit-combinations branch from 94693b9 to 120fa0d Compare April 24, 2026 09:33
RobinTail added a commit that referenced this pull request Apr 24, 2026
…chema` (#3345)

Performance regression since v24.0.0 - the `outputSchema` getter was
calling the expensive `pullResponseExamples()` operation on first access
(R.once protected), even though examples are only needed for OpenAPI
documentation generation (getResponses).

This fixes the issue where simply accessing the output schema for
runtime validation was unnecessarily triggering example generation.

first introduced by #2666 
merged by #2546 
found this during my work on #3344 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved request performance by deferring output schema example
processing; examples are now generated only when needed for
documentation/OpenAPI workflows, not during initial request handling.

* **Documentation**
* Added changelog entry v27.2.4 documenting the change in example
processing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@RobinTail RobinTail marked this pull request as ready for review April 24, 2026 15:20
@RobinTail RobinTail added refactoring The better way to achieve the same result labels Apr 24, 2026
Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Caution

R.once on #ensureOutputExamples silently drops the limit parameter from all callers after the first. In development mode, Diagnostics wins the race with maxCombinations: 0, preventing Documentation from ever generating output examples.

Task list (4/4 completed)
  • Read full diff and understand the feature
  • Trace data flow and check boundaries for each area of change
  • Self-critique drafted comments
  • Submit review

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread express-zod-api/src/endpoint.ts Outdated
Comment thread express-zod-api/src/diagnostics.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@express-zod-api/src/documentation-helpers.ts`:
- Around line 129-133: depictIntersection reads maxCombinations from the
depiction context but depictResponse and depictRequest build ctx without that
field, causing intersections to run unbounded; update the context construction
in depictResponse and depictRequest to include maxCombinations (propagate the
value from the function arguments/config/options into the ctx passed to
depictIntersection and flattenIO) so that flattenIO receives the intended
maxCombinations limit.

In `@express-zod-api/src/documentation.ts`:
- Around line 89-94: The JSDoc for the public constructor option maxCombinations
is missing an `@example`; update the comment block for the maxCombinations
property in documentation.ts to include an `@example` directive showing typical
usage (e.g., setting maxCombinations: 20 or Infinity) while keeping the existing
`@desc` and `@default` lines so the property has `@desc`, `@default`, and `@example` as
required by the public API documentation guidelines.

In `@express-zod-api/src/endpoint.ts`:
- Around line 94-98: Remove the R.once memoization wrapper from the private
methods that generate examples so they can be re-run with different limits:
specifically un-wrap the definitions of `#ensureOutputExamples` and the analogous
`#ensureInputExamples` (the ones currently defined as R.once((limit?: number) => {
... })). Leave the existing guard checks (e.g., the globalRegistry lookup and
isSchema check inside `#ensureOutputExamples`) intact so redundant work is still
avoided, but allow the function body to execute on each call with the provided
limit parameter instead of being permanently memoized.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99c13e7d-1c60-4cf9-8e92-1770c8851336

📥 Commits

Reviewing files that changed from the base of the PR and between 3e7482b and 81f1b76.

📒 Files selected for processing (14)
  • express-zod-api/src/common-helpers.ts
  • express-zod-api/src/diagnostics.ts
  • express-zod-api/src/documentation-helpers.ts
  • express-zod-api/src/documentation.ts
  • express-zod-api/src/endpoint.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/json-schema-helpers.ts
  • express-zod-api/src/logical-container.ts
  • express-zod-api/src/result-helpers.ts
  • express-zod-api/tests/common-helpers.spec.ts
  • express-zod-api/tests/endpoint.spec.ts
  • express-zod-api/tests/json-schema-helpers.spec.ts
  • express-zod-api/tests/logical-container.spec.ts
  • express-zod-api/tests/result-helpers.spec.ts

Comment thread express-zod-api/src/documentation-helpers.ts
Comment thread express-zod-api/src/documentation.ts
Comment thread express-zod-api/src/endpoint.ts Outdated
@RobinTail
Copy link
Copy Markdown
Owner Author

the direct limit of the security schemes may be really confusing. it should only apply to the Cartesian product (multiplication), early exits should be removed too.

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Important

Stale %s placeholder in a test name after converting from test.each to a plain test.

Reviewed the following changes:

  • Narrowed limit semantics in combinations() to apply only to the cartesian product of two non-empty arrays — the concatenation passthrough path (left.concat(right)) is no longer truncated
  • Removed redundant early-return guards from mergeExamples, pullRequestExamples, and pullResponseExamples — these are unnecessary since combinations() already handles 0, negative, and NaN limits correctly via the loop condition result.length < limit
  • Updated JSDoc on maxCombinations to "disables combinations, but keeps concatenations", clarifying the intent that 0 suppresses cross-products without suppressing passthrough concatenation
  • Removed edge-case tests for non-positive/NaN limits in mergeExamples, pullRequestExamples, pullResponseExamples, and simplified the processContainers limit test to a single assertion with limit = 2

Prior review feedback:

  • R.once on #ensureOutputExamples silently drops the limit parameter — addressed in prior commits
  • Propagate maxCombinations into depiction ctx for depictResponse and depictRequest — addressed in prior commits

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread express-zod-api/tests/logical-container.spec.ts Outdated
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
express-zod-api/tests/logical-container.spec.ts (1)

127-137: Consider asserting the returned combinations, not just their count.

toHaveLength(2) confirms truncation occurred but does not verify which combinations survived or that ordering is preserved (i.e., that the cap stops generation deterministically rather than discarding arbitrary results). Given the existing tests in this file already assert the full Cartesian output, asserting the exact truncated prefix would make this test more robust against regressions in the truncation order.

♻️ Suggested stronger assertion
     test("should control the maximum combinations", () => {
       expect(
         processContainers(
           [
             { or: [{ and: [1, 2] }, { and: [3, 4] }] },
             { or: [{ and: [5, 6] }, { and: [7, 8] }] },
           ],
           2,
         ),
-      ).toHaveLength(2);
+      ).toEqual([
+        [1, 2, 5, 6],
+        [1, 2, 7, 8],
+      ]);
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@express-zod-api/tests/logical-container.spec.ts` around lines 127 - 137, The
test "should control the maximum combinations" only checks length; update it to
assert the actual returned combinations from processContainers (not just
toHaveLength) to ensure deterministic truncation and ordering. Replace the
existing length assertion with an equality assertion against the expected
truncated prefix (call processContainers with the same inputs and max=2 and
assert the returned array equals the first two combinations in the deterministic
Cartesian order produced by processContainers) so the test verifies which
combinations survive and that ordering is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@express-zod-api/tests/logical-container.spec.ts`:
- Around line 127-137: The test "should control the maximum combinations" only
checks length; update it to assert the actual returned combinations from
processContainers (not just toHaveLength) to ensure deterministic truncation and
ordering. Replace the existing length assertion with an equality assertion
against the expected truncated prefix (call processContainers with the same
inputs and max=2 and assert the returned array equals the first two combinations
in the deterministic Cartesian order produced by processContainers) so the test
verifies which combinations survive and that ordering is preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e62ed6c2-3d0b-4383-adb3-becc1d3f5695

📥 Commits

Reviewing files that changed from the base of the PR and between 204ac1a and 22f6563.

📒 Files selected for processing (1)
  • express-zod-api/tests/logical-container.spec.ts

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Important

JSDoc indentation on the new maxCombinations property is misaligned with the rest of the interface.

Reviewed changes:

  • Added CHANGELOG entry for v27.3.0 documenting the maxCombinations feature with a code example
  • Rewrote the maxCombinations JSDoc in DocumentationParams — clearer wording distinguishing "product combinations" from "concatenations"

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread express-zod-api/src/documentation.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@express-zod-api/src/documentation.ts`:
- Around line 89-95: The JSDoc block starting with "Limits cartesian product
when generating examples by combining each property's own examples." is
mis-indented and failing Prettier; fix it by adding 2-space indentation to each
line of the comment block so each line aligns with the surrounding code style
(prefix each line with two spaces), preserving all text and annotations (e.g.,
`@desc`, `@example`, `@default`, `@todo`).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18c023d7-21a1-4f4b-8f1b-5e1b780f8d87

📥 Commits

Reviewing files that changed from the base of the PR and between 18e622d and 639cc12.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • express-zod-api/src/documentation.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

Comment thread express-zod-api/src/documentation.ts Outdated
@RobinTail RobinTail force-pushed the limit-combinations branch from 639cc12 to c969f22 Compare April 25, 2026 11:13
@RobinTail RobinTail added the coverage Additional tests label Apr 25, 2026
Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

No new issues. Reviewed the following changes:

  • Removed early-exit guards (if (!(limit > 0)) return []) from mergeExamples, processContainers, pullRequestExamples, and pullResponseExamples — the limit is now enforced solely by the combinations() loop condition (result.length < limit), which naturally handles 0, -1, and NaN
  • Changed combinations() to return left.concat(right) without .slice(0, limit) when one side is empty, making the limit apply exclusively to the Cartesian product of two non-empty arrays — JSDoc updated to reflect this
  • Rearranged pickHeaders to check isLogicalAnd/isLogicalOr first and fall through to the simple leaf case, removing the redundant isSimple guard and fixing coverage of the terminal return []
  • Simplified tests: removed edge-case parameterized tests for 0/-1/NaN from higher-level functions (now covered by combinations() itself), added explicit test for passthrough-without-limit behavior
  • Updated JSDoc for maxCombinations to clarify it limits Cartesian product only ("disables product combinations, keeps concatenations")
  • Added CHANGELOG entry for v27.3.0 with a clear example of the combinatorial explosion this feature addresses

Pullfrog  | View workflow run | Using Claude Opus𝕏

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Apr 25, 2026

No code changes needed — the PR author already fixed the JSDoc indentation on maxCombinations in documentation.ts. Replied to confirm and resolved the thread.

Task list (3/3 completed)
  • Review the comment thread and understand feedback
  • Verify fix is already applied
  • Reply to review comment and resolve thread

Pullfrog  | View workflow run | via Pullfrog | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

No new issues. Reviewed the following changes:

  • Removed early-exit guards from mergeExamples, pullRequestExamples, pullResponseExamples, and processContainers — the limit is now enforced solely by the loop condition in combinations(), and only for actual Cartesian products (both arrays non-empty)
  • Changed combinations() passthrough path (left.concat(right)) to no longer truncate by limit, with updated JSDoc clarifying the narrowed scope: "only applies to Cartesian product of non-empty arrays"
  • Removed the .slice(0, maxCombinations) truncation from mergeExamples's optional/concat branch, consistent with the design that limit targets cross-products only
  • Rearranged pickHeaders to check isLogicalAnd/isLogicalOr first and fall through to the simple container.type === "header" case, fixing coverage for the terminal return []
  • Added CHANGELOG entry for v27.3.0 with a clear description and worked example of the combinatorial explosion the feature addresses
  • Refined maxCombinations JSDoc: more specific wording on what "applies to" means, updated @example to say "disables product combinations, keeps concatenations"
  • Cleaned up tests: removed edge-case parameterized tests for removed early-exit behavior, added "should not apply the limit when returns original array" test, simplified processContainers limit test to single limit=2 case

Pullfrog  | View workflow run | Using Claude Opus𝕏

@RobinTail RobinTail changed the title feat: Limit combinations feat: Ability to limit combinations Apr 25, 2026
Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

No new issues. Reviewed the following changes:

  • Moved pickHeaders and pickSecurityHeaders from logical-container.ts to security.ts and renamed the export to getSecurityHeaders — colocating the helper with the Security types it operates on and removing the Security import from logical-container.ts
  • Updated the documentation.ts import accordingly

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

No new issues. Reviewed the following changes:

  • Added security.spec.ts with unit tests for getSecurityHeaders — covers flat security arrays (filtering to header-type only), empty input, single-level AND/OR containers, and nested AND/OR containers using test.each with %# placeholders

Pullfrog  | View workflow run | Using Claude Opus𝕏

@RobinTail RobinTail force-pushed the limit-combinations branch from 9727140 to 61ef57a Compare April 29, 2026 06:08
@RobinTail
Copy link
Copy Markdown
Owner Author

RobinTail commented Apr 29, 2026

Swagger validator is 502/504

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coverage Additional tests enhancement New feature or request refactoring The better way to achieve the same result

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant