Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use enum type for discriminator #13846

Merged
merged 18 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -3385,8 +3385,16 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
discriminator.setPropertyBaseName(sourceDiscriminator.getPropertyName());
discriminator.setPropertyGetter(toGetter(discriminator.getPropertyName()));

// FIXME: for now, we assume that the discriminator property is String
discriminator.setPropertyType(typeMapping.get("string"));
// FIXME: there are other ways to define the type of the discriminator property (inline
// for example). Handling those scenarios is too complicated for me, I'm leaving it for
// the future..
String propertyType =
Optional.ofNullable(schema.getProperties())
.map(p -> (Schema) p.get(discPropName))
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I know this isn't a variable you named, but I think full naming is better to disambiguate. discriminatorPropertyName might be more clear what the variable clearly represents

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Variable has been renamed, please review again

.map(Schema::get$ref)
.map(ModelUtils::getSimpleRef)
.orElseGet(() -> typeMapping.get("string"));
discriminator.setPropertyType(propertyType);
Copy link
Contributor

Choose a reason for hiding this comment

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

This will impact more than just the JavaSpring generation - can you regenerate the samples and tests to see what impact this has on other clients?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@welshm I've run the commands in the PR instructions above. No files changed to commit to Git. I'm not certain that it worked properly, but the scripts seemed to run without errors.


// check to see if the discriminator property is an enum string
if (schema.getProperties() != null &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import java.util.*;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.*;


Expand Down Expand Up @@ -1447,6 +1448,12 @@ public void testComposedSchemaOneOfDiscriminatorMap() {
hs.add(new CodegenDiscriminator.MappedModel(mn, mn));
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);

// ref oneOf models with enum property discriminator
modelName = "FruitOneOfEnumMappingDisc";
sc = openAPI.getComponents().getSchemas().get(modelName);
cm = codegen.fromModel(modelName, sc);
assertThat(cm.discriminator.getPropertyType()).isEqualTo("FruitTypeEnum");

// ref oneOf models with discriminator in the grandparent schemas of those oneof models
modelName = "FruitGrandparentDisc";
sc = openAPI.getComponents().getSchemas().get(modelName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,55 @@ public void testOneOfAndAllOf() throws IOException {
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal");
}

@Test
void testOneOfWithEnumDiscriminator() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');
OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/oneOfDiscriminator.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.setUseOneOfInterfaces(true);

ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
codegen.setHateoas(true);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
//generator.setGeneratorPropertyDefault(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false");

codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");

generator.opts(input).generate();

assertFileContains(
Paths.get(outputPath + "/src/main/java/org/openapitools/model/FruitOneOfEnumMappingDisc.java"),
"public FruitTypeEnum getFruitType();"
);
assertFileContains(
Paths.get(outputPath + "/src/main/java/org/openapitools/model/AppleOneOfEnumMappingDisc.java"),
"private FruitTypeEnum fruitType;",
"public FruitTypeEnum getFruitType() {"
);
assertFileContains(
Paths.get(outputPath + "/src/main/java/org/openapitools/model/BananaOneOfEnumMappingDisc.java"),
"private FruitTypeEnum fruitType;",
"public FruitTypeEnum getFruitType() {"
);
}

@Test
public void testTypeMappings() {
final SpringCodegen codegen = new SpringCodegen();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,37 @@ components:
type: integer
oneOf:
- $ref: '#/components/schemas/FruitType'
FruitTypeEnum:
type: string
enum: [APPLE, BANANA]
FruitOneOfEnumMappingDisc:
type: object
properties:
fruitType:
$ref: "#/components/schemas/FruitTypeEnum"
required: fruitType
Copy link
Contributor

Choose a reason for hiding this comment

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

The required property must be an array.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I see the problem now. I've pushed a fix.

oneOf:
- $ref: '#/components/schemas/AppleOneOfEnumMappingDisc'
- $ref: '#/components/schemas/BananaOneOfEnumMappingDisc'
discriminator:
propertyName: fruitType
mapping:
Copy link
Contributor

Choose a reason for hiding this comment

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

The mapping to the enum value might cause problems with the auto generated JsonSubType.

Copy link
Contributor

Choose a reason for hiding this comment

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

I actually generated a sample Project:

@JsonIgnoreProperties(
  value = "fruitType", // ignore manually set fruitType, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the fruitType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "fruitType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = AppleOneOfEnumMappingDisc.class, name = "APPLE"),
  @JsonSubTypes.Type(value = AppleOneOfEnumMappingDisc.class, name = "AppleOneOfEnumMappingDisc"),
  @JsonSubTypes.Type(value = BananaOneOfEnumMappingDisc.class, name = "BANANA"),
  @JsonSubTypes.Type(value = BananaOneOfEnumMappingDisc.class, name = "BananaOneOfEnumMappingDisc")
})

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-11-03T09:30:55.032940+01:00[Europe/Berlin]")
public interface FruitOneOfEnumMappingDisc {
    public FruitTypeEnum getFruitType();
}

@welshm Isn't there another issue / pr dealing with duplicated JsonSubTypes?

Copy link
Contributor

Choose a reason for hiding this comment

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

@bernie-schelberg-mywave Could you please add a enum based discriminator to Ione of the generated sample projects (for example bin/configs/spring-boot-oneof.yaml)?

Choose a reason for hiding this comment

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

I actually generated a sample Project:

@JsonIgnoreProperties(
  value = "fruitType", // ignore manually set fruitType, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the fruitType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "fruitType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = AppleOneOfEnumMappingDisc.class, name = "APPLE"),
  @JsonSubTypes.Type(value = AppleOneOfEnumMappingDisc.class, name = "AppleOneOfEnumMappingDisc"),
  @JsonSubTypes.Type(value = BananaOneOfEnumMappingDisc.class, name = "BANANA"),
  @JsonSubTypes.Type(value = BananaOneOfEnumMappingDisc.class, name = "BananaOneOfEnumMappingDisc")
})

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-11-03T09:30:55.032940+01:00[Europe/Berlin]")
public interface FruitOneOfEnumMappingDisc {
    public FruitTypeEnum getFruitType();
}

@welshm Isn't there another issue / pr dealing with duplicated JsonSubTypes?

I'm running openapi-generator version 6.2.0 and the above is the current behaviour when you add a mapping to the discriminator. Both the derived classes and the mapping values are added as JsonSubTypes.

The documentations seems to indicate that this is the expected behaviour.

The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cachescrubber Yes, there's another project for duplicated JsonSubTypes: #13815

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done (added enum based discriminator to samples)

APPLE: '#/components/schemas/AppleOneOfEnumMappingDisc'
BANANA: '#/components/schemas/BananaOneOfEnumMappingDisc'
AppleOneOfEnumMappingDisc:
type: object
required:
- seeds
properties:
seeds:
type: integer
BananaOneOfEnumMappingDisc:
type: object
required:
- length
properties:
length:
type: integer
FruitGrandparentDisc:
oneOf:
- $ref: '#/components/schemas/AppleGrandparentDisc'
Expand Down