Skip to content

feat(cpp-boost-beast): add OAS 3.1 schema validation - #24760

Open
bold84 wants to merge 23 commits into
OpenAPITools:masterfrom
bold84:cpp-boost-beast-oas31
Open

feat(cpp-boost-beast): add OAS 3.1 schema validation#24760
bold84 wants to merge 23 commits into
OpenAPITools:masterfrom
bold84:cpp-boost-beast-oas31

Conversation

@bold84

@bold84 bold84 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

cpp-boost-beast-client: OAS 3.1 schema validation support

Generates C++ Boost.Beast clients with decode-time validation for OpenAPI 3.1
documents: a densified schema IR, exact numeric lexemes, composition
(oneOf/anyOf/allOf) semantics with CompositionBranchValue tagged
variants, JSON Schema 2020-12 vocabulary support (not, boolean value-schemas,
$dynamicRef/$dynamicAnchor, dependentRequired, contains,
patternProperties, propertyNames, if/then/else, type arrays, and deep
enum/const values), SSE modes, and multipart encoding metadata.

This is a follow-up to #24335, which introduced the Boost.Beast client
generator and its initial OpenAPI 3.1 support. It supersedes the
composition-only draft #24387; that work is incorporated here. Discussion of
the three proposed generator options is tracked in #24761.

What's in this PR

Motivation / parser rationale

