Skip to content

Commit 8a17590

Browse files
committed
Fix bug
1 parent a09bdc4 commit 8a17590

6 files changed

Lines changed: 120 additions & 25 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,18 @@ private Map<String, Integer> countModelReferences(Map<String, CodegenModel> allM
310310
.flatMap(m -> m.oneOf.stream())
311311
.forEach(name -> counts.merge(name, 1, Integer::sum));
312312

313-
// Count property-type references (prevents inlining models used as field types)
313+
// Count property-type references (prevents inlining models used as field types).
314+
// Check both dataType and complexType
314315
allModels.values().stream()
315316
.flatMap(m -> m.vars.stream())
316-
.map(prop -> prop.dataType)
317-
.filter(allModels::containsKey)
318-
.forEach(name -> counts.merge(name, 1, Integer::sum));
317+
.forEach(prop -> {
318+
if (prop.dataType != null && allModels.containsKey(prop.dataType)) {
319+
counts.merge(prop.dataType, 1, Integer::sum);
320+
}
321+
if (prop.complexType != null && allModels.containsKey(prop.complexType)) {
322+
counts.merge(prop.complexType, 1, Integer::sum);
323+
}
324+
});
319325

320326
return counts;
321327
}
@@ -324,9 +330,10 @@ private Map<String, Integer> countModelReferences(Map<String, CodegenModel> allM
324330
* Mark oneOf parents as sealed/regular traits with discriminator vendor extensions,
325331
* and configure child models for inlining.
326332
*/
327-
private void markOneOfTraits(Map<String, ModelsMap> modelsMap,
328-
Map<String, CodegenModel> allModels,
329-
Map<String, Integer> refCounts) {
333+
private void markOneOfTraits(
334+
Map<String, ModelsMap> modelsMap,
335+
Map<String, CodegenModel> allModels,
336+
Map<String, Integer> refCounts) {
330337
for (ModelsMap mm : modelsMap.values()) {
331338
for (ModelMap modelMap : mm.getModels()) {
332339
CodegenModel model = modelMap.getModel();
@@ -345,16 +352,26 @@ private void markOneOfTraits(Map<String, ModelsMap> modelsMap,
345352
}
346353
}
347354

348-
private void configureOneOfModel(CodegenModel parent,
349-
Map<String, CodegenModel> allModels,
350-
Map<String, Integer> refCounts) {
355+
private void configureOneOfModel(
356+
CodegenModel parent,
357+
Map<String, CodegenModel> allModels,
358+
Map<String, Integer> refCounts) {
351359
List<CodegenModel> inlineableMembers = new ArrayList<>();
352360
Set<String> childImports = new HashSet<>();
353361

354362
for (String childName : parent.oneOf) {
355363
CodegenModel child = allModels.get(childName);
356-
if (child != null && isInlineable(child, refCounts)) {
357-
markChildForInlining(child, parent);
364+
if (child == null) continue;
365+
366+
// All children extend the parent trait
367+
child.getVendorExtensions().put("x-oneOfParent", parent.classname);
368+
if (parent.discriminator != null) {
369+
child.getVendorExtensions().put("x-parentDiscriminatorName",
370+
parent.discriminator.getPropertyName());
371+
}
372+
373+
if (isInlineable(child, refCounts)) {
374+
child.getVendorExtensions().put("x-isOneOfMember", true);
358375
inlineableMembers.add(child);
359376
if (child.imports != null) {
360377
childImports.addAll(child.imports);
@@ -376,15 +393,6 @@ private boolean isInlineable(CodegenModel child, Map<String, Integer> refCounts)
376393
&& refCounts.getOrDefault(child.classname, 0) == 1;
377394
}
378395

379-
private void markChildForInlining(CodegenModel child, CodegenModel parent) {
380-
child.getVendorExtensions().put("x-isOneOfMember", true);
381-
child.getVendorExtensions().put("x-oneOfParent", parent.classname);
382-
if (parent.discriminator != null) {
383-
child.getVendorExtensions().put("x-parentDiscriminatorName",
384-
parent.discriminator.getPropertyName());
385-
}
386-
}
387-
388396
private void buildDiscriminatorEntries(CodegenModel parent, Map<String, CodegenModel> allModels) {
389397
List<Map<String, String>> entries = parent.oneOf.stream()
390398
.map(allModels::get)
@@ -394,8 +402,10 @@ private void buildDiscriminatorEntries(CodegenModel parent, Map<String, CodegenM
394402
parent.getVendorExtensions().put("x-discriminator-entries", entries);
395403
}
396404

397-
private void markAsSealedTrait(CodegenModel parent, List<CodegenModel> members,
398-
Set<String> childImports) {
405+
private void markAsSealedTrait(
406+
CodegenModel parent,
407+
List<CodegenModel> members,
408+
Set<String> childImports) {
399409
parent.getVendorExtensions().put("x-isSealedTrait", true);
400410
parent.getVendorExtensions().put("x-oneOfMembers", members);
401411

@@ -411,8 +421,6 @@ private void markAsRegularTrait(CodegenModel parent, List<CodegenModel> partialM
411421
parent.getVendorExtensions().put("x-isRegularTrait", true);
412422
for (CodegenModel member : partialMembers) {
413423
member.getVendorExtensions().remove("x-isOneOfMember");
414-
member.getVendorExtensions().remove("x-oneOfParent");
415-
member.getVendorExtensions().remove("x-parentDiscriminatorName");
416424
}
417425
}
418426

modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/ScalaSttpCirceCodegenTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,41 @@ public void verifyAllOfDiscriminator() throws IOException {
129129
"Dog.scala should not exist (inlined in Animal.scala)");
130130
}
131131

132+
@Test(description = "container-wrapped model ref (Seq[Dog]) prevents inlining of oneOf child")
133+
public void verifyContainerWrappedRefPreventsInlining() throws IOException {
134+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
135+
output.deleteOnExit();
136+
String outputPath = output.getAbsolutePath().replace('\\', '/');
137+
138+
generateFromSpec("src/test/resources/3_0/scala-sttp-circe/petstore.yaml", output);
139+
140+
// Dog is referenced both as a oneOf child of Animal AND as Seq[Dog] in Kennel.dogs.
141+
// Since not all children can be inlined, Animal becomes a regular trait (not sealed).
142+
// Both Dog and Cat get their own files.
143+
Path dogPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Dog.scala");
144+
Assert.assertTrue(dogPath.toFile().exists(),
145+
"Dog.scala must exist as a separate file (used as array element in Kennel)");
146+
assertFileContains(dogPath, "case class Dog");
147+
assertFileContains(dogPath, "extends Animal");
148+
149+
// Cat also gets its own file (regular trait = no inlining)
150+
Path catPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Cat.scala");
151+
Assert.assertTrue(catPath.toFile().exists(),
152+
"Cat.scala must exist (Animal is a regular trait, no children inlined)");
153+
assertFileContains(catPath, "case class Cat");
154+
assertFileContains(catPath, "extends Animal");
155+
156+
// Animal is a regular trait (not sealed) because not all children can be inlined
157+
Path animalPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Animal.scala");
158+
assertFileContains(animalPath, "trait Animal");
159+
assertFileNotContains(animalPath, "case class Cat");
160+
assertFileNotContains(animalPath, "case class Dog");
161+
162+
// Kennel should reference Dog via Seq
163+
Path kennelPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Kennel.scala");
164+
assertFileContains(kennelPath, "dogs: Option[Seq[Dog]]");
165+
}
166+
132167
@Test(description = "oneOf + discriminator generates sealed trait (standard pattern)")
133168
public void verifyOneOfDiscriminator() throws IOException {
134169
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();

modules/openapi-generator/src/test/resources/3_0/scala-sttp-circe/petstore.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,3 +886,12 @@ components:
886886
properties:
887887
reflective:
888888
type: boolean
889+
Kennel:
890+
type: object
891+
properties:
892+
name:
893+
type: string
894+
dogs:
895+
type: array
896+
items:
897+
$ref: '#/components/schemas/Dog'

samples/client/petstore/scala-sttp-circe/.openapi-generator/FILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ src/main/scala/org/openapitools/client/model/ApiResponse.scala
1515
src/main/scala/org/openapitools/client/model/Category.scala
1616
src/main/scala/org/openapitools/client/model/Collar.scala
1717
src/main/scala/org/openapitools/client/model/EnumTest.scala
18+
src/main/scala/org/openapitools/client/model/Kennel.scala
1819
src/main/scala/org/openapitools/client/model/Order.scala
1920
src/main/scala/org/openapitools/client/model/Pet.scala
2021
src/main/scala/org/openapitools/client/model/PropertyNameMapping.scala

samples/client/petstore/scala-sttp-circe/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Class | Method | HTTP request | Description
9696
- [Category](Category.md)
9797
- [Collar](Collar.md)
9898
- [EnumTest](EnumTest.md)
99+
- [Kennel](Kennel.md)
99100
- [Order](Order.md)
100101
- [Pet](Pet.md)
101102
- [PropertyNameMapping](PropertyNameMapping.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* OpenAPI Petstore
3+
* This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
4+
*
5+
* The version of the OpenAPI document: 1.0.0
6+
*
7+
*
8+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9+
* https://openapi-generator.tech
10+
* Do not edit the class manually.
11+
*/
12+
package org.openapitools.client.model
13+
14+
import io.circe.{Decoder, DecodingFailure, Encoder, Json}
15+
import io.circe.syntax._
16+
import org.openapitools.client.core.JsonSupport._
17+
18+
case class Kennel(
19+
name: Option[String] = None,
20+
dogs: Option[Seq[Dog]] = None
21+
)
22+
object Kennel {
23+
implicit val encoderKennel: Encoder[Kennel] = Encoder.instance { t =>
24+
Json.fromFields{
25+
Seq(
26+
t.name.map(v => "name" -> v.asJson),
27+
t.dogs.map(v => "dogs" -> v.asJson)
28+
).flatten
29+
}
30+
}
31+
implicit val decoderKennel: Decoder[Kennel] = Decoder.instance { c =>
32+
for {
33+
name <- c.downField("name").as[Option[String]]
34+
dogs <- c.downField("dogs").as[Option[Seq[Dog]]]
35+
} yield Kennel(
36+
name = name,
37+
dogs = dogs
38+
)
39+
}
40+
}
41+

0 commit comments

Comments
 (0)