feat(cpp-boost-beast): add OAS 3.1 schema validation - #24760
Conversation
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
41628ea to
3081673
Compare
3081673 to
a6d4582
Compare
There was a problem hiding this comment.
All reported issues were addressed 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.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 38 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 15 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 18 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
2 issues found across 21 files (changes from recent commits).
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/Oas31RawSpecRecovery.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Oas31RawSpecRecovery.java:409">
P1: When `const: null` appears in an `allOf`, this recovered marker is not consulted by composition lowering, so the branch's constraint is omitted from the flattened model. Make composition lowering recognize `hasExplicitConst` and intersect the recovered `constJsonOf` value, including JSON null.</violation>
</file>
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppBoostBeastModelCodegen.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppBoostBeastModelCodegen.java:290">
P1: Nested containers can lose the shared pointer needed by their recursive item. This helper searches any nested pointer while `isCircularReference` only describes the immediate item; strip only when `container.items.dataType` itself starts with `std::shared_ptr<`, or recurse with matching cycle metadata.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| addExtension(parsed, DEFAULT_JSON_EXT, raw.get("default").toString()); | ||
| } | ||
| if (raw.has("const")) { | ||
| addExtension(parsed, CONST_PRESENT_EXT, true); |
There was a problem hiding this comment.
P1: When const: null appears in an allOf, this recovered marker is not consulted by composition lowering, so the branch's constraint is omitted from the flattened model. Make composition lowering recognize hasExplicitConst and intersect the recovered constJsonOf value, including JSON null.
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/Oas31RawSpecRecovery.java, line 409:
<comment>When `const: null` appears in an `allOf`, this recovered marker is not consulted by composition lowering, so the branch's constraint is omitted from the flattened model. Make composition lowering recognize `hasExplicitConst` and intersect the recovered `constJsonOf` value, including JSON null.</comment>
<file context>
@@ -402,6 +405,14 @@ private static void recoverSchema(
addExtension(parsed, DEFAULT_JSON_EXT, raw.get("default").toString());
}
+ if (raw.has("const")) {
+ addExtension(parsed, CONST_PRESENT_EXT, true);
+ addExtension(parsed, CONST_JSON_EXT, raw.get("const").toString());
+ }
</file context>
| * container-level circular-reference flag says the edge is acyclic. | ||
| */ | ||
| private static void stripNonCyclicContainerItemSharedPtr(CodegenProperty container) { | ||
| if (container.isCircularReference || container.dataType == null) { |
There was a problem hiding this comment.
P1: Nested containers can lose the shared pointer needed by their recursive item. This helper searches any nested pointer while isCircularReference only describes the immediate item; strip only when container.items.dataType itself starts with std::shared_ptr<, or recurse with matching cycle metadata.
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/CppBoostBeastModelCodegen.java, line 290:
<comment>Nested containers can lose the shared pointer needed by their recursive item. This helper searches any nested pointer while `isCircularReference` only describes the immediate item; strip only when `container.items.dataType` itself starts with `std::shared_ptr<`, or recurse with matching cycle metadata.</comment>
<file context>
@@ -282,6 +282,45 @@ private static void stripNonCyclicSharedPtr(CodegenProperty var) {
+ * container-level circular-reference flag says the edge is acyclic.
+ */
+ private static void stripNonCyclicContainerItemSharedPtr(CodegenProperty container) {
+ if (container.isCircularReference || container.dataType == null) {
+ return;
+ }
</file context>
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 withCompositionBranchValuetaggedvariants, JSON Schema 2020-12 vocabulary support (
not, boolean value-schemas,$dynamicRef/$dynamicAnchor,dependentRequired,contains,patternProperties,propertyNames,if/then/else, type arrays, and deepenum/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 totypes=[string],float-form count bounds (
minItems: 1.0) are dropped (getMinItems()==null),multi-entry
dependentRequiredmaps get their lists merged, and$dynamicRefsiblings on
$ref-carrying schemas are lost. Rather than fight the model, thegenerator 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 leavesOAS 3.0 and parser-retained OAS 3.1 facts untouched.
Generator restructuring (behavior-preserving, output byte-identical):
the 9,216-line
CppBoostBeastClientCodegenmonolith is split intoOas31KeywordScanner,Oas31RawSpecRecovery,Oas31SchemaIrEmitterandOas31CompositionLowering(codegen down to ~3,000 lines). Verified by afull 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,
-Werrorcompilation of all generated TUs) before and after.Densified schema IR + evaluator: every schema/branch/component is
flattened into a
SchemaNoderegistry (schema_ir.generated.cpp) andvalidated at decode time by the generated
SchemaEvaluator. Numericconstraints carry their original lexemes (
ExactNumber::parseLexeme/setExact) so values > 2^53, decimals like 0.3 and exponent forms validatewithout 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_validationand exposed through CamelCase generated headers (
Oas31ExactNumber.h,Oas31ExactJson.h,Oas31SchemaIr.h,Oas31DeepEqual.h,Oas31Validator.h, andOas31SchemaRegistry.h), so multiple generatedclients can coexist in one translation unit without type or ODR collisions.
OAS 3.1 / 2020-12 vocabulary: type arrays (incl. literal
"null"),boolean value-schemas,
notsubschemas, deep (array/object) enum/const,uniqueItems,min/maxContains+contains,dependentRequired(withraw-literal recovery for the parser's list-merge corruption),
patternProperties/propertyNames,min/maxProperties,$refresourceidentity +
$dynamicRef/$dynamicAnchorscope resolution, annotationkeywords per 2020-12 §8.2.6 (
contentEncoding/contentMediaType/contentSchema,$commentshape checks).Composition semantics:
CompositionBranchValue<N, T>tagged variantspreserve branch identity (dedup, null-collapse, enum-union cases no longer
blind-collapse to
std::string); model-qualified public branch accessorsavoid 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.
SSE and wire support:
sseSchemaMode=representation|jsonEventDataplusper-operation
x-sse-event-data-schemaopt-in,formatAssertionPolicy=annotation|strict, andcompileWithValidation=true|false(default true; false compiles validationout).
Tests (134 focused tests, all passing):
Oas31IrComplianceTest,CompositionLoweringTest,ModelApiSurfaceTest,Oas31ExactRuntimeTest,CppBoostBeastClientApiCodegenTest,DependentRequiredParserRetentionTest, andDynamicRefParserRetentionTest.Native C++ runtime cases compile with
-Wall -Wextra -Werrorand exerciseexact numbers, composition dispatch, public branch accessors, wire paths,
and two independently generated clients in one translation unit.
Docs & samples: generator page regenerated (all three new CLI options
documented with defaults); regenerated petstore sample included.
Compatibility
generated validation support files and decode-time validation path.
Boost.JSON can only represent as a non-finite value or surrogate; exact schema
validation still evaluates the original token.
x-oas31-*/x-cpp-*engine channels never appear in generatedoutput (regression-tested); the user-facing
x-sse-event-data-schemaisdocumented in the generator page.
Verification
checkstyle:checkand the moduleverify/forbidden-apis gates pass.generated-path 39/39, Wave-1-complete 35/35, and M-probe 50/50.
PR checklist
master.x-sse-event-data-schema.forbiddenapis/checkstyle gates green.