OpenAPI 3.1 documents cannot be generated faithfully by the upstream
swagger-parser 2.1.x model layer alone: type: [...] arrays (incl. a literal
"null" member) are lowered away, enum: [] degrades to types=[string],
float-form count bounds (minItems: 1.0) are dropped (getMinItems()==null),
multi-entry dependentRequired maps get their lists merged, and $dynamicRef
siblings on $ref-carrying schemas are lost. Rather than fight the model, the
generator recovers the pristine facts from the raw spec text and re-injects
them as extension-marked channels that only fire when the parser has already
dropped information (see Oas31RawSpecRecovery). Recovery therefore leaves
OAS 3.0 and parser-retained OAS 3.1 facts untouched.

  1. Generator restructuring (behavior-preserving, output byte-identical):
    the 9,216-line CppBoostBeastClientCodegen monolith is split into
    Oas31KeywordScanner, Oas31RawSpecRecovery, Oas31SchemaIrEmitter and
    Oas31CompositionLowering (codegen down to ~3,000 lines). Verified by a
    full battery equivalence run (JSTS 281/281 corpus cases, Gate A 191/191,
    generated-path 39/39, Wave-1-complete 35/35, M-probe 50/50, sample
    determinism, -Werror compilation of all generated TUs) before and after.

  2. Densified schema IR + evaluator: every schema/branch/component is
    flattened into a SchemaNode registry (schema_ir.generated.cpp) and
    validated at decode time by the generated SchemaEvaluator. Numeric
    constraints carry their original lexemes (ExactNumber::parseLexeme /
    setExact) so values > 2^53, decimals like 0.3 and exponent forms validate
    without double-rounding drift. If Boost.JSON cannot represent an instance
    number, validation still uses its exact token and public conversion rejects
    the payload instead of exposing a surrogate or non-JSON value. The runtime
    is rendered per client into <modelNamespace>::detail::schema_validation
    and exposed through CamelCase generated headers (Oas31ExactNumber.h,
    Oas31ExactJson.h, Oas31SchemaIr.h, Oas31DeepEqual.h,
    Oas31Validator.h, and Oas31SchemaRegistry.h), so multiple generated
    clients can coexist in one translation unit without type or ODR collisions.

  3. OAS 3.1 / 2020-12 vocabulary: type arrays (incl. literal "null"),
    boolean value-schemas, not subschemas, deep (array/object) enum/const,
    uniqueItems, min/maxContains + contains, dependentRequired (with
    raw-literal recovery for the parser's list-merge corruption),
    patternProperties/propertyNames, min/maxProperties, $ref resource
    identity + $dynamicRef/$dynamicAnchor scope resolution, annotation
    keywords per 2020-12 §8.2.6 (contentEncoding/contentMediaType/
    contentSchema, $comment shape checks).

  4. Composition semantics: CompositionBranchValue<N, T> tagged variants
    preserve branch identity (dedup, null-collapse, enum-union cases no longer
    blind-collapse to std::string); model-qualified public branch accessors
    avoid cross-model symbol collisions; oneOf enforces exactly one match and
    anyOf at least one; discriminator mappings reorder diagnostics; allOf builds
    flat synthetic models with JSON-value enum/const intersection; optional-
    impossible properties are rejected; unsupported membership assertions fail
    generation closed.

  5. SSE and wire support: sseSchemaMode=representation|jsonEventData plus
    per-operation x-sse-event-data-schema opt-in,
    formatAssertionPolicy=annotation|strict, and
    compileWithValidation=true|false (default true; false compiles validation
    out).

  6. Tests (134 focused tests, all passing):
    Oas31IrComplianceTest, CompositionLoweringTest, ModelApiSurfaceTest,
    Oas31ExactRuntimeTest, CppBoostBeastClientApiCodegenTest,
    DependentRequiredParserRetentionTest, and DynamicRefParserRetentionTest.
    Native C++ runtime cases compile with -Wall -Wextra -Werror and exercise
    exact numbers, composition dispatch, public branch accessors, wire paths,
    and two independently generated clients in one translation unit.

  7. Docs & samples: generator page regenerated (all three new CLI options
    documented with defaults); regenerated petstore sample included.

Compatibility

  • OAS 3.0 inputs retain their existing model and API surface while gaining the
    generated validation support files and decode-time validation path.
  • Public model/response/SSE conversion now rejects numeric instances that
    Boost.JSON can only represent as a non-finite value or surrogate; exact schema
    validation still evaluates the original token.
  • Generated CMake now requires C++17 (was C++11).
  • Internal x-oas31-*/x-cpp-* engine channels never appear in generated
    output (regression-tested); the user-facing x-sse-event-data-schema is
    documented in the generator page.

Verification

  • Focused Java/native suite: 134 tests, 0 failures, 0 errors, 0 skips.
  • Full generated-path JSTS slice: 281/281 cases pass.
  • Petstore sample regeneration is byte-stable and its CMake target builds.
  • checkstyle:check and the module verify/forbidden-apis gates pass.
  • Earlier restructuring equivalence gates also passed: Gate A 191/191,
    generated-path 39/39, Wave-1-complete 35/35, and M-probe 50/50.

PR checklist

bold84 added 3 commits August 23, 2026 11:44
Generates a compile-time-validating C++ Boost.Beast client for OpenAPI 3.1 documents:
densified schema IR, exact numeric lexemes, composition (oneOf/anyOf/allOf)
semantics with `CompositionBranchValue` tagged variants, JSON-Schema-2020-12
vocabulary support (`not`, boolean value-schemas, `$dynamicRef`/`$dynamicAnchor`,
`dependentRequired`, `contains`, `patternProperties`, `propertyNames`,
`if`/`then`/`else`, type arrays, deep enum/const stores), SSE (text/event-stream)
modes, multipart encoding metadata, and decode-time validation of every branch.

## What's in this PR

1. **Generator restructuring** (behavior-preserving, output byte-identical):
   the 9,216-line `CppBoostBeastClientCodegen` monolith is split into
   `Oas31KeywordScanner`, `Oas31RawSpecRecovery`, `Oas31SchemaIrEmitter` and
   `Oas31CompositionLowering` (codegen down to ~3,700 lines). Verified by a
   full battery equivalence run (JSTS 281/281 corpus cases, Gate A 191/191,
   generated-path 39/39, Wave-1-complete 35/35, M-probe 50/50, sample
   determinism, `-Werror` compilation of all generated TUs) before and after.

2. **Densified schema IR + evaluator**: every schema/branch/component is
   flattened into a `SchemaNode` registry (`schema_ir.generated.cpp`) and
   validated at decode time by the generated `SchemaEvaluator`. Numeric
   constraints carry their original lexemes (`ExactNumber::parseLexeme` /
   `setExact`) so values > 2^53, decimals like 0.3 and exponent forms
   reconstruct exactly — no double-rounding drift.

3. **OAS 3.1 / 2020-12 vocabulary**: type arrays (incl. literal `"null"`),
   boolean value-schemas, `not` subschemas, deep (array/object) enum/const,
   `uniqueItems`, `min/maxContains` + `contains`, `dependentRequired` (with
   raw-literal recovery for the parser's list-merge corruption),
   `patternProperties`/`propertyNames`, `min/maxProperties`, `$ref` resource
   identity + `$dynamicRef`/`$dynamicAnchor` scope resolution, annotation
   keywords per 2020-12 §8.2.6 (`contentEncoding`/`contentMediaType`/
   `contentSchema`, `$comment` shape checks).

4. **Composition semantics**: `CompositionBranchValue<N, T>` tagged variants
   preserve branch identity (dedup, null-collapse, enum-union cases no longer
   blind-collapse to `std::string`); oneOf exactly-one enforcement; anyOf
   at-least-one; discriminator-based branch reordering for diagnostics;
   allOf flat synthetic models with enum intersection; optional-impossible
   property rejection; fail-closed generation only where a membership-affecting
   assertion has no generated validator.

5. **SSE and wire support**: `sseSchemaMode=representation|jsonEventData` +
   `x-sse-event-data-schema` per-operation opt-in, `formatAssertionPolicy=
   annotation|strict`, `compileWithValidation=true|false` (default true —
   decode-time validation is the feature; disable to compile it out).

6. **Tests** (120, all passing): test suite split into
   `Oas31IrComplianceTest` (34) / `CompositionLoweringTest` (46) /
   `ModelApiSurfaceTest` (33) + `CppBoostBeastTestSupport`; fixtures relocated
@bold84
bold84 force-pushed the cpp-boost-beast-oas31 branch from 41628ea to 3081673 Compare August 23, 2026 10:10
@bold84
bold84 force-pushed the cpp-boost-beast-oas31 branch from 3081673 to a6d4582 Compare August 23, 2026 10:22
@bold84
bold84 marked this pull request as ready for review August 24, 2026 22:53

@cubic-dev-ai cubic-dev-ai 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.

40 issues found across 137 files

Not reviewed (too large): modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppBoostBeastClientCodegen.java (~3,172 lines), modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/CompositionLoweringTest.java (~2,959 lines), modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31SchemaIrEmitter.java (~2,774 lines), modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/Oas31IrComplianceTest.java (~1,998 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:312">
P1: When a 3.1 schema contains multiple composition keywords, this `else-if` records only the first one and drops the other constraint from descriptor-driven validation and lowering. Track each composition keyword independently, then apply all of them to the schema.</violation>

<violation number="2" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:499">
P1: When an allOf branch contains an unsupported assertion, this exemption lets generation proceed without preserving that assertion. Remove the allOf exemption and fail closed, or emit a validator for every unsupported membership assertion before lowering.</violation>

<violation number="3" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:624">
P1: When an allOf contributor closes the object, this discarded value makes the flattened schema treat additional properties as unconstrained. Merge the strictest additionalProperties schema or false value into the synthetic schema.</violation>

<violation number="4" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:683">
P1: For a valid branch such as `{ "type": "number", "exclusiveMinimum": 5 }`, this block skips the bound entirely. Track numeric exclusive bound values separately and intersect them with inclusive bounds before building the synthetic schema.</violation>

<violation number="5" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:890">
P2: The `visited` parameter of intersectPropertySchemas is never consulted or mutated, so the recursive property intersection has no cycle guard. intersectPropertySchemas resolves both operands with ModelUtils.getReferencedSchema and then recurses into nested `getProperties()` (the call at the merged-put site passes `visited` straight through). Unlike resolveAllOfBranch / computeAllOfIntersection, which guard against re-entering a schema name, this path will recurse unboundedly when two intersected object schemas share a cyclic property schema (e.g. a self-referential tree property), risking StackOverflowError during allOf lowering. Either thread the visit-set through (mark/resolve each schema name before descending) or drop the dead parameter; currently the parameter silently implies protection that does not exist.</violation>

<violation number="6" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:898">
P2: When an allOf property uses `$ref` with sibling keywords, this replacement drops the siblings and can produce an incorrect flattened property intersection. Resolve the target while retaining and intersecting the original `$ref` node's sibling assertions.</violation>

<violation number="7" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java:1628">
P2: When a composition has multiple ALWAYS-null branches plus exactly one distinct non-null type (e.g. anyOf [null, null, T]), the nullability is silently dropped. Rule 1 only fires for a 2-branch [T, null] composition; Rule 3 removes all ALWAYS-null branches; then Rule 7 returns early with `deduped.get(0)`, so the null restoration intended by Rule 8 (`hasNull && !nullsAlreadyPreserved`) never runs. The result is a plain `T` C++ type that cannot represent a legitimately-null value, changing decode behavior for an anyOf that allows null. The Rule 8 comment claims "every null surviving to this point must be restored", but Rule 7 returns before that restoration.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/http-client-header.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/http-client-header.mustache:47">
P1: Every normal SSE response reads `streamCancelled` before initializing it, because `HttpResponseData` leaves its scalar members indeterminate. Initialize the status and boolean members in the struct so `streamBody` starts with a valid response state.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/MultipartWireTest.cpp.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/MultipartWireTest.cpp.mustache:81">
P2: This executable never exercises the generated API serializer because it compiles the duplicate defined in this file. Move the serializer into a shared generated helper or link the test against the generated implementation.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/MultipartWireTest.cpp.mustache:141">
P2: When this optional test is built in Release mode, `NDEBUG` disables every validation and CTest can pass on invalid multipart output. Use always-on test failures or explicitly keep assertions enabled for this target.</violation>

<violation number="3" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/MultipartWireTest.cpp.mustache:141">
P2: These assertions do not verify the per-part mapping promised by the test. Parse the multipart body into parts and assert each part's `Content-Disposition`, `Content-Type`, and payload together.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/model-source.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/model-source.mustache:81">
P1: When a composed branch is a cyclic model reference (`std::shared_ptr<T>`), variant serialization falls through to `boost::json::value_from(v)`, which does not use the generated model conversion. Add pointer handling to `VariantJsonHelper` and serialize null or `*v` through the model converter.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/model-source.mustache:288">
P1: When a composed branch lowers to `std::shared_ptr<T>`, `tryParseBranch` cannot decode it because the fallback only recognizes model objects with `fromJsonValue`. Add a shared-pointer branch that accepts null and delegates non-null values to the pointed-to model converter.</violation>

<violation number="3" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/model-source.mustache:531">
P2: With compileWithValidation=false, duplicate-type (x-cpp-has-duplicate-types) variant models always convert branch 0. matchedBranchIndex is forced to 0, so convertMatchedBranch() attempts only branch 0 and throws "Branch 0 conversion failed" when the JSON actually matches a different branch. Select the first parseable branch (like the non-duplicate tryVariantBranches path) instead of hard-coding branch 0.</violation>

<violation number="4" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/model-source.mustache:1501">
P2: For optional const properties, this unconditional assignment emits the key even when the input omitted it. Track const-property presence or emit the inline const only for required properties so optional fields retain omit-versus-present wire semantics.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-header.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-header.mustache:49">
P1: When a response-union operation also declares a non-union response after its successful responses, this filtered loop emits a trailing comma in `std::variant`. Render commas based on the filtered union members, for example by supplying a union-specific last marker from the assembler.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-source.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-source.mustache:445">
P1: When an operation uses a valid relative server URL without a leading slash, the generated request target is malformed (`api/pets`). Resolve relative server URLs to an origin-form path before concatenating the operation path.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-source.mustache:1194">
P1: When a response `oneOf` or `anyOf` contains a `float` branch, variant decoding rejects every valid float value. Add an explicit float conversion path before the model fallback.</violation>
</file>

<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppBoostBeastTemplateModelAssembler.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppBoostBeastTemplateModelAssembler.java:282">
P2: When an operation or path explicitly declares `servers: [{ url: "/" }]`, this code treats it as parser-injected and falls through to the parent server. Preserve the source-level distinction between an omitted server list and an explicit `/` override before applying the fallback.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/oas31_validator.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/oas31_validator.mustache:814">
P2: A valid pure-`$ref` chain longer than 16 hops cannot reach its dynamic anchor, so `$dynamicRef` falls back to the wrong static schema. Replace the fixed hop limit with cycle detection.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/oas31_validator.mustache:1032">
P2: When an `if` guard succeeds, its annotations and evaluated-property coverage leak into the enclosing result. Evaluate the guard in a branch and roll it back before evaluating the selected branch.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/validation-types.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/validation-types.mustache:133">
P2: When a `boost::json::value` contains NaN or infinity, `checkJsonType(..., "number")` accepts it as a JSON number. Require `std::isfinite(v.as_double())` for the double case.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/validation-types.mustache:343">
P2: When a schema pattern matches only a substring, `validatePattern` rejects valid instances because `std::regex_match` requires the entire string. Use `std::regex_search` to implement JSON Schema’s unanchored pattern semantics.</violation>
</file>

<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31KeywordScanner.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31KeywordScanner.java:155">
P2: When an unsupported version such as `3.10.0` reaches this method, the prefix check misclassifies it as OAS 3.1 and applies the wrong default dialect. Match the OAS minor version exactly before selecting OAS 3.1 behavior.

(Based on your team's feedback about exclusion-based version checks.)</violation>

<violation number="2" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31KeywordScanner.java:529">
P2: A valid schema nested beyond 1024 levels is silently skipped, allowing fail-closed keywords below that point to evade `validateDialectPolicy`. Reject excessive nesting instead of returning as though the subtree was scanned.</violation>

<violation number="3" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31KeywordScanner.java:589">
P2: For a valid `enum: []`, this condition suppresses the keyword occurrence entirely. Record every non-null enum, including an empty list, so the ledger remains exhaustive.</violation>
</file>

<file name="modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/fixtures/composition-fixtures.yaml">

<violation number="1" location="modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/fixtures/composition-fixtures.yaml:452">
P3: The OneOfStringStringEnum description says to type-erase to boost::json::value, but the tests for this same fixture assert the opposite: the generator produces std::variant<CompositionBranchValue<0, std::string>, CompositionBranchValue<1, std::string>>, and oneOfStringStringEnumViaGateFixtures explicitly fails if the model is boost::json::value. Update the description to state the actual branch-preserving behavior so it does not misdocument the expected output.</violation>

<violation number="2" location="modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/fixtures/composition-fixtures.yaml:754">
P2: The AnyOfEnumUnion description documents an expected behavior that is both wrong and semantically unsound, and it contradicts the tests written for this fixture. Collapsing [enum red] ∪ [enum blue] to std::string would accept any string (e.g. "green"), which violates the schema, so the "collapse is sound" reasoning is false (unlike AnyOfStringStringEnum, whose plain string branch genuinely accepts all strings). The generator in fact produces a CompositionBranchValue variant that preserves per-branch enum validators, as the tests assert. Fix the description so it does not instruct a sound collapse to std::string.</violation>

<violation number="3" location="modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/fixtures/composition-fixtures.yaml:771">
P3: The AllNullAnyOf description says it should produce boost::json::value, but the tests for this fixture assert it must be CompositionBranchValue<0, std::nullptr_t> to preserve null cardinality. Update the description to match the actual branch-preserving lowering.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/NullableField.h.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/NullableField.h.mustache:63">
P2: For an optional nullable property with a schema default, passing `getField()` back to the generated setter does not mark the field present because the wrapper preserves `missing_ == true`; serialization therefore still omits it. Add a distinct present-value/null promotion in the setter path while preserving missing state for default initialization.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/CMakeLists.txt.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/CMakeLists.txt.mustache:104">
P2: When the generated client is consumed through `add_subdirectory`, sibling targets may not be able to resolve the newly PUBLIC `Boost::json` imported target. Promote the Boost imported targets to `IMPORTED_GLOBAL` (or avoid exposing a directory-scoped target) before publishing this interface.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/oas31_exact_json.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/oas31_exact_json.mustache:307">
P2: When `payload` contains malformed numeric syntax such as `1.2.3`, `captureAndSanitizeNumbers` can turn it into valid JSON and this fallback returns it. Retry the sanitized payload only after proving the original error was numeric-representation overflow; otherwise propagate the original parse error.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/CppBoostBeastTestSupport.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/CppBoostBeastTestSupport.java:64">
P2: `extractMethod` and `countOccurrences` here duplicate existing helpers: `TestUtils.countOccurrences` (TestUtils.java:188) and the private `extractMethod`/`countOccurrences` that `CppBoostBeastClientApiCodegenTest.java` still defines at lines 311-329. The new copies drift in parallel with the old ones; reuse the shared helpers (or migrate the API test to this class) instead of adding a third implementation.</violation>
</file>

<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31RawSpecRecovery.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31RawSpecRecovery.java:98">
P2: When a tuple schema omits `type: array`, this guard prevents recovery of its `prefixItems`. Recover based on the presence of raw `prefixItems`, because array keywords remain valid and meaningful when the schema type is omitted.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/DependentRequiredParserRetentionTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/DependentRequiredParserRetentionTest.java:61">
P2: This regression pin never detects the parser fix it is meant to catch, so it passes whether or not swagger-parser merges the dependentRequired lists. The fixture keys are "foo\nbar"->["req\rA"] and "foo'bar"->["req'B"], and the test accumulates seenCr from one list and seenQuote from the other. When the parser is fixed (lists stay separate), the first still sets seenCr and the second sets seenQuote, so `assertTrue(seenCr && seenQuote)` passes; the MERGED-union corruption is not distinguished from correct behavior. Assert within one list instead that the first trigger's list also contains the second trigger's member (and/or that both entries share the same list object), per the corruption signals in the comment.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/DynamicRefParserRetentionTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/DynamicRefParserRetentionTest.java:28">
P3: The test claims to "prove what swagger-parser retains for $dynamicRef / $dynamicAnchor / $anchor / embedded $id", and its spec defines $defs.foo with $dynamicAnchor and $defs.bar with $anchor/$id, but none of those are ever asserted. Only items.get$dynamicRef() and the x-oas31-resource extension are checked. If swagger-parser 2.1.47 silently drops $dynamicAnchor/$anchor/$id, this test still passes, so the retention property it advertises (and which Oas31SchemaIrEmitter relies on via get$dynamicAnchor) is untested and the javadoc overstates coverage.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-operation-source.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/cpp-boost-beast-client/api-operation-source.mustache:199">
P2: In `sseSchemaMode=jsonEventData`, the generated SSE callback returns `true` for a `[DONE]` event, which tells `SseEventFramer` to keep streaming (`if (!m_onEvent(event)) m_cancelled = true;` in http-client-impl-source.mustache). The README documents `[DONE]` as a terminator ("consumed as terminator before decoding") and that returning `false` cancels the stream cooperatively, so after `[DONE]` the client should stop reading. As written, a server that sends `[DONE]` but keeps the connection open leaves the client waiting indefinitely for further events. Return `false` for `[DONE]` to terminate the stream.</violation>
</file>

<file name="modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/nullable-object-regression.yaml">

<violation number="1" location="modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/nullable-object-regression.yaml:3">
P3: This new spec is never wired to a test: no test references the 3_1/cpp-boost-beast-client/nullable-object-regression.yaml path, and none of the cpp-boost-beast tests load resources by directory listing. The only test exercising a nullable object (generatesOas30NullableObject) reads the 3_0 copy instead. Either add a test that consumes this fixture or remove it, so the regression it documents is actually verified.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/ModelApiSurfaceTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/ModelApiSurfaceTest.java:303">
P3: These comments claim the generator is broken ('CURRENT BROKEN BEHAVIOUR ... emits only an IsSet flag' / 'does NOT produce a status-aware response union') yet the very next assertions require the fixed behavior (NullableField, response-union struct). Update or drop the stale comments so they match what the tests actually verify.</violation>

<violation number="2" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/ModelApiSurfaceTest.java:1332">
P2: Files.createTempDirectory(Path.of("target"), ...) throws NoSuchFileException when no 'target' directory exists. The other test here (callbacksAndResponseLinksArePreservedFromParsedModel) explicitly creates target first; these two sites rely on it existing by chance. Create the directory (or use the system temp dir) before creating the temp subdir.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

// assertions don't change match count.
if ("not".equals(unsupported)) {
// `not` always fails generation regardless of keyword
} else if ("allOf".equals(desc.getKeyword())) {

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.

P1: When an allOf branch contains an unsupported assertion, this exemption lets generation proceed without preserving that assertion. Remove the allOf exemption and fail closed, or emit a validator for every unsupported membership assertion before lowering.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java, line 499:

<comment>When an allOf branch contains an unsupported assertion, this exemption lets generation proceed without preserving that assertion. Remove the allOf exemption and fail closed, or emit a validator for every unsupported membership assertion before lowering.</comment>

<file context>
@@ -0,0 +1,1645 @@
+                // assertions don't change match count.
+                if ("not".equals(unsupported)) {
+                    // `not` always fails generation regardless of keyword
+                } else if ("allOf".equals(desc.getKeyword())) {
+                    continue; // non-not unsupported assertions exempted for allOf
+                }
</file context>

List<Schema> branchSchemas = null;
String keyword = null;

if (schema.getOneOf() != null && !schema.getOneOf().isEmpty()) {

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.

P1: When a 3.1 schema contains multiple composition keywords, this else-if records only the first one and drops the other constraint from descriptor-driven validation and lowering. Track each composition keyword independently, then apply all of them to the schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java, line 312:

<comment>When a 3.1 schema contains multiple composition keywords, this `else-if` records only the first one and drops the other constraint from descriptor-driven validation and lowering. Track each composition keyword independently, then apply all of them to the schema.</comment>

<file context>
@@ -0,0 +1,1645 @@
+        List<Schema> branchSchemas = null;
+        String keyword = null;
+
+        if (schema.getOneOf() != null && !schema.getOneOf().isEmpty()) {
+            branchSchemas = schema.getOneOf();
+            keyword = "oneOf";
</file context>

Comment on lines +47 to +51
boost::beast::http::status status;
std::map<std::string, std::string> headers;
std::string body;
bool isEventStream = false;
bool streamCancelled = false;

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.

P1: Every normal SSE response reads streamCancelled before initializing it, because HttpResponseData leaves its scalar members indeterminate. Initialize the status and boolean members in the struct so streamBody starts with a valid response state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/cpp-boost-beast-client/http-client-header.mustache, line 47:

<comment>Every normal SSE response reads `streamCancelled` before initializing it, because `HttpResponseData` leaves its scalar members indeterminate. Initialize the status and boolean members in the struct so `streamBody` starts with a valid response state.</comment>

<file context>
@@ -1,17 +1,57 @@
+
+/// Rich HTTP response containing status, response headers, and body.
+struct HttpResponseData {
+    boost::beast::http::status status;
+    std::map<std::string, std::string> headers;
+    std::string body;
</file context>
Suggested change
boost::beast::http::status status;
std::map<std::string, std::string> headers;
std::string body;
bool isEventStream = false;
bool streamCancelled = false;
boost::beast::http::status status = boost::beast::http::status::unknown;
std::map<std::string, std::string> headers;
std::string body;
bool isEventStream = false;
bool streamCancelled = false;

}

// Intersect numeric bounds: minimum/maximum take tighter range
if (resolvedBranch.getMinimum() != null) {

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.

P1: For a valid branch such as { "type": "number", "exclusiveMinimum": 5 }, this block skips the bound entirely. Track numeric exclusive bound values separately and intersect them with inclusive bounds before building the synthetic schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java, line 683:

<comment>For a valid branch such as `{ "type": "number", "exclusiveMinimum": 5 }`, this block skips the bound entirely. Track numeric exclusive bound values separately and intersect them with inclusive bounds before building the synthetic schema.</comment>

<file context>
@@ -0,0 +1,1645 @@
+                }
+
+                // Intersect numeric bounds: minimum/maximum take tighter range
+                if (resolvedBranch.getMinimum() != null) {
+                    hasRootScalarConstraints = true;
+                    BigDecimal branchMin = resolvedBranch.getMinimum();
</file context>

// result is closed. If multiple contributors constrain the
// additional properties schema, use the stricter intersection.
// Preserve the strictest additionalProperties constraint available.
Object branchAddProps = resolvedBranch.getAdditionalProperties();

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.

P1: When an allOf contributor closes the object, this discarded value makes the flattened schema treat additional properties as unconstrained. Merge the strictest additionalProperties schema or false value into the synthetic schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31CompositionLowering.java, line 624:

<comment>When an allOf contributor closes the object, this discarded value makes the flattened schema treat additional properties as unconstrained. Merge the strictest additionalProperties schema or false value into the synthetic schema.</comment>

<file context>
@@ -0,0 +1,1645 @@
+            // result is closed. If multiple contributors constrain the
+            // additional properties schema, use the stricter intersection.
+            // Preserve the strictest additionalProperties constraint available.
+            Object branchAddProps = resolvedBranch.getAdditionalProperties();
+            if (branchAddProps != null) {
+                // Track the constraint — but we don't currently produce a
</file context>

AllNullAnyOf:
description: >
anyOf over two null branches. The only valid value is null.
Should produce boost::json::value since all branches are type void.

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.

P3: The AllNullAnyOf description says it should produce boost::json::value, but the tests for this fixture assert it must be CompositionBranchValue<0, std::nullptr_t> to preserve null cardinality. Update the description to match the actual branch-preserving lowering.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/fixtures/composition-fixtures.yaml, line 771:

<comment>The AllNullAnyOf description says it should produce boost::json::value, but the tests for this fixture assert it must be CompositionBranchValue<0, std::nullptr_t> to preserve null cardinality. Update the description to match the actual branch-preserving lowering.</comment>

<file context>
@@ -0,0 +1,902 @@
+    AllNullAnyOf:
+      description: >
+        anyOf over two null branches.  The only valid value is null.
+        Should produce boost::json::value since all branches are type void.
+      anyOf:
+        - type: "null"
</file context>
Suggested change
Should produce boost::json::value since all branches are type void.
+ Should lower as CompositionBranchValue<0, std::nullptr_t> to preserve
+ null branch identity.

Comment on lines +452 to +454
enum members match both branches (invalid oneOf). Emit boost::json::value
instead of a false exclusive typed union.
oneOf:

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.

P3: The OneOfStringStringEnum description says to type-erase to boost::json::value, but the tests for this same fixture assert the opposite: the generator produces std::variant<CompositionBranchValue<0, std::string>, CompositionBranchValue<1, std::string>>, and oneOfStringStringEnumViaGateFixtures explicitly fails if the model is boost::json::value. Update the description to state the actual branch-preserving behavior so it does not misdocument the expected output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/fixtures/composition-fixtures.yaml, line 452:

<comment>The OneOfStringStringEnum description says to type-erase to boost::json::value, but the tests for this same fixture assert the opposite: the generator produces std::variant<CompositionBranchValue<0, std::string>, CompositionBranchValue<1, std::string>>, and oneOfStringStringEnumViaGateFixtures explicitly fails if the model is boost::json::value. Update the description to state the actual branch-preserving behavior so it does not misdocument the expected output.</comment>

<file context>
@@ -0,0 +1,902 @@
+        oneOf over a plain string and a string enum.  The generator MUST NOT
+        apply the anyOf-only string+enum collapse.  std::variant<std::string,
+        std::string> is invalid C++, and collapsing to std::string hides that
+        enum members match both branches (invalid oneOf).  Emit boost::json::value
+        instead of a false exclusive typed union.
+      oneOf:
</file context>
Suggested change
enum members match both branches (invalid oneOf). Emit boost::json::value
instead of a false exclusive typed union.
oneOf:
+ enum members match both branches (invalid oneOf). Lower as a
+ CompositionBranchValue variant to preserve branch identity instead of
+ type-erasing to boost::json::value.

+ " \"items\": {\"$dynamicRef\": \"#items\"},\n"
+ " \"x-oas31-resource\": 7,\n"
+ " \"$defs\": {\n"
+ " \"foo\": {\"$dynamicAnchor\": \"items\", \"type\": \"string\"},\n"

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.

P3: The test claims to "prove what swagger-parser retains for $dynamicRef / $dynamicAnchor / $anchor / embedded $id", and its spec defines $defs.foo with $dynamicAnchor and $defs.bar with $anchor/$id, but none of those are ever asserted. Only items.get$dynamicRef() and the x-oas31-resource extension are checked. If swagger-parser 2.1.47 silently drops $dynamicAnchor/$anchor/$id, this test still passes, so the retention property it advertises (and which Oas31SchemaIrEmitter relies on via get$dynamicAnchor) is untested and the javadoc overstates coverage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/DynamicRefParserRetentionTest.java, line 28:

<comment>The test claims to "prove what swagger-parser retains for $dynamicRef / $dynamicAnchor / $anchor / embedded $id", and its spec defines $defs.foo with $dynamicAnchor and $defs.bar with $anchor/$id, but none of those are ever asserted. Only items.get$dynamicRef() and the x-oas31-resource extension are checked. If swagger-parser 2.1.47 silently drops $dynamicAnchor/$anchor/$id, this test still passes, so the retention property it advertises (and which Oas31SchemaIrEmitter relies on via get$dynamicAnchor) is untested and the javadoc overstates coverage.</comment>

<file context>
@@ -0,0 +1,93 @@
+            + "      \"items\": {\"$dynamicRef\": \"#items\"},\n"
+            + "      \"x-oas31-resource\": 7,\n"
+            + "      \"$defs\": {\n"
+            + "        \"foo\": {\"$dynamicAnchor\": \"items\", \"type\": \"string\"},\n"
+            + "        \"bar\": {\"$anchor\": \"plain\", \"$id\": \"urn:embedded\", \"type\": \"number\"}\n"
+            + "      }\n"
</file context>

@@ -0,0 +1,25 @@
openapi: 3.0.3
info:
title: Nullable object regression

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.

P3: This new spec is never wired to a test: no test references the 3_1/cpp-boost-beast-client/nullable-object-regression.yaml path, and none of the cpp-boost-beast tests load resources by directory listing. The only test exercising a nullable object (generatesOas30NullableObject) reads the 3_0 copy instead. Either add a test that consumes this fixture or remove it, so the regression it documents is actually verified.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/nullable-object-regression.yaml, line 3:

<comment>This new spec is never wired to a test: no test references the 3_1/cpp-boost-beast-client/nullable-object-regression.yaml path, and none of the cpp-boost-beast tests load resources by directory listing. The only test exercising a nullable object (generatesOas30NullableObject) reads the 3_0 copy instead. Either add a test that consumes this fixture or remove it, so the regression it documents is actually verified.</comment>

<file context>
@@ -0,0 +1,25 @@
+openapi: 3.0.3
+info:
+  title: Nullable object regression
+  version: 1.0.0
+paths:
</file context>

public void generatesOptionalNullableTriState() throws IOException {
// Optional nullable property must preserve missing, null, and value.
// The tri-state requires a Nullable<T>-like field wrapper, not just an IsSet bool.
// CURRENT BROKEN BEHAVIOUR: the generator emits only an IsSet flag, which cannot

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.

P3: These comments claim the generator is broken ('CURRENT BROKEN BEHAVIOUR ... emits only an IsSet flag' / 'does NOT produce a status-aware response union') yet the very next assertions require the fixed behavior (NullableField, response-union struct). Update or drop the stale comments so they match what the tests actually verify.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/cppboostbeast/ModelApiSurfaceTest.java, line 303:

<comment>These comments claim the generator is broken ('CURRENT BROKEN BEHAVIOUR ... emits only an IsSet flag' / 'does NOT produce a status-aware response union') yet the very next assertions require the fixed behavior (NullableField, response-union struct). Update or drop the stale comments so they match what the tests actually verify.</comment>

<file context>
@@ -0,0 +1,1624 @@
+    public void generatesOptionalNullableTriState() throws IOException {
+        // Optional nullable property must preserve missing, null, and value.
+        // The tri-state requires a Nullable<T>-like field wrapper, not just an IsSet bool.
+        // CURRENT BROKEN BEHAVIOUR: the generator emits only an IsSet flag, which cannot
+        // distinguish missing from explicit null.  This test locks the tri-state gap.
+        File output = java.nio.file.Files.createTempDirectory("cpp-boost-beast-tri-state").toFile();
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant