feat: add support for oneOf in sttp4 client#22916
feat: add support for oneOf in sttp4 client#22916plaflamme wants to merge 24 commits intoOpenAPITools:masterfrom
oneOf in sttp4 client#22916Conversation
There was a problem hiding this comment.
4 issues found across 8 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="modules/openapi-generator/src/main/resources/scala-sttp4/api.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/scala-sttp4/api.mustache:38">
P2: Request bodies are now always JSON‑serialized with `asJson(...)`, which breaks operations that consume non‑JSON media types by sending JSON instead of the raw body.</violation>
</file>
<file name="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache:52">
P2: json4s oneOf deserialization aborts on first mismatch because `Extraction.extract` throws, so later oneOf types are never tried.</violation>
<violation number="2" location="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache:74">
P2: Discriminator handling hardcodes the Scala class name as the wire value, ignoring OpenAPI discriminator mappings, so specs with mapped discriminator values will fail to deserialize/serialize correctly.</violation>
<violation number="3" location="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache:172">
P2: Inline oneOf member case classes are generated inside the parent model file, but the parent model’s import list isn’t augmented with the child models’ imports. If a oneOf member uses a type not referenced by the parent, the inline case class will compile without the necessary import.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
1ec344d to
c56b433
Compare
|
thanks for the PR cc @clasnake (2017/07), @shijinkui (2018/01), @ramzimaalej (2018/03), @chameleon82 (2020/03), @Bouillie (2020/04) @Fish86 (2023/06) |
|
https://github.com/OpenAPITools/openapi-generator/actions/runs/21774387501/job/62832048821?pr=22916 please follow step 3 to update the samples |
c56b433 to
4437a9e
Compare
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache:107">
P2: `transformConstructorNames` is defined as a partial match over only `x-oneOfMembers`. Because `x-oneOfMembers` excludes shared oneOf subtypes, the function is not total and will throw `MatchError` when encoding/decoding those subtypes. Add a default case (identity) so unmapped constructor names don’t crash serialization.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache:110">
P2: Wildcard `case other` is emitted inside the oneOfMembers loop, so the first wildcard matches everything and makes subsequent member cases unreachable; only the first oneOf member can be encoded/decoded.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
d05761d to
7aba995
Compare
On large APIs this can dramatically speed up compilation.
|
https://github.com/OpenAPITools/openapi-generator/actions/runs/21850815146/job/63223036192?pr=22916 please replace tabs with 4-space in the java file. |
|
@wing328 should be all good now |
|
please review the build failure when you've time. let us know if you need any help |
|
@wing328 I think this is good to go now? |
# Conflicts: # modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java
…f Enumeration-based EnumNameSerializer The json4s variant of JsonSupport.scala used EnumNameSerializer which expects Scala Enumeration types, but sttp4 model templates generate sealed traits with case objects for enums. This caused compilation errors since the types are incompatible. Replace EnumNameSerializer with direct references to the implicit Serializer objects already defined in each enum's companion object.
|
@wing328 updated this on top of the latest master branch, anything else needed to get this in? |
…ropagate parent props When a schema has both its own properties and a oneOf composition, the sttp4 generator previously had three issues: 1. Empty oneOf children (no type, no properties) were silently dropped because DefaultCodegen's unaliasSchema treated them as type aliases, replacing the model name with the language type (e.g. io.circe.Json). 2. Parent-owned properties (e.g. cancellation_datetime) were lost entirely - they didn't appear on the sealed trait or any child. 3. Sibling properties from other oneOf children could contaminate each child's vars list via DefaultCodegen's property merging. This fix: - Resolves aliased oneOf children by looking at the original OpenAPI schema's $refs to restore correct model names - Identifies parent-owned properties and propagates them to each child model's allVars so they appear in generated case classes - Renders parent properties as abstract def members on the sealed trait for compile-time enforcement - Adds a test covering the CancellationInfo pattern (parent with properties + oneOf with empty and non-empty children)
|
will try to run some tests tomorrow and merge if no questions/feedback from anyone. |
| @@ -0,0 +1,107 @@ | |||
| openapi: 3.0.0 | |||
There was a problem hiding this comment.
i did a test with this spec:
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g scala-sttp4 -i https://raw.githubusercontent.com/plaflamme/openapi-generator/ac1b84e6a05e1bbfbfed7903aed15cc20fdb7c9b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf.yaml -o /tmp/scala3 --additional-properties jsonLibrary=circe --skip-validate-spec
but got compilation errors on the output:
$ sbt compile
[info] welcome to sbt 1.10.11 (Eclipse Adoptium Java 11.0.30)
[info] loading settings for project scala3-build from plugins.sbt...
[info] loading project definition from C:\Users\User\AppData\Local\Temp\scala3\project
[info] loading settings for project scala3 from build.sbt...
[info] set current project to openapi-client (in build file:/C:/Users/User/AppData/Local/Temp/scala3/)
[info] Executing in batch mode. For better performance use sbt's shell
[info] compiling 8 Scala sources to C:\Users\User\AppData\Local\Temp\scala3\target\scala-2.13\classes ...
[error] C:\Users\User\AppData\Local\Temp\scala3\src\main\scala\org\openapitools\client\core\JsonSupport.scala:19:66: not found: type DateSerializers
[error] object JsonSupport extends SttpCirceApi with AutoDerivation with DateSerializers with AdditionalTypeSerializers {
[error] ^
[error] C:\Users\User\AppData\Local\Temp\scala3\src\main\scala\org\openapitools\client\model\Animal.scala:29:43: Could not find ConfiguredAsObjectEncoder for type org.openapitools.client.model.Animal.
[error] Some possible causes for this:
[error] - org.openapitools.client.model.Animal isn't a case class or sealed trait
[error] - some of org.openapitools.client.model.Animal's members don't have codecs of their own
[error] - missing implicit Configuration
[error] implicit val encoder: Encoder[Animal] = deriveConfiguredEncoder
[error] ^
[error] C:\Users\User\AppData\Local\Temp\scala3\src\main\scala\org\openapitools\client\model\Animal.scala:30:43: Could not find ConfiguredDecoder for type org.openapitools.client.model.Animal.
[error] Some possible causes for this:
[error] - org.openapitools.client.model.Animal isn't a case class or sealed trait
[error] - some of org.openapitools.client.model.Animal's members don't have codecs of their own
[error] - missing implicit Configuration
[error] implicit val decoder: Decoder[Animal] = deriveConfiguredDecoder
[error] ^
[error] C:\Users\User\AppData\Local\Temp\scala3\src\main\scala\org\openapitools\client\model\Pet.scala:22:40: could not find Lazy implicit value of type io.circe.generic.encoding.DerivedAsObjectEncoder[org.openapitools.client.model.Pet]
[error] implicit val encoder: Encoder[Pet] = deriveEncoder
[error] ^
[error] C:\Users\User\AppData\Local\Temp\scala3\src\main\scala\org\openapitools\client\model\Pet.scala:23:40: could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[org.openapitools.client.model.Pet]
[error] implicit val decoder: Decoder[Pet] = deriveDecoder
[error] ^
[error] 5 errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 4 s, completed Mar 22, 2026, 10:09:05 PM
can you please take a look when you've time?
When a schema is referenced by multiple oneOf parents (e.g., Dog and Cat used by both Pet and Animal), the generator cannot inline them into any single parent. Previously this produced broken code: sealed traits with no subtypes, causing circe derivation and json4s serialization to fail. This fix introduces wrapper composition for shared members: each parent generates wrapper case classes (e.g., DogPet, CatAnimal) inside its companion object that delegate to the standalone type. Hand-written codecs replace semiauto derivation when wrappers are present. Exclusive members (used by only one parent) continue to be inlined directly, preserving the existing behavior. Fixes compilation errors reported in PR OpenAPITools#22916.
There was a problem hiding this comment.
1 issue found across 3 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/resources/scala-sttp4/model.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache:149">
P1: Circe oneOf encoder with wrapper composition does not add discriminator in discriminator mode, causing mismatch with decoder that requires discriminator.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
When a schema is referenced by multiple oneOf parents (e.g., Dog and Cat used by both Pet and Animal), the generator cannot inline them into any single parent. Previously this produced broken code: sealed traits with no subtypes, causing circe derivation and json4s serialization to fail. This fix introduces wrapper composition for shared members: each parent generates wrapper case classes (e.g., DogPet, CatAnimal) inside its companion object that delegate to the standalone type. Hand-written codecs replace semiauto derivation when wrappers are present. Exclusive members (used by only one parent) continue to be inlined directly, preserving the existing behavior. Fixes compilation errors reported in PR OpenAPITools#22916.
774dfc9 to
79e738b
Compare
When the discriminator property name is a Scala keyword (e.g., `type`), the codegen escapes it with backticks for use as a Scala identifier. However, the serialization templates (circe and json4s) were using `discriminator.propertyName` which contains the escaped form, producing incorrect JSON field names like `\`type\`` instead of `type`. Switch all 10 usages in model.mustache to `discriminator.propertyBaseName` which holds the original, unescaped wire name from the OpenAPI spec. Add test case with Shape/Circle/Square using `type` as discriminator.
Address #19891 for sttp4 clients.
@Fish86
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
master(upcoming7.x.0minor release - breaking changes with fallbacks),8.0.x(breaking changes without fallbacks)"fixes #123"present in the PR description)Summary by cubic
Adds oneOf support to the Scala
scala-sttp4client with discriminator mapping, wrapper composition for shared members, and aligned JSON/request/response handling. Updates docs, adds ascala-sttp4-circesample and CI, bumps dependencies, fixes parent-with-properties and aliased-child cases, and adds tests (including keyword discriminator wire-name). Fixes #19891.New Features
scala-sttp4-circesample + CI; sttp client 4.0.15; Scala 2.13.18; addcirce-genericandcirce-generic-extras; use semiauto derivation.Migration
Written for commit 21efd98. Summary will update on new commits.