-
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
feat(cpp-boost-beast-client): variant-first oneOf/anyOf/allOf composition #24387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
bold84
wants to merge
74
commits into
OpenAPITools:master
Choose a base branch
from
bold84:plan/cpp-boost-beast-oneof-anyof-plan
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+11,800
−195
Draft
Changes from all commits
Commits
Show all changes
74 commits
Select commit
Hold shift + click to select a range
9f421b0
feat(cpp-boost-beast): add OpenAI compliance harness for composed sch…
bold84 88ee024
fix(phase-0): address local review findings
bold84 ee72168
fix(phase-0): address strong review findings
bold84 5aeefac
fix(phase-0): close pin reproducibility and clean up expected-types
bold84 7fc4b2f
feat(cpp-boost-beast): implement oneOf/anyOf type lowering engine
bold84 6554228
fix(phase-1): address local review findings
bold84 26a8a1f
fix(phase-1): strip nested shared_ptr from template args
bold84 f4059ce
fix(phase-1): address strong review findings
bold84 6fa7be4
feat(cpp-boost-beast): implement variant-first template generation
bold84 7ceaf9e
fix(phase-2): address local review findings
bold84 9804018
fix(phase-2): address strong review findings
bold84 a350721
fix(phase-2): address remaining strong review findings
bold84 4065cfe
fix(phase-2): add missing map include for variant sources
bold84 8dc8060
feat(cpp-boost-beast): implement API integration for variant types
bold84 b0e4406
fix(phase-3): complete variant JSON helpers and model import fixes
bold84 251b2e8
fix(phase-3): wire SSE parseEventStream and multipart variant branches
bold84 3b97fb7
fix(phase-3): address local review
bold84 bcd4e31
fix(phase-3): address strong review findings
bold84 0a016f0
fix(phase-3): address strong review findings (WIP checkpoint)
bold84 06f0711
fix(phase-3): address strong review findings
bold84 5a7e9c0
fix(phase-3): address strong review findings
bold84 dd58f09
feat(phase-0): add OAS Gate A composition compliance harness
bold84 6053616
fix(phase-0): address strong review findings
bold84 05716b3
fix(phase-0): address strong review findings
bold84 2f2c05e
fix(phase-1): OAS-first composition lowering rules
bold84 3c6b6c0
fix(phase-1): strong review remediation — shared_ptr stripping, allOf…
bold84 6bf0053
fix(phase-1): address strong review findings
bold84 d30ef4f
fix(phase-1): gate model-source.mustache IsSet with x-cpp-no-is-set
bold84 84d4c22
fix(phase-2): oneOf exactly-one vs anyOf first-match decode
bold84 5f9d913
fix(phase-2): address strong review findings
bold84 db2104b
fix(phase-2): address strong review findings
bold84 20373f4
fix(phase-3): address strong review findings
bold84 f4f25a6
fix(phase-4): transitive string collapse and Gate B corpus gate
bold84 a1a756a
fix(phase-4): omit empty default initializers for corpus compile
bold84 cc07f95
fix(phase-4): nested model JSON via fromJsonValue not value_to
bold84 87d8a8f
fix(phase-4): complete OpenAI corpus C++ compile
bold84 2be0c97
fix(phase-4): fix golden runner namespace and round-trips
bold84 30b886c
fix(phase-4): discriminator-aware nested union round-trips
bold84 ef45fd5
fix(phase-4): remove unused discriminatorMatched
bold84 8af6a46
fix(phase-4): address strong review findings
bold84 781acbd
feat(phase-5): validation hardening required fields paths discriminator
bold84 4a7710f
fix(phase-5): address strong review findings
bold84 c3aa67b
docs(phase-6): variant-first OAS dual-gate and streaming docs
bold84 27a1ee7
fix(phase-6): address strong review findings
bold84 5e2f70e
fix(phase-6): align Gate A nullability blurb with implementation
bold84 37eda81
fix(cpp-boost-beast): harden OAS-first composed-schema fidelity
bold84 dcbe25c
merge: integrate origin/master into plan/cpp-boost-beast-oneof-anyof-…
bold84 fe87d1c
chore(cpp-boost-beast): drop OpenAI corpus harness from PR surface
bold84 a8ad215
chore(cpp-boost-beast): remove accidental OpenAI pin files from branch
bold84 57008b0
fix(cpp-boost-beast): replace setAccessible with setMappedModels
bold84 62dcb97
fix(cpp-boost-beast): implement incremental streaming in executeStream
bold84 476b450
fix(cpp-boost-beast): frame SSE events in executeStream
bold84 6542604
fix(cpp-boost-beast): reject SSE streaming for schema-less operations
bold84 95aabfe
fix(cpp-boost-beast): guard dual-content stream method behind resolve…
bold84 b4d7ef8
fix(cpp-boost-beast): unwrap ModelPropertyStorage for nullable inheri…
bold84 d18fe52
fix(cpp-boost-beast): preserve branch enum metadata in Phase 1b alias…
bold84 3734a35
fix(cpp-boost-beast): wrap streaming return type in std::vector when …
bold84 ff6ef09
fix(cpp-boost-beast): lower nullable $ref schemas and arrays to std::…
bold84 3dcbbf9
fix(cpp-boost-beast): enforce exactly-one match in oneOf variant resp…
bold84 6fb4910
chore(cpp-boost-beast): regenerate samples after oneOf variant fix
bold84 4e0000c
fix(cpp-boost-beast): declare variant/optional serializers before con…
bold84 8a76940
Qualify discriminator literal encode behavior in cpp-boost-beast README
bold84 e86b150
fix(cpp-boost-beast): add std::map branch to tryParseBranch for compo…
bold84 c149e4d
fix(cpp-boost-beast): validate int32_t bounds in variant decode to pr…
bold84 4575fb8
[cpp-boost-beast-client] Extend oneOf string-enum exclusivity Rule 6 …
bold84 aeb5f0a
fix(cpp-boost-beast): enforce const property decode invariants
bold84 0052b74
fix(cpp-boost-beast-client): add missing std::uint8_t branch to tryPa…
bold84 ad036fd
fix(cpp-boost-beast): refresh x-discriminator-resolved-type after Pha…
bold84 4de7012
fix(cpp-boost-beast): lower nullable arrays, maps, and inline objects…
bold84 9bde41f
fix(cpp-boost-beast): restore gate-a.sh compliance harness dropped wi…
bold84 82eefd0
docs(cpp-boost-beast): align README minimum versions with CMakeLists.txt
bold84 3d347c4
fix: correct README.mustache toJsonValue/fromJsonValue function names
bold84 059c373
fix(cpp-boost-beast): address reviewer regressions
bold84 aeef0bd
fix(cpp-boost-beast): resolve nullable model regressions
bold84 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1,748 changes: 1,738 additions & 10 deletions
1,748
...enerator/src/main/java/org/openapitools/codegen/languages/CppBoostBeastClientCodegen.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| # {{packageName}} | ||
|
|
||
| {{#appDescriptionWithNewLines}}{{{.}}}{{/appDescriptionWithNewLines}} | ||
|
|
||
| Generated by [OpenAPI Generator](https://openapi-generator.tech). | ||
| {{#generatorVersion}}Generator version: {{generatorVersion}}{{/generatorVersion}} | ||
|
|
||
| - API version: {{appVersion}} | ||
| {{^hideGenerationTimestamp}}- Build date: {{generatedDate}}{{/hideGenerationTimestamp}} | ||
| - API namespace: `{{{apiPackage}}}` | ||
| - Model namespace: `{{{modelPackage}}}` | ||
|
|
||
| --- | ||
|
|
||
| ## Architecture | ||
|
|
||
| This client uses **variant-first composed-schema lowering** — a deliberate departure | ||
| from the default OpenAPI Generator composed-schema handling. | ||
|
|
||
| ### Breaking model strategy | ||
|
|
||
| The generator treats composed schemas (`oneOf`, `anyOf`, `allOf`) as first-class | ||
| C++ types rather than empty object shells. This is a **breaking change**: generated | ||
| model headers, source, and API surfaces are fundamentally different from the previous | ||
| output. | ||
|
|
||
| **The break is intentional and acceptable.** No known downstream project depends on | ||
| the prior composed-model output for `cpp-boost-beast-client`. The generator stability | ||
| label stays STABLE because the generated output targets a specific C++ client; the | ||
| prior output for composed schemas was non-functional (empty structs that could not | ||
| hold union data). | ||
|
|
||
| | Before | After | | ||
| |--------|-------| | ||
| | Empty struct `AnyOfStringArray {}` | `std::variant<std::string, std::vector<ItemStruct>>` | | ||
| | Composed → `{}` on wire | Correct encode/decode via `toJsonValue`/`fromJsonValue` | | ||
| | Polymorphism disabled | `oneOf`/`anyOf`/`allOf` fully supported | | ||
|
|
||
| ### Ordered composition lowering | ||
|
|
||
| The generator applies the following rules in order to every composed schema: | ||
|
|
||
| 1. **Merge compatible `allOf` object composition** through generated inheritance and property aggregation. Conflict → error. | ||
| 2. **Lower `anyOf`/`oneOf` null unions** with a single non-null branch to `std::optional<T>` (and OAS 3.0 `nullable`). | ||
| 3. **Collapse `anyOf` of only string and string-enums to `std::string`.** | ||
| Does **not** apply this collapse to `oneOf` unless exclusivity is proven. | ||
| 4. **Collapse single remaining non-null branch** to that branch type. | ||
| 5. **Deduplicate identical C++ branch types** inside variants. | ||
| 6. **Else emit `std::variant<Branches...>`**. | ||
| 7. **If still untyped/open**, emit `boost::json::value`. | ||
|
|
||
| ### OpenAPI → C++ type mapping | ||
|
|
||
| | OpenAPI shape | C++ type | | ||
| |---------------|----------| | ||
| | `string` | `std::string` | | ||
| | `integer`/`int32` | `std::int32_t` | | ||
| | `integer`/`int64` | `std::int64_t` | | ||
| | `number` | `double` | | ||
| | `boolean` | `bool` | | ||
| | `anyOf: [T, null]` / nullable `T` | `std::optional<T>` | | ||
| | `oneOf` / non-null `anyOf` unions | `std::variant<...>` | | ||
| | `allOf` object | Inherited/merged value class | | ||
| | array | `std::vector<T>` | | ||
| | map / `additionalProperties` | `std::map<std::string, T>` | | ||
| | free-form JSON | `boost::json::value` | | ||
| | object struct | Value class | | ||
| | cyclic edge | `std::shared_ptr<T>` | | ||
| | `enum` string | `enum class` + string conversion | | ||
| | Discriminator literal | Fixed on encode when branch schema declares a `const` (OAS 3.1) or single-value `enum`; otherwise not injected | | ||
|
|
||
| --- | ||
|
|
||
| ## Nullability: omit vs null policy | ||
|
|
||
| ### Current scope (Phase 2) | ||
|
|
||
| The generated encode logic uses a **two-state model** for optional fields: | ||
|
|
||
| | Field kind | Encode behavior | | ||
| |-----------|----------------| | ||
| | Non-required, disengaged optional / isSet=false | **Key omitted** from JSON output | | ||
| | Non-required, engaged optional / isSet=true | Key written with converted value | | ||
| | Required, nullable, `std::nullopt` | Key written as JSON `null` | | ||
| | Required, nullable, has value | Key written with converted value | | ||
|
|
||
| For non-required fields, the current implementation omits the key when the | ||
| optional is disengaged. The `JsonValueConverter<std::optional<T>>::toJsonValue` | ||
| helper unwraps the inner value; the outer `has_value()` check at the call site | ||
| prevents unreachable `null` output for absent optionals. | ||
|
|
||
| ### Deferred: full tri-state | ||
|
|
||
| A full **tri-state model** (field absent / field present with JSON null / field | ||
| present with JSON value) is **not yet implemented**. When the schema allows null, | ||
| the current implementation cannot distinguish "field was sent as JSON null" from | ||
| "field was absent" for non-required properties — both result in the key being | ||
| omitted on encode and `std::nullopt` on decode. | ||
|
|
||
| A future phase may add a separate `null_state` flag per nullable field to | ||
| preserve the absent-vs-null distinction on the wire, matching OAS 3.1's | ||
| semantic model where a missing key and a present `null` value are distinct. | ||
|
|
||
| --- | ||
|
|
||
| ## Compliance testing | ||
|
|
||
| Composition lowering is covered by unit tests and OAS fixture specs under: | ||
|
|
||
| ```text | ||
| modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/ | ||
| modules/openapi-generator/src/test/resources/3_1/cpp-boost-beast-client/oas-compliance/ | ||
| ``` | ||
|
|
||
| Run: | ||
|
|
||
| ```sh | ||
| ./mvnw -pl modules/openapi-generator \ | ||
| -Dtest=CppBoostBeastClientCodegenTest,CppBoostBeastClientApiCodegenTest test | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Media-type-driven streaming (not `stream` flags) | ||
|
|
||
| OpenAPI models streaming via the response `content` map, not via boolean parameters: | ||
|
|
||
| ```yaml | ||
| responses: | ||
| '200': | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/Response' | ||
| text/event-stream: | ||
| schema: | ||
| $ref: '#/components/schemas/ResponseStreamEvent' | ||
| ``` | ||
|
|
||
| The generator **discovers** media types and schemas from the `content` map. This | ||
| includes same-status multi-content operations where one media type is | ||
| `text/event-stream` and another is `application/json`. | ||
|
|
||
| ### Required behavior | ||
|
|
||
| 1. **Client selection** — The caller chooses representation via the `Accept` header. | ||
| Non-streaming path sets `Accept: application/json`; streaming path sets | ||
| `Accept: text/event-stream`. | ||
| 2. **SSE decoding** — When `text/event-stream` is selected, the generator emits | ||
| an event-stream parser that decodes each SSE `data:` line using the | ||
| schema from the `text/event-stream` media type entry (often a `oneOf` union). | ||
| 3. **Transport** — Streaming operations use `HttpClient::executeStream`, which | ||
| reads the response body incrementally and delivers complete SSE events | ||
| (WHATWG-framed `data` payloads) via callback as they arrive. | ||
|
|
||
| ### Explicitly forbidden as core OAS behavior | ||
|
|
||
| - Hard-coding parameter name `stream` or operationId patterns from any single API. | ||
| - Assuming event type names (`ResponseStreamEvent`, etc.) in the engine. | ||
| - Treating dual media-type content as vendor-specific. | ||
|
|
||
| Parameter properties named `stream` in the OpenAPI document are ordinary schema | ||
| parameters — not OAS keywords. The generator does not special-case them. | ||
|
|
||
| ### Incremental SSE transport | ||
|
|
||
| `HttpClient::executeStream` performs incremental `async_read_some` reads and | ||
| frames Server-Sent Events per the WHATWG specification. Each callback receives | ||
| one complete event's data payload (multi-line `data:` fields joined by LF). | ||
| Generated streaming operations parse each event as it is framed. Incomplete | ||
| events at EOF are discarded. | ||
|
|
||
| --- | ||
|
|
||
| ## Non-standard format mappings | ||
|
|
||
| The generator documents non-standard format mappings as explicit type-mapping | ||
| entries, not as core OAS vocabulary. | ||
|
|
||
| | Format | OAS status | C++ type | Notes | | ||
| |--------|-----------|----------|-------| | ||
| | `unixtime` | **Non-standard** (not in OAS format registry) | `std::int64_t` | Optional generator convenience in `getSchemaType` when `format: unixtime` appears. Not core OAS vocabulary. | | ||
|
|
||
| This mapping is a **documented non-standard convenience** for APIs that encode | ||
| Unix-epoch seconds as integers. It does **not** imply that `unixtime` is a | ||
| standard OAS format. Specs that omit `format: unixtime` are unaffected. Any API | ||
| using a different time representation (ISO 8601 strings, millisecond epochs, | ||
| etc.) must define its own schema. | ||
|
|
||
| --- | ||
|
|
||
| ## Large breaking PR is acceptable | ||
|
|
||
| This work ships as a single, large, explicitly-breaking pull request that: | ||
|
|
||
| 1. Eliminates the prior empty-shell composed-model output entirely. | ||
| 2. Replaces it with `std::variant` / `std::optional` / collapsed types. | ||
| 3. Updates every mustache template for models and API operations. | ||
| 4. Adds OAS composition fixtures and unit tests for lowering rules. | ||
| 5. Adds media-type-driven streaming support. | ||
|
|
||
| **No attempt is made to preserve backward compatibility with prior generated | ||
| output.** The prior output was non-functional for composed schemas, and no | ||
| known downstream project depends on it. The breaking change is confined to | ||
| a single generator (`cpp-boost-beast-client`) and does not affect any other | ||
| OpenAPI Generator language target. | ||
|
|
||
| Reviewers should evaluate the PR against: | ||
| - **OAS compliance** — do the generated types and encode/decode faithfully | ||
| represent the OpenAPI specification's composition rules? | ||
| - **Fixture coverage** — does every lowering rule have dedicated unit/fixture | ||
| coverage with green tests? | ||
|
|
||
| --- | ||
|
|
||
| ## Requirements | ||
|
|
||
| - C++17 compiler (GCC 7+, Clang 8+, MSVC 2019+) | ||
| - CMake 3.14+ | ||
| - Boost 1.75+ (Beast, Asio, JSON) | ||
| - OpenSSL 1.1.0+ | ||
|
|
||
| ## Build | ||
|
|
||
| ```sh | ||
| mkdir build && cd build | ||
| cmake .. -DCMAKE_BUILD_TYPE=Release | ||
| cmake --build . | ||
| ``` | ||
|
|
||
| ## Installation | ||
|
|
||
| ```sh | ||
| cmake --install . | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The documented Boost 1.75 minimum is too low for generated nullable alias models: their alias source relies on Boost.JSON's
std::optionalvalue conversion, which was added in Boost 1.81. Either raise the generated client's minimum to Boost 1.81 or route alias serialization through a generator-provided optional converter.Prompt for AI agents