Skip to content

Commit 1564ded

Browse files
authored
test: [OpenAPI-Gen] Deserialising oneOf and anyOf with array of objects (#752)
Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent ce30d56 commit 1564ded

File tree

5 files changed

+129
-50
lines changed

5 files changed

+129
-50
lines changed

datamodel/openapi/openapi-api-sample/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@
9898
<artifactId>jackson-datatype-jsr310</artifactId>
9999
<scope>test</scope>
100100
</dependency>
101+
<dependency>
102+
<groupId>org.junit.jupiter</groupId>
103+
<artifactId>junit-jupiter-params</artifactId>
104+
</dependency>
101105
</dependencies>
102106
<build>
103107
<plugins>

datamodel/openapi/openapi-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/openapi/sample/model/FantaFlavor.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package com.sap.cloud.sdk.datamodel.openapi.sample.model;
1717

18+
import java.util.List;
19+
1820
import javax.annotation.Nonnull;
1921

2022
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -45,22 +47,22 @@ static InnerInteger create( @Nonnull final Integer val )
4547
}
4648

4749
/**
48-
* Helper class to create a FantaFlavorOneOf that implements {@link FantaFlavor}.
50+
* Helper class to create a FlavorType that implements {@link FantaFlavor}.
4951
*/
50-
record InnerFantaFlavorOneOf(@com.fasterxml.jackson.annotation.JsonValue @Nonnull FantaFlavorOneOf value) implements FantaFlavor {}
52+
record InnerFlavorType(@com.fasterxml.jackson.annotation.JsonValue @Nonnull FlavorType value) implements FantaFlavor {}
5153

5254
/**
53-
* Creator to enable deserialization of a FantaFlavorOneOf.
55+
* Creator to enable deserialization of a FlavorType.
5456
*
5557
* @param val
5658
* the value to use
57-
* @return a new instance of {@link InnerFantaFlavorOneOf}.
59+
* @return a new instance of {@link InnerFlavorType}.
5860
*/
5961
@com.fasterxml.jackson.annotation.JsonCreator
6062
@Nonnull
61-
static InnerFantaFlavorOneOf create( @Nonnull final FantaFlavorOneOf val )
63+
static InnerFlavorType create( @Nonnull final FlavorType val )
6264
{
63-
return new InnerFantaFlavorOneOf(val);
65+
return new InnerFlavorType(val);
6466
}
6567

6668
/**
@@ -82,4 +84,23 @@ static InnerString create( @Nonnull final String val )
8284
return new InnerString(val);
8385
}
8486

87+
/**
88+
* Helper class to create a list of FlavorType that implements {@link FantaFlavor}.
89+
*/
90+
record InnerFlavorTypes(@com.fasterxml.jackson.annotation.JsonValue @Nonnull List<FlavorType> values) implements FantaFlavor {}
91+
92+
/**
93+
* Creator to enable deserialization of a list of FlavorType.
94+
*
95+
* @param val
96+
* the value to use
97+
* @return a new instance of {@link InnerFlavorTypes}.
98+
*/
99+
@com.fasterxml.jackson.annotation.JsonCreator
100+
@Nonnull
101+
static InnerFlavorTypes create( @Nonnull final List<FlavorType> val )
102+
{
103+
return new InnerFlavorTypes(val);
104+
}
105+
85106
}
Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
import com.fasterxml.jackson.annotation.JsonProperty;
3131

3232
/**
33-
* FantaFlavorOneOf
33+
* FlavorType
3434
*/
3535
// CHECKSTYLE:OFF
36-
public class FantaFlavorOneOf
36+
public class FlavorType
3737
// CHECKSTYLE:ON
3838
{
3939
@JsonProperty( "intensity" )
@@ -47,30 +47,30 @@ public class FantaFlavorOneOf
4747
private final Map<String, Object> cloudSdkCustomFields = new LinkedHashMap<>();
4848

4949
/**
50-
* Default constructor for FantaFlavorOneOf.
50+
* Default constructor for FlavorType.
5151
*/
52-
protected FantaFlavorOneOf()
52+
protected FlavorType()
5353
{
5454
}
5555

5656
/**
57-
* Set the intensity of this {@link FantaFlavorOneOf} instance and return the same instance.
57+
* Set the intensity of this {@link FlavorType} instance and return the same instance.
5858
*
5959
* @param intensity
60-
* The intensity of this {@link FantaFlavorOneOf}
61-
* @return The same instance of this {@link FantaFlavorOneOf} class
60+
* The intensity of the flavor
61+
* @return The same instance of this {@link FlavorType} class
6262
*/
6363
@Nonnull
64-
public FantaFlavorOneOf intensity( @Nullable final Integer intensity )
64+
public FlavorType intensity( @Nullable final Integer intensity )
6565
{
6666
this.intensity = intensity;
6767
return this;
6868
}
6969

7070
/**
71-
* Get intensity
71+
* The intensity of the flavor
7272
*
73-
* @return intensity The intensity of this {@link FantaFlavorOneOf} instance.
73+
* @return intensity The intensity of this {@link FlavorType} instance.
7474
*/
7575
@Nonnull
7676
public Integer getIntensity()
@@ -79,34 +79,34 @@ public Integer getIntensity()
7979
}
8080

8181
/**
82-
* Set the intensity of this {@link FantaFlavorOneOf} instance.
82+
* Set the intensity of this {@link FlavorType} instance.
8383
*
8484
* @param intensity
85-
* The intensity of this {@link FantaFlavorOneOf}
85+
* The intensity of the flavor
8686
*/
8787
public void setIntensity( @Nullable final Integer intensity )
8888
{
8989
this.intensity = intensity;
9090
}
9191

9292
/**
93-
* Set the nuance of this {@link FantaFlavorOneOf} instance and return the same instance.
93+
* Set the nuance of this {@link FlavorType} instance and return the same instance.
9494
*
9595
* @param nuance
96-
* The nuance of this {@link FantaFlavorOneOf}
97-
* @return The same instance of this {@link FantaFlavorOneOf} class
96+
* The nuance of the flavor
97+
* @return The same instance of this {@link FlavorType} class
9898
*/
9999
@Nonnull
100-
public FantaFlavorOneOf nuance( @Nullable final String nuance )
100+
public FlavorType nuance( @Nullable final String nuance )
101101
{
102102
this.nuance = nuance;
103103
return this;
104104
}
105105

106106
/**
107-
* Get nuance
107+
* The nuance of the flavor
108108
*
109-
* @return nuance The nuance of this {@link FantaFlavorOneOf} instance.
109+
* @return nuance The nuance of this {@link FlavorType} instance.
110110
*/
111111
@Nonnull
112112
public String getNuance()
@@ -115,18 +115,18 @@ public String getNuance()
115115
}
116116

117117
/**
118-
* Set the nuance of this {@link FantaFlavorOneOf} instance.
118+
* Set the nuance of this {@link FlavorType} instance.
119119
*
120120
* @param nuance
121-
* The nuance of this {@link FantaFlavorOneOf}
121+
* The nuance of the flavor
122122
*/
123123
public void setNuance( @Nullable final String nuance )
124124
{
125125
this.nuance = nuance;
126126
}
127127

128128
/**
129-
* Get the names of the unrecognizable properties of the {@link FantaFlavorOneOf}.
129+
* Get the names of the unrecognizable properties of the {@link FlavorType}.
130130
*
131131
* @return The set of properties names
132132
*/
@@ -138,7 +138,7 @@ public Set<String> getCustomFieldNames()
138138
}
139139

140140
/**
141-
* Get the value of an unrecognizable property of this {@link FantaFlavorOneOf} instance.
141+
* Get the value of an unrecognizable property of this {@link FlavorType} instance.
142142
*
143143
* @deprecated Use {@link #toMap()} instead.
144144
* @param name
@@ -153,13 +153,13 @@ public Object getCustomField( @Nonnull final String name )
153153
throws NoSuchElementException
154154
{
155155
if( !cloudSdkCustomFields.containsKey(name) ) {
156-
throw new NoSuchElementException("FantaFlavorOneOf has no field with name '" + name + "'.");
156+
throw new NoSuchElementException("FlavorType has no field with name '" + name + "'.");
157157
}
158158
return cloudSdkCustomFields.get(name);
159159
}
160160

161161
/**
162-
* Get the value of all properties of this {@link FantaFlavorOneOf} instance including unrecognized properties.
162+
* Get the value of all properties of this {@link FlavorType} instance including unrecognized properties.
163163
*
164164
* @return The map of all properties
165165
*/
@@ -176,8 +176,8 @@ public Map<String, Object> toMap()
176176
}
177177

178178
/**
179-
* Set an unrecognizable property of this {@link FantaFlavorOneOf} instance. If the map previously contained a
180-
* mapping for the key, the old value is replaced by the specified value.
179+
* Set an unrecognizable property of this {@link FlavorType} instance. If the map previously contained a mapping for
180+
* the key, the old value is replaced by the specified value.
181181
*
182182
* @param customFieldName
183183
* The name of the property
@@ -199,10 +199,10 @@ public boolean equals( @Nullable final java.lang.Object o )
199199
if( o == null || getClass() != o.getClass() ) {
200200
return false;
201201
}
202-
final FantaFlavorOneOf fantaFlavorOneOf = (FantaFlavorOneOf) o;
203-
return Objects.equals(this.cloudSdkCustomFields, fantaFlavorOneOf.cloudSdkCustomFields)
204-
&& Objects.equals(this.intensity, fantaFlavorOneOf.intensity)
205-
&& Objects.equals(this.nuance, fantaFlavorOneOf.nuance);
202+
final FlavorType flavorType = (FlavorType) o;
203+
return Objects.equals(this.cloudSdkCustomFields, flavorType.cloudSdkCustomFields)
204+
&& Objects.equals(this.intensity, flavorType.intensity)
205+
&& Objects.equals(this.nuance, flavorType.nuance);
206206
}
207207

208208
@Override
@@ -216,7 +216,7 @@ public int hashCode()
216216
public String toString()
217217
{
218218
final StringBuilder sb = new StringBuilder();
219-
sb.append("class FantaFlavorOneOf {\n");
219+
sb.append("class FlavorType {\n");
220220
sb.append(" intensity: ").append(toIndentedString(intensity)).append("\n");
221221
sb.append(" nuance: ").append(toIndentedString(nuance)).append("\n");
222222
cloudSdkCustomFields
@@ -237,11 +237,11 @@ private String toIndentedString( final java.lang.Object o )
237237
}
238238

239239
/**
240-
* Create a new {@link FantaFlavorOneOf} instance. No arguments are required.
240+
* Create a new {@link FlavorType} instance. No arguments are required.
241241
*/
242-
public static FantaFlavorOneOf create()
242+
public static FlavorType create()
243243
{
244-
return new FantaFlavorOneOf();
244+
return new FlavorType();
245245
}
246246

247247
}

datamodel/openapi/openapi-api-sample/src/main/resources/sodastore.yaml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,19 @@ components:
165165
oneOf:
166166
- type: string
167167
- type: integer
168-
- type: object
169-
properties:
170-
intensity:
171-
type: integer
172-
nuance:
173-
type: string
168+
- type: array
169+
items:
170+
$ref: '#/components/schemas/FlavorType'
171+
- $ref: '#/components/schemas/FlavorType'
172+
FlavorType:
173+
type: object
174+
properties:
175+
intensity:
176+
type: integer
177+
description: The intensity of the flavor
178+
nuance:
179+
type: string
180+
description: The nuance of the flavor
174181
paths:
175182
/sodas:
176183
get:

datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/OneOfDeserializationTest.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.sap.cloud.sdk.datamodel.openapi.sample.api;
22

3+
import static org.assertj.core.api.Assertions.assertThat;
34
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4-
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
5+
6+
import java.util.stream.Stream;
57

68
import javax.annotation.Nonnull;
79

810
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.params.ParameterizedTest;
12+
import org.junit.jupiter.params.provider.MethodSource;
913
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
1014

1115
import com.fasterxml.jackson.annotation.JsonAutoDetect;
@@ -20,7 +24,7 @@
2024
import com.sap.cloud.sdk.datamodel.openapi.sample.model.Cola;
2125
import com.sap.cloud.sdk.datamodel.openapi.sample.model.Fanta;
2226
import com.sap.cloud.sdk.datamodel.openapi.sample.model.FantaFlavor;
23-
import com.sap.cloud.sdk.datamodel.openapi.sample.model.FantaFlavorOneOf;
27+
import com.sap.cloud.sdk.datamodel.openapi.sample.model.FlavorType;
2428
import com.sap.cloud.sdk.datamodel.openapi.sample.model.Foo;
2529
import com.sap.cloud.sdk.datamodel.openapi.sample.model.OneOf;
2630
import com.sap.cloud.sdk.datamodel.openapi.sample.model.OneOfWithDiscriminator;
@@ -37,7 +41,7 @@ class OneOfDeserializationTest
3741
.create()
3842
.color("orange")
3943
.sodaType("Fanta")
40-
.flavor(new FantaFlavor.InnerFantaFlavorOneOf(FantaFlavorOneOf.create().intensity(3).nuance("wood")));
44+
.flavor(new FantaFlavor.InnerFlavorType(FlavorType.create().intensity(3).nuance("wood")));
4145
private static final String COLA_JSON = """
4246
{
4347
"sodaType": "Cola",
@@ -49,6 +53,15 @@ class OneOfDeserializationTest
4953
"color": "orange",
5054
"flavor": {"intensity":3,"nuance":"wood"}
5155
}""";
56+
private static final String FANTA_FLAVOR_ARRAY_JSON = """
57+
{
58+
"sodaType": "Fanta",
59+
"color": "orange",
60+
"flavor": [
61+
{"intensity":3,"nuance":"wood"},
62+
{"intensity":5,"nuance":"citrus"}
63+
]
64+
}""";
5265
private static final String UNKNOWN_JSON = """
5366
{
5467
"sodaType": "Sprite",
@@ -93,7 +106,8 @@ void oneOfWithDiscriminator()
93106
.isInstanceOf(Fanta.class)
94107
.isEqualTo(FANTA_OBJECT);
95108

96-
assertThatThrownBy(() -> objectMapper.readValue(UNKNOWN_JSON, OneOfWithDiscriminator.class));
109+
assertThatThrownBy(() -> objectMapper.readValue(UNKNOWN_JSON, OneOfWithDiscriminator.class))
110+
.isInstanceOf(JsonProcessingException.class);
97111
}
98112

99113
@Test
@@ -126,6 +140,32 @@ void oneOfWithDiscriminatorAndMapping()
126140

127141
assertThatThrownBy(() -> objectMapper.readValue(UNKNOWN_JSON, OneOfWithDiscriminatorAndMapping.class))
128142
.isInstanceOf(JsonProcessingException.class);
143+
}
144+
145+
static Stream<Class<?>> oneOfStrategiesProvider()
146+
{
147+
return Stream.of(OneOf.class, OneOfWithDiscriminator.class, OneOfWithDiscriminatorAndMapping.class);
148+
}
149+
150+
@ParameterizedTest( name = "Deserialization with strategy: {0}" )
151+
@MethodSource( "oneOfStrategiesProvider" )
152+
void oneOfWithNestedArrayOfObjects( Class<?> strategy )
153+
throws JsonProcessingException
154+
{
155+
Object actual = objectMapper.readValue(FANTA_FLAVOR_ARRAY_JSON, strategy);
156+
157+
assertThat(actual)
158+
.describedAs("Object should automatically be deserialized as Fanta with JSON subtype deduction")
159+
.isInstanceOf(Fanta.class);
160+
var fanta = (Fanta) actual;
161+
assertThat(fanta.getFlavor())
162+
.describedAs("Flavor should be deserialized as wrapper class for a list of FlavorType instances")
163+
.isInstanceOf(FantaFlavor.InnerFlavorTypes.class);
164+
var flavorTypes = (FantaFlavor.InnerFlavorTypes) fanta.getFlavor();
165+
assertThat(flavorTypes.values())
166+
.describedAs("Flavor should be deserialized as a list of FlavorType instances")
167+
.isNotEmpty()
168+
.allMatch(FlavorType.class::isInstance);
129169

130170
}
131171

@@ -142,6 +182,13 @@ void anyOf()
142182
assertThat(anyOfFanta.getSodaType()).isEqualTo("Fanta");
143183
assertThat(anyOfFanta.getColor()).isEqualTo("orange");
144184
assertThat(anyOfFanta.isCaffeine()).isNull();
185+
186+
AnyOf actual = objectMapper.readValue(FANTA_FLAVOR_ARRAY_JSON, AnyOf.class);
187+
188+
assertThat(actual.getSodaType()).isEqualTo("Fanta");
189+
assertThat(actual.getColor()).isEqualTo("orange");
190+
assertThat(actual.getFlavor()).isInstanceOf(FantaFlavor.InnerFlavorTypes.class);
191+
assertThat(((FantaFlavor.InnerFlavorTypes) actual.getFlavor()).values()).allMatch(FlavorType.class::isInstance);
145192
}
146193

147194
@Test

0 commit comments

Comments
 (0)