Skip to content

Commit 96d8a59

Browse files
wiebrenclaude
andauthored
fix: [java] okhttp-gson allOf children of a oneOf/anyOf parent do not compile with additionalProperties (#25156)
With disallowAdditionalPropertiesIfNotPresent=false, an allOf child of a discriminated oneOf/anyOf parent was tagged x-inherits-additional-properties because the parent model has isAdditionalPropertiesTrue. The parent is rendered by oneof_model/anyof_model, which extends AbstractOpenApiSchema and declares no bag, so the child's @OverRide putAdditionalProperty and its super calls had nothing to bind to. Stop the ancestor walk at a oneOf/anyOf model: nothing is inherited through it, so the first pojo below it declares the bag again. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e17e024 commit 96d8a59

3 files changed

Lines changed: 109 additions & 1 deletion

File tree

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1243,9 +1243,14 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
12431243
if (isLibrary(OKHTTP_GSON)) {
12441244
// gson refuses a class whose hierarchy declares two fields bound to one JSON name, so
12451245
// the okhttp-gson additionalProperties bag is declared once, on the topmost ancestor
1246-
// that has one; every descendant inherits it instead of declaring its own copy
1246+
// that has one; every descendant inherits it instead of declaring its own copy.
1247+
// A oneOf/anyOf wrapper extends AbstractOpenApiSchema, not its own parent, and has no
1248+
// bag, so nothing is inherited through it
12471249
for (CodegenModel cm : getAllModels(objs).values()) {
12481250
for (CodegenModel ancestor = cm.getParentModel(); ancestor != null; ancestor = ancestor.getParentModel()) {
1251+
if (!ancestor.oneOf.isEmpty() || !ancestor.anyOf.isEmpty()) {
1252+
break;
1253+
}
12491254
if (ancestor.isAdditionalPropertiesTrue) {
12501255
cm.vendorExtensions.put("x-inherits-additional-properties", true);
12511256
break;

‎modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2263,6 +2263,31 @@ public void testAdditionalPropertiesFieldIsDeclaredOnceAcrossMultiLevelAllOfForG
22632263
.doesNotContain("Map<String, Object> additionalProperties;");
22642264
}
22652265

2266+
@Test
2267+
public void testAdditionalPropertiesFieldIsDeclaredBelowOneOfAndAnyOfParentsForGson() {
2268+
final Path output = generateOkHttpGsonWithAdditionalProperties("src/test/resources/3_0/java/okhttp-gson-additional-properties-composed-parent.yaml");
2269+
2270+
// a oneOf/anyOf wrapper has no bag to inherit, so its allOf children declare their own
2271+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/Pet.java"))
2272+
.content()
2273+
.contains("public class Pet extends AbstractOpenApiSchema")
2274+
.doesNotContain("Map<String, Object> additionalProperties;");
2275+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/ExtendedPet.java"))
2276+
.content()
2277+
.contains("public class ExtendedPet extends Pet {")
2278+
.contains("private Map<String, Object> additionalProperties;");
2279+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/ExtendedShape.java"))
2280+
.content()
2281+
.contains("public class ExtendedShape extends Shape {")
2282+
.contains("private Map<String, Object> additionalProperties;");
2283+
// below the first class that declares it, the bag is inherited as usual
2284+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/ExtendedPetLeaf.java"))
2285+
.content()
2286+
.contains("public class ExtendedPetLeaf extends ExtendedPet {")
2287+
.contains("public ExtendedPetLeaf putAdditionalProperty(String key, Object value) {")
2288+
.doesNotContain("Map<String, Object> additionalProperties;");
2289+
}
2290+
22662291
private Path generateOkHttpGsonWithAdditionalProperties(String inputSpec) {
22672292
final Path output = newTempFolder();
22682293
final CodegenConfigurator configurator = new CodegenConfigurator()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
openapi: 3.0.3
2+
info: {title: t, version: '1'}
3+
paths:
4+
/pet:
5+
get:
6+
responses:
7+
'200':
8+
description: ok
9+
content:
10+
application/json:
11+
schema: {$ref: '#/components/schemas/ExtendedPetLeaf'}
12+
/shape:
13+
get:
14+
responses:
15+
'200':
16+
description: ok
17+
content:
18+
application/json:
19+
schema: {$ref: '#/components/schemas/ExtendedShape'}
20+
components:
21+
schemas:
22+
Cat:
23+
type: object
24+
required: [petType]
25+
properties:
26+
petType: {type: string}
27+
meows: {type: boolean}
28+
Dog:
29+
type: object
30+
required: [petType]
31+
properties:
32+
petType: {type: string}
33+
barks: {type: boolean}
34+
Pet:
35+
oneOf:
36+
- $ref: '#/components/schemas/Cat'
37+
- $ref: '#/components/schemas/Dog'
38+
discriminator: {propertyName: petType}
39+
ExtendedPet:
40+
allOf:
41+
- $ref: '#/components/schemas/Pet'
42+
- type: object
43+
required: [petType]
44+
properties:
45+
petType: {type: string}
46+
name: {type: string}
47+
discriminator: {propertyName: petType}
48+
ExtendedPetLeaf:
49+
allOf:
50+
- $ref: '#/components/schemas/ExtendedPet'
51+
- type: object
52+
properties:
53+
nickname: {type: string}
54+
Circle:
55+
type: object
56+
required: [shapeType]
57+
properties:
58+
shapeType: {type: string}
59+
radius: {type: number}
60+
Square:
61+
type: object
62+
required: [shapeType]
63+
properties:
64+
shapeType: {type: string}
65+
side: {type: number}
66+
Shape:
67+
anyOf:
68+
- $ref: '#/components/schemas/Circle'
69+
- $ref: '#/components/schemas/Square'
70+
discriminator: {propertyName: shapeType}
71+
ExtendedShape:
72+
allOf:
73+
- $ref: '#/components/schemas/Shape'
74+
- type: object
75+
required: [shapeType]
76+
properties:
77+
shapeType: {type: string}
78+
colour: {type: string}

0 commit comments

Comments
 (0)