Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

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

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

/**
* OneOf
*/

@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "", visible = true )
@JsonTypeInfo( use = JsonTypeInfo.Id.DEDUCTION )
@JsonSubTypes( { @JsonSubTypes.Type( value = Cola.class ), @JsonSubTypes.Type( value = Fanta.class ), } )

public interface OneOf
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
/**
* OneOfWithDiscriminator
*/

@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "sodaType", visible = true )
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "sodaType", visible = true )
@JsonSubTypes( {
@JsonSubTypes.Type( value = Cola.class, name = "Cola" ),
@JsonSubTypes.Type( value = Fanta.class, name = "Fanta" ), } )

public interface OneOfWithDiscriminator
{
public String getSodaType();
String getSodaType();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public is redundant for interfaces

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
/**
* OneOfWithDiscriminatorAndMapping
*/

@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "sodaType", visible = true )
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "sodaType", visible = true )
@JsonSubTypes( {
@JsonSubTypes.Type( value = Cola.class, name = "cool_cola" ),
@JsonSubTypes.Type( value = Fanta.class, name = "fancy_fanta" ),
@JsonSubTypes.Type( value = Cola.class, name = "Cola" ),
@JsonSubTypes.Type( value = Fanta.class, name = "Fanta" ), } )

public interface OneOfWithDiscriminatorAndMapping
{
public String getSodaType();
String getSodaType();
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ components:
discriminator:
propertyName: sodaType
mapping:
Cola: '#/components/schemas/Cola'
Fanta: '#/components/schemas/Fanta'
cool_cola: '#/components/schemas/Cola'
fancy_fanta: '#/components/schemas/Fanta'
OneOfWithDiscriminator:
oneOf:
- $ref: '#/components/schemas/Cola'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.sap.cloud.sdk.datamodel.openapi.sample.api;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import javax.annotation.Nonnull;

Expand All @@ -11,8 +11,8 @@
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.sap.cloud.sdk.datamodel.openapi.sample.model.AllOf;
import com.sap.cloud.sdk.datamodel.openapi.sample.model.AnyOf;
Expand All @@ -22,86 +22,152 @@
import com.sap.cloud.sdk.datamodel.openapi.sample.model.OneOfWithDiscriminator;
import com.sap.cloud.sdk.datamodel.openapi.sample.model.OneOfWithDiscriminatorAndMapping;

public class OneOfDeserializationTest
class OneOfDeserializationTest
{
String cola = """
private static final ObjectMapper objectMapper = newDefaultObjectMapper();

private static final Cola COLA_OBJECT = Cola.create().caffeine(true).sodaType("Cola");
private static final Fanta FANTA_OBJECT = Fanta.create().color("orange").sodaType("Fanta");
private static final String COLA_JSON = """
{
"sodaType": "Cola",
"caffeine": true
}""";
String fanta = """
private static final String FANTA_JSON = """
{
"sodaType": "Fanta",
"color": "orange"
}""";
private static final String UNKNOWN_JSON = """
{
"sodaType": "Sprite",
"someProperty": "someValue"
}""";

@Test
void oneOf()
throws JsonProcessingException
{
// useOneOfInterfaces is enabled and no discriminator is present, the deserialization will fail
// The fix is to use set a mixIn in the ObjectMapper
assertThatThrownBy(() -> newDefaultObjectMapper().readValue(cola, OneOf.class))
.isInstanceOf(InvalidTypeIdException.class)
.hasMessageContaining("Could not resolve subtype");
assertThatThrownBy(() -> newDefaultObjectMapper().readValue(fanta, OneOf.class))
.isInstanceOf(InvalidTypeIdException.class)
.hasMessageContaining("Could not resolve subtype");
var actual = objectMapper.readValue(COLA_JSON, OneOf.class);
assertThat(actual)
.describedAs("Object should automatically be deserialized as Cola with JSON subtype deduction")
.isInstanceOf(Cola.class)
.isEqualTo(COLA_OBJECT);

actual = objectMapper.readValue(FANTA_JSON, OneOf.class);
assertThat(actual)
.describedAs("Object should automatically be deserialized as Fanta with JSON subtype deduction")
.isInstanceOf(Fanta.class)
.isEqualTo(FANTA_OBJECT);

assertThatThrownBy(() -> objectMapper.readValue(UNKNOWN_JSON, OneOf.class))
.isInstanceOf(JsonProcessingException.class);
}

@Test
void oneOfWithDiscriminator()
throws JsonProcessingException
{
Cola oneOfCola = (Cola) newDefaultObjectMapper().readValue(cola, OneOfWithDiscriminator.class);
assertThat(oneOfCola.getSodaType()).isEqualTo("Cola");
assertThat(oneOfCola.isCaffeine()).isTrue();
var actual = objectMapper.readValue(COLA_JSON, OneOfWithDiscriminator.class);
assertThat(actual)
.describedAs(
"Object should automatically be deserialized as Cola using the class names as discriminator mapping values")
.isInstanceOf(Cola.class)
.isEqualTo(COLA_OBJECT);

Fanta oneOfFanta = (Fanta) newDefaultObjectMapper().readValue(fanta, OneOfWithDiscriminator.class);
assertThat(oneOfFanta.getSodaType()).isEqualTo("Fanta");
assertThat(oneOfFanta.getColor()).isEqualTo("orange");
actual = objectMapper.readValue(FANTA_JSON, OneOfWithDiscriminator.class);
assertThat(actual)
.describedAs(
"Object should automatically be deserialized as Fanta using the class names as discriminator mapping values")
.isInstanceOf(Fanta.class)
.isEqualTo(FANTA_OBJECT);

assertThatThrownBy(() -> objectMapper.readValue(UNKNOWN_JSON, OneOfWithDiscriminator.class));
}

@Test
void oneOfWithDiscriminatorAndMapping()
throws JsonProcessingException
{
Cola oneOfCola = (Cola) newDefaultObjectMapper().readValue(cola, OneOfWithDiscriminatorAndMapping.class);
assertThat(oneOfCola.getSodaType()).isEqualTo("Cola");
assertThat(oneOfCola.isCaffeine()).isTrue();
var jsonWithCustomMapping = """
{
"sodaType": "cool_cola",
"caffeine": true
}""";
var actual = objectMapper.readValue(jsonWithCustomMapping, OneOfWithDiscriminatorAndMapping.class);
assertThat(actual)
.describedAs(
"Object should automatically be deserialized as Cola using the explicit discriminator mapping values")
.isInstanceOf(Cola.class)
.isEqualTo(Cola.create().caffeine(true).sodaType("cool_cola"));

jsonWithCustomMapping = """
{
"sodaType": "fancy_fanta",
"color": "orange"
}""";
actual = objectMapper.readValue(jsonWithCustomMapping, OneOfWithDiscriminatorAndMapping.class);
assertThat(actual)
.describedAs(
"Object should automatically be deserialized as Fanta using the explicit discriminator mapping values")
.isInstanceOf(Fanta.class)
.isEqualTo(Fanta.create().color("orange").sodaType("fancy_fanta"));

assertThatThrownBy(() -> objectMapper.readValue(UNKNOWN_JSON, OneOfWithDiscriminatorAndMapping.class))
.isInstanceOf(JsonProcessingException.class);

Fanta oneOfFanta = (Fanta) newDefaultObjectMapper().readValue(fanta, OneOfWithDiscriminatorAndMapping.class);
assertThat(oneOfFanta.getSodaType()).isEqualTo("Fanta");
assertThat(oneOfFanta.getColor()).isEqualTo("orange");
}

@Test
void anyOf()
throws JsonProcessingException
{
AnyOf anyOfCola = newDefaultObjectMapper().readValue(cola, AnyOf.class);
AnyOf anyOfCola = objectMapper.readValue(COLA_JSON, AnyOf.class);
assertThat(anyOfCola.getSodaType()).isEqualTo("Cola");
assertThat(anyOfCola.isCaffeine()).isTrue();
assertThat(anyOfCola.getColor()).isNull();

AnyOf anyOfFanta = newDefaultObjectMapper().readValue(fanta, AnyOf.class);
AnyOf anyOfFanta = objectMapper.readValue(FANTA_JSON, AnyOf.class);
assertThat(anyOfFanta.getSodaType()).isEqualTo("Fanta");
assertThat(anyOfFanta.getColor()).isEqualTo("orange");
assertThat(anyOfFanta.isCaffeine()).isNull();
}

@Test
void allOf()
throws JsonProcessingException
{
AllOf allOfCola = newDefaultObjectMapper().readValue(cola, AllOf.class);
AllOf allOfCola = objectMapper.readValue(COLA_JSON, AllOf.class);
assertThat(allOfCola.getSodaType()).isEqualTo("Cola");
assertThat(allOfCola.isCaffeine()).isTrue();
assertThat(allOfCola.getColor()).isNull();

AllOf allOfFanta = newDefaultObjectMapper().readValue(fanta, AllOf.class);
AllOf allOfFanta = objectMapper.readValue(FANTA_JSON, AllOf.class);
assertThat(allOfFanta.getSodaType()).isEqualTo("Fanta");
assertThat(allOfFanta.getColor()).isEqualTo("orange");
assertThat(allOfFanta.isCaffeine()).isNull();
}

@Test
void testColaSerialization()
throws JsonProcessingException
{
var expected = objectMapper.readValue(COLA_JSON, JsonNode.class);
var actual = objectMapper.valueToTree(expected);

assertThat(actual).isEqualTo(expected);
}

@Test
void testFantaSerialization()
throws JsonProcessingException
{
var expected = objectMapper.readValue(FANTA_JSON, JsonNode.class);
var actual = objectMapper.valueToTree(expected);

assertThat(actual).isEqualTo(expected);
}

/**
* Taken from {@link com.sap.cloud.sdk.services.openapi.apiclient.ApiClient}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ private static void setGlobalSettings( @Nonnull final GenerationConfiguration co
if( configuration.isGenerateModels() ) {
GlobalSettings.setProperty(CodegenConstants.MODELS, "");
}
if( configuration.isDebugModels() ) {
GlobalSettings.setProperty("debugModels", "true");
}
GlobalSettings.setProperty(CodegenConstants.MODEL_TESTS, Boolean.FALSE.toString());
GlobalSettings.setProperty(CodegenConstants.MODEL_DOCS, Boolean.FALSE.toString());
GlobalSettings.setProperty(CodegenConstants.API_TESTS, Boolean.FALSE.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public class GenerationConfiguration
@Builder.Default
boolean generateApis = true;

@Builder.Default
boolean debugModels = false;

/**
* Indicates whether to use the default SAP copyright header for generated files.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
{{>additionalOneOfTypeAnnotations}}{{>typeInfoAnnotation}}{{>xmlAnnotation}}
public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{propertyType}} {{propertyGetter}}();
{{/discriminator}}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
{{#jackson}}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
{{#discriminator.mappedModels}}
{{#-first}}
{{#discriminator}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "{{{discriminator.propertyBaseName}}}", visible = true)
{{/discriminator}}
{{^discriminator}}
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
{{/discriminator}}
@JsonSubTypes({
{{/-first}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"),
{{#-last}}
})
{{/-last}}
{{#discriminator.mappedModels}}
@JsonSubTypes.Type(value = {{modelName}}.class{{#discriminator}}, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"{{/discriminator}}),
{{/discriminator.mappedModels}}
{{/jackson}}
{{^discriminator.mappedModels}}
{{#model.oneOf}}
@JsonSubTypes.Type(value = {{.}}.class),
{{/model.oneOf}}
{{/discriminator.mappedModels}}
})
{{/jackson}}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ void integrationTests( final TestCase testCase, @TempDir final Path path )
final var generationConfiguration =
GenerationConfiguration
.builder()
// .debugModels(true) enable this for better mustache file debugging
.apiPackage(testCase.apiPackageName)
.generateApis(testCase.generateApis)
.modelPackage(testCase.modelPackageName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
/**
* OneOfWithDiscriminator
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "sodaType", visible = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "sodaType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cola.class, name = "Cola"),
@JsonSubTypes.Type(value = Fanta.class, name = "Fanta"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
/**
* OneOfWithDiscriminatorAndMapping
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "sodaType", visible = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "sodaType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cola.class, name = "Cola"),
@JsonSubTypes.Type(value = Fanta.class, name = "Fanta"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
/**
* OneOfWithDiscriminator
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "sodaType", visible = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "sodaType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cola.class, name = "Cola"),
@JsonSubTypes.Type(value = Fanta.class, name = "Fanta"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
/**
* OneOfWithDiscriminatorAndMapping
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "sodaType", visible = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "sodaType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cola.class, name = "Cola"),
@JsonSubTypes.Type(value = Fanta.class, name = "Fanta"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ components:
discriminator:
propertyName: sodaType
mapping:
Cola: '#/components/schemas/Cola'
Fanta: '#/components/schemas/Fanta'
cool_cola: '#/components/schemas/Cola'
fancy_fanta: '#/components/schemas/Fanta'
OneOfWithDiscriminator:
oneOf:
- $ref: '#/components/schemas/Cola'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@
/**
* OneOf
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "", visible = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cola.class),
@JsonSubTypes.Type(value = Fanta.class),
})

public interface OneOf {
}
Expand Down
Loading
Loading