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

[csharp][java] Fix enum discriminator default value #19614

Merged
Merged
Show file tree
Hide file tree
Changes from 11 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 @@ -3080,6 +3080,7 @@ public CodegenModel fromModel(String name, Schema schema) {
listOLists.add(m.requiredVars);
listOLists.add(m.vars);
listOLists.add(m.allVars);
listOLists.add(m.readWriteVars);
for (List<CodegenProperty> theseVars : listOLists) {
for (CodegenProperty requiredVar : theseVars) {
if (discPropName.equals(requiredVar.baseName)) {
Expand Down Expand Up @@ -3113,6 +3114,37 @@ public CodegenModel fromModel(String name, Schema schema) {
return m;
}

protected static void setEnumDiscriminatorDefaultValue(CodegenModel model) {
david-marconis marked this conversation as resolved.
Show resolved Hide resolved
if (model.discriminator == null) {
return;
}
String discPropName = model.discriminator.getPropertyBaseName();
Stream.of(model.requiredVars, model.vars, model.allVars, model.readWriteVars)
.flatMap(List::stream)
.filter(v -> discPropName.equals(v.baseName))
.forEach(v -> v.defaultValue = getEnumValueForProperty(model.schemaName, model.discriminator, v));
}

protected static String getEnumValueForProperty(
david-marconis marked this conversation as resolved.
Show resolved Hide resolved
String modelName, CodegenDiscriminator discriminator, CodegenProperty var) {
if (!discriminator.getIsEnum() && !var.isEnum) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

In this case, I guess we shouldn't override the defaultValue at all. But currently we're overriding it with null. In line 3125. Or am I wrong?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I changed it to use the existing defaultValue instead of null.

If I set defaultValue to the enum discriminator in DefaultCodegen then there is only this change to dart-dio when I generate the samples:

diff --git a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/ChildWithNullable.md b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/
doc/ChildWithNullable.md
index 770494fcf4c..d3e5b182076 100644
--- a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/ChildWithNullable.md
+++ b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/ChildWithNullable.md
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
 ## Properties
 Name | Type | Description | Notes
 ------------ | ------------- | ------------- | -------------
-**type** | **String** |  | [optional]
+**type** | **String** |  | [optional] [default to ChildWithNullableTypeEnum.childWithNullable]
 **nullableProperty** | **String** |  | [optional]
 **otherProperty** | **String** |  | [optional]

I am not sure if this change is fine or if I should just keep it to Java and C# for now, but this could also be a problem that needs to be solved for other languages.

}
Map<String, String> mapping = Optional.ofNullable(discriminator.getMapping()).orElseGet(Collections::emptyMap);
for (Map.Entry<String, String> e : mapping.entrySet()) {
String schemaName = e.getValue().indexOf('/') < 0 ? e.getValue() : ModelUtils.getSimpleRef(e.getValue());
if (modelName.equals(schemaName)) {
return e.getKey();
}
}
Object values = var.allowableValues.get("values");
if (!(values instanceof List<?>)) {
return null;
}
List<?> valueList = (List<?>) values;
return valueList.stream().filter(o -> o.equals(modelName)).map(o -> (String) o).findAny().orElse(null);
}

protected void SortModelPropertiesByRequiredFlag(CodegenModel model) {
Comparator<CodegenProperty> comparator = new Comparator<CodegenProperty>() {
@Override
Expand Down Expand Up @@ -3523,15 +3555,6 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
.orElseGet(() -> typeMapping.get("string"));
discriminator.setPropertyType(propertyType);

// check to see if the discriminator property is an enum string
if (schema.getProperties() != null &&
schema.getProperties().get(discriminatorPropertyName) instanceof StringSchema) {
StringSchema s = (StringSchema) schema.getProperties().get(discriminatorPropertyName);
if (s.getEnum() != null && !s.getEnum().isEmpty()) { // it's an enum string
discriminator.setIsEnum(true);
}
}

discriminator.setMapping(sourceDiscriminator.getMapping());
List<MappedModel> uniqueDescendants = new ArrayList<>();
if (sourceDiscriminator.getMapping() != null && !sourceDiscriminator.getMapping().isEmpty()) {
Expand Down Expand Up @@ -3583,6 +3606,26 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
}
discriminator.getMappedModels().addAll(uniqueDescendants);

// check current schema, all parents and descendants to see if the discriminator property is an enum string
Stream<Schema> schemasToCheckForEnumDiscriminator = Stream.concat(
Stream.of(schema),
Stream.concat(
ModelUtils.isComposedSchema(schema)
? ModelUtils.getAllParentsName(schema, openAPI.getComponents().getSchemas(), true).stream()
: Stream.of(),
uniqueDescendants.stream().map(MappedModel::getModelName)
).flatMap(s -> Optional.ofNullable(ModelUtils.getSchema(openAPI, s)).stream())
);

boolean isEnum = schemasToCheckForEnumDiscriminator.anyMatch(s -> Optional
.ofNullable(s.getProperties())
.map(p -> (Schema) p.get(discriminatorPropertyName))
.map(s1 -> ModelUtils.getReferencedSchema(openAPI, s1))
.filter(s1 -> s1 instanceof StringSchema && s1.getEnum() != null && !s1.getEnum().isEmpty())
.isPresent()
);
discriminator.setIsEnum(isEnum);

return discriminator;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,7 @@ public CodegenModel fromModel(String name, Schema model) {

// additional import for different cases
addAdditionalImports(codegenModel, codegenModel.getComposedSchemas());
setEnumDiscriminatorDefaultValue(codegenModel);
return codegenModel;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ public String apiTestFileFolder() {
public CodegenModel fromModel(String name, Schema model) {
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
CodegenModel codegenModel = super.fromModel(name, model);
setEnumDiscriminatorDefaultValue(codegenModel);
if (allDefinitions != null && codegenModel != null && codegenModel.parent != null) {
final Schema<?> parentModel = allDefinitions.get(toModelName(codegenModel.parent));
if (parentModel != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,12 @@ public static String getParentName(Schema composedSchema, Map<String, Schema> al
* @return the name of the parent model
*/
public static List<String> getAllParentsName(Schema composedSchema, Map<String, Schema> allSchemas, boolean includeAncestors) {
return getAllParentsName(composedSchema, allSchemas, includeAncestors, new HashSet<>());
}

// Use a set of seen names to avoid infinite recursion
private static List<String> getAllParentsName(
Schema composedSchema, Map<String, Schema> allSchemas, boolean includeAncestors, Set<String> seenNames) {
List<Schema> interfaces = getInterfaces(composedSchema);
List<String> names = new ArrayList<String>();

Expand All @@ -1603,6 +1609,10 @@ public static List<String> getAllParentsName(Schema composedSchema, Map<String,
// get the actual schema
if (StringUtils.isNotEmpty(schema.get$ref())) {
String parentName = getSimpleRef(schema.get$ref());
if (seenNames.contains(parentName)) {
continue;
}
seenNames.add(parentName);
Schema s = allSchemas.get(parentName);
if (s == null) {
LOGGER.error("Failed to obtain schema from {}", parentName);
Expand All @@ -1611,7 +1621,7 @@ public static List<String> getAllParentsName(Schema composedSchema, Map<String,
// discriminator.propertyName is used or x-parent is used
names.add(parentName);
if (includeAncestors && isComposedSchema(s)) {
names.addAll(getAllParentsName(s, allSchemas, true));
names.addAll(getAllParentsName(s, allSchemas, true, seenNames));
}
} else {
// not a parent since discriminator.propertyName is not set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
{{/parcelableModel}}
{{/parent}}
{{#discriminator}}
{{#discriminator.isEnum}}
{{#readWriteVars}}{{#isDiscriminator}}{{#defaultValue}}
this.{{name}} = {{defaultValue}};
{{/defaultValue}}{{/isDiscriminator}}{{/readWriteVars}}
{{/discriminator.isEnum}}
{{^discriminator.isEnum}}
this.{{{discriminatorName}}} = this.getClass().getSimpleName();
{{/discriminator.isEnum}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,46 @@ public void test31specAdditionalPropertiesOfOneOf() throws IOException {
assertFileContains(modelFile.toPath(),
" Dictionary<string, ResponseResultsValue> results = default(Dictionary<string, ResponseResultsValue>");
}

@Test
public void testEnumDiscriminatorDefaultValueIsNotString() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
final OpenAPI openAPI = TestUtils.parseFlattenSpec(
"src/test/resources/3_0/enum_discriminator_inheritance.yaml");
final DefaultGenerator defaultGenerator = new DefaultGenerator();
final ClientOptInput clientOptInput = new ClientOptInput();
clientOptInput.openAPI(openAPI);
CSharpClientCodegen cSharpClientCodegen = new CSharpClientCodegen();
cSharpClientCodegen.setOutputDir(output.getAbsolutePath());
cSharpClientCodegen.setAutosetConstants(true);
clientOptInput.config(cSharpClientCodegen);
defaultGenerator.opts(clientOptInput);

Map<String, File> files = defaultGenerator.generate().stream()
.collect(Collectors.toMap(File::getPath, Function.identity()));

Map<String, String> expectedContents = Map.of(
"Cat", "PetTypeEnum petType = PetTypeEnum.Catty",
"Dog", "PetTypeEnum petType = PetTypeEnum.Dog",
"Gecko", "PetTypeEnum petType = PetTypeEnum.Gecko",
"Chameleon", "PetTypeEnum petType = PetTypeEnum.Camo",
"MiniVan", "CarType carType = CarType.MiniVan",
"CargoVan", "CarType carType = CarType.CargoVan",
"SUV", "CarType carType = CarType.SUV",
"Truck", "CarType carType = CarType.Truck",
"Sedan", "CarType carType = CarType.Sedan"

);
for (Map.Entry<String, String> e : expectedContents.entrySet()) {
String modelName = e.getKey();
String expectedContent = e.getValue();
File file = files.get(Paths
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", modelName + ".cs")
.toString()
);
assertNotNull(file, "Could not find file for model: " + modelName);
assertFileContains(file.toPath(), expectedContent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2287,6 +2287,39 @@ public void testAllOfWithSinglePrimitiveTypeRef() {
assertNull(files.get("pom.xml"));
}

@Test
public void testEnumDiscriminatorDefaultValueIsNotString() {
final Path output = newTempFolder();
final OpenAPI openAPI = TestUtils.parseFlattenSpec(
"src/test/resources/3_0/enum_discriminator_inheritance.yaml");
JavaClientCodegen codegen = new JavaClientCodegen();
codegen.setOutputDir(output.toString());

Map<String, File> files = new DefaultGenerator().opts(new ClientOptInput().openAPI(openAPI).config(codegen))
.generate().stream().collect(Collectors.toMap(File::getName, Function.identity()));

Map<String, String> expectedContents = Map.of(
"Cat", "this.petType = PetTypeEnum.CATTY",
"Dog", "this.petType = PetTypeEnum.DOG",
"Gecko", "this.petType = PetTypeEnum.GECKO",
"Chameleon", "this.petType = PetTypeEnum.CAMO",
"MiniVan", "this.carType = CarType.MINI_VAN",
"CargoVan", "this.carType = CarType.CARGO_VAN",
"SUV", "this.carType = CarType.SUV",
"Truck", "this.carType = CarType.TRUCK",
"Sedan", "this.carType = CarType.SEDAN"

);
for (Map.Entry<String, String> e : expectedContents.entrySet()) {
String modelName = e.getKey();
String expectedContent = e.getValue();
File entityFile = files.get(modelName + ".java");
assertNotNull(entityFile);
assertThat(entityFile).content().doesNotContain("Type = this.getClass().getSimpleName();");
assertThat(entityFile).content().contains(expectedContent);
}
}

@Test
public void testRestTemplateHandleURIEnum() {
String[] expectedInnerEnumLines = new String[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
openapi: 3.0.3
info:
title: Test schema with enum discriminator
version: v1
paths:
"/animal":
get:
responses:
'200':
description: OK
content:
application/json:
schema:
"$ref": "#/components/schemas/Animal"
components:
schemas:
Animal:
type: object
properties:
# Inline enum type
petType:
type: string
enum:
- Dog
- Catty
- Gecko
- Camo
discriminator:
propertyName: petType
# Mapping with implicit (Dog, Gecko), explicit ref (Catty -> Cat), and explicit schema name (Camo -> Chameleon)
mapping:
Catty: '#/components/schemas/Cat'
Camo: 'Chameleon'
required:
- petType
Cat:
type: object
allOf:
- $ref: '#/components/schemas/Animal'
properties:
meow:
type: string
Dog:
type: object
allOf:
- $ref: '#/components/schemas/Animal'
properties:
bark:
type: string
Lizard:
oneOf:
- $ref: '#/components/schemas/Gecko'
- $ref: '#/components/schemas/Chameleon'
Gecko:
type: object
allOf:
- $ref: '#/components/schemas/Animal'
properties:
lovesRocks:
type: string
Chameleon:
type: object
allOf:
- $ref: '#/components/schemas/Animal'
properties:
currentColor:
type: string
# Car inheritance tree: Car -> Truck -> SUV
# Car -> Van -> MiniVan
# Car -> Van -> CargoVan
# Car -> Sedan
Car:
type: object
required:
- carType
# Discriminator carType not defined in Car properties, but in child properties
discriminator:
propertyName: carType
CarType:
type: string
enum:
- Truck
- SUV
- Sedan
- MiniVan
- CargoVan
Truck:
type: object
allOf:
- $ref: '#/components/schemas/Car'
properties:
carType:
$ref: '#/components/schemas/CarType'
required:
- carType
SUV:
type: object
# SUV gets its discriminator property from Truck
allOf:
- $ref: '#/components/schemas/Truck'
Sedan:
type: object
allOf:
- $ref: '#/components/schemas/Car'
required:
- carType
properties:
carType:
$ref: '#/components/schemas/CarType'
Van:
oneOf:
- $ref: '#/components/schemas/MiniVan'
- $ref: '#/components/schemas/CargoVan'
MiniVan:
type: object
allOf:
- $ref: '#/components/schemas/Car'
properties:
carType:
$ref: '#/components/schemas/CarType'
required:
- carType
CargoVan:
type: object
allOf:
- $ref: '#/components/schemas/Car'
properties:
carType:
$ref: '#/components/schemas/CarType'
required:
- carType
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public static void validateJsonElement(JsonElement jsonElement) throws IOExcepti
protected EnumStrTypeEnum enumStrType;

public EnumStringDiscriminator() {

}

public EnumStringDiscriminator enumStrType(EnumStrTypeEnum enumStrType) {
Expand Down
Loading