-
Notifications
You must be signed in to change notification settings - Fork 6k
#6545: Fixing several bugs for Java model generation for enums #6579
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
base: master
Are you sure you want to change the base?
Changes from all commits
be86ef1
faae261
464e93d
d11df6f
04b61a0
7864eee
5f82d88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,7 +60,7 @@ public enum {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum | |
|
||
@Override | ||
public {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { | ||
{{{dataType}}} value = jsonReader.{{#isInteger}}nextInt(){{/isInteger}}{{^isInteger}}next{{{dataType}}}(){{/isInteger}}; | ||
{{{dataType}}} value = {{#isFloat}}new Float(jsonReader.nextDouble()); // floats shall be restricted so this cast is safe{{/isFloat}}{{^isFloat}}jsonReader.{{#isInteger}}nextInt(){{/isInteger}}{{^isInteger}}next{{{dataType}}}(){{/isInteger}};{{/isFloat}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we overflow here while converting double => float? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. That is not the best solution. To make it better the JsonReader functionality has to be approximately reimplemented. Something like this: final double nextDouble = jsonReader.nextDouble();
float result = (float) nextDouble;
if (Float.isInfinite(result)) {
throw new MalformedJsonException("JSON forbids infinities: " + result + " for " + nextDouble);
} (Compare with: https://github.com/google/gson/blob/b1fb9ca9a1bea5440bc6a5b506ccf67236b08243/gson/src/main/java/com/google/gson/stream/JsonReader.java#L909) But this seems like overkill. My thoughts were: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ok, seems reasonable. |
||
return {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue(String.valueOf(value)); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,9 +7,18 @@ | |
import io.swagger.parser.SwaggerParser; | ||
|
||
import org.testng.Assert; | ||
import org.testng.annotations.DataProvider; | ||
import org.testng.annotations.Test; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
import com.google.common.collect.ImmutableMap; | ||
import com.google.common.collect.Sets; | ||
|
||
public class CodegenTest { | ||
|
||
|
@@ -217,7 +226,7 @@ public void fileResponeseTest() { | |
Assert.assertTrue(op.responses.get(0).isFile); | ||
Assert.assertTrue(op.isResponseFile); | ||
} | ||
|
||
@Test(description = "discriminator is present") | ||
public void discriminatorTest() { | ||
final Swagger model = parseAndPrepareSwagger("src/test/resources/2_0/discriminatorTest.json"); | ||
|
@@ -359,39 +368,39 @@ public void localConsumesAndProducesTest() { | |
final String path = "/tests/localConsumesAndProduces"; | ||
final Operation p = model.getPaths().get(path).getGet(); | ||
CodegenOperation op = codegen.fromOperation(path, "get", p, model.getDefinitions(), model); | ||
|
||
Assert.assertTrue(op.hasConsumes); | ||
Assert.assertEquals(op.consumes.size(), 1); | ||
Assert.assertEquals(op.consumes.get(0).get("mediaType"), "application/json"); | ||
Assert.assertTrue(op.hasProduces); | ||
Assert.assertEquals(op.produces.size(), 1); | ||
Assert.assertEquals(op.produces.get(0).get("mediaType"), "application/json"); | ||
} | ||
|
||
@Test(description = "use spec consumes and produces") | ||
public void globalConsumesAndProducesTest() { | ||
final Swagger model = parseAndPrepareSwagger("src/test/resources/2_0/globalConsumesAndProduces.json"); | ||
final DefaultCodegen codegen = new DefaultCodegen(); | ||
final String path = "/tests/globalConsumesAndProduces"; | ||
final Operation p = model.getPaths().get(path).getGet(); | ||
CodegenOperation op = codegen.fromOperation(path, "get", p, model.getDefinitions(), model); | ||
|
||
Assert.assertTrue(op.hasConsumes); | ||
Assert.assertEquals(op.consumes.size(), 1); | ||
Assert.assertEquals(op.consumes.get(0).get("mediaType"), "application/global_consumes"); | ||
Assert.assertTrue(op.hasProduces); | ||
Assert.assertEquals(op.produces.size(), 1); | ||
Assert.assertEquals(op.produces.get(0).get("mediaType"), "application/global_produces"); | ||
} | ||
|
||
@Test(description = "use operation consumes and produces (reset in operation with empty array)") | ||
public void localResetConsumesAndProducesTest() { | ||
final Swagger model = parseAndPrepareSwagger("src/test/resources/2_0/globalConsumesAndProduces.json"); | ||
final DefaultCodegen codegen = new DefaultCodegen(); | ||
final String path = "/tests/localResetConsumesAndProduces"; | ||
final Operation p = model.getPaths().get(path).getGet(); | ||
CodegenOperation op = codegen.fromOperation(path, "get", p, model.getDefinitions(), model); | ||
|
||
Assert.assertNotNull(op); | ||
Assert.assertFalse(op.hasConsumes); | ||
Assert.assertNull(op.consumes); | ||
|
@@ -417,4 +426,110 @@ public void deprecatedParamTest() { | |
|
||
Assert.assertTrue(op.isDeprecated); | ||
} | ||
|
||
@Test(description = "Setting the values for defined object") | ||
public void fromModelObjectTest() throws NoSuchFieldException, IllegalAccessException { | ||
final Swagger swagger = parseAndPrepareSwagger("src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml"); | ||
final Model model = swagger.getDefinitions().get("Category"); | ||
final CodegenModel codegenModel = new DefaultCodegen().fromModel("Category", model, swagger.getDefinitions()); | ||
|
||
// test the not set fields | ||
for (final String fieldName : Arrays.asList("parent", "parentSchema", "interfaces", "parentModel", "interfaceModels", | ||
"children", "title", "description", "xmlPrefix", "xmlNamespace", "unescapedDescription", "discriminator", | ||
"defaultValue", "arrayModelType", "allowableValues", "externalDocs", "additionalPropertiesType")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
Assert.assertNull(field.get(codegenModel), "Field: " + fieldName); | ||
} | ||
// test the boolean fields | ||
for (final String fieldName : Arrays.asList("isAlias", "emptyVars", "hasMoreModels", "hasEnums", "isEnum", | ||
"hasRequired", "isArrayModel", "hasChildren", "isInteger", "isFloat", "hasOnlyReadOnly")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
Assert.assertFalse(field.getBoolean(codegenModel), "Field: " + fieldName); | ||
} | ||
// test the variables fields - they have all the same content | ||
for (final String varsFieldName : Arrays.asList("vars", "optionalVars", "readWriteVars", "allVars")) { | ||
final Field field = CodegenModel.class.getField(varsFieldName); | ||
final List<CodegenProperty> vars = (List<CodegenProperty>) field.get(codegenModel); | ||
|
||
final Set<List<String>> variables = new HashSet<>(); | ||
for (final CodegenProperty codegenProperty : vars) { | ||
variables.add(Arrays.asList(codegenProperty.baseName, codegenProperty.datatype)); | ||
} | ||
Assert.assertEquals(variables, new HashSet<>( | ||
Arrays.asList(Arrays.asList("id", "Long"), Arrays.asList("name", "String")) | ||
), "Field: " + varsFieldName); | ||
} | ||
// test the class names which shall all be "Category" | ||
for (final String fieldName : Arrays.asList("name", "classname", "classVarName", "xmlName", "classFilename")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
Assert.assertEquals(field.get(codegenModel), "Category", "Field: " + fieldName); | ||
} | ||
// test the empty collections | ||
for (final String fieldName : Arrays.asList("requiredVars", "readOnlyVars", "parentVars", "mandatory", "allMandatory")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
final Collection collection = (Collection) field.get(codegenModel); | ||
Assert.assertTrue(collection.isEmpty(), "Field: " + fieldName); | ||
} | ||
|
||
Assert.assertEquals(codegenModel.dataType, "object"); | ||
Assert.assertTrue(codegenModel.hasVars); | ||
Assert.assertTrue(codegenModel.hasOptional); | ||
Assert.assertTrue(codegenModel.vendorExtensions.isEmpty()); | ||
Assert.assertEquals(codegenModel.imports, Sets.newHashSet("string")); | ||
Assert.assertNotNull(codegenModel.modelJson); | ||
} | ||
|
||
@DataProvider(name = "fromModelEnumTest") | ||
public static Object[][] fromModelEnumTest() { | ||
return new Object[][]{ | ||
{"IntegerEnum", true, false, "integer", Arrays.asList("1", "2", "3") /* @see: https://github.com/swagger-api/swagger-core/issues/2449 */}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm not sure that we need those links in comments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this test fails it is probably because the mentioned bug was solved and the swagger-core was updated in the swagger-codegen. This link shall help to understand that there was a bug which was solved and because of that the test is now failing and can be corrected. Without that link the contributor can only guess how the tests might be. But I can remove the link and write a big fat comment explaining the whole situation and that test might fail in the future and what to do if that happens. I thought a link might shorter and is more capable of explaining the test situation. |
||
{"LongEnum", false, false, "long", Arrays.asList("1", "2", "3") /* @see: https://github.com/swagger-api/swagger-core/issues/2449 */}, | ||
{"FloatEnum", false, true, "float", Arrays.asList("1", "2", "3") /* @see: https://github.com/swagger-api/swagger-core/issues/2449 */}, | ||
{"DoubleEnum", false, false, "double", Arrays.asList("1", "2", "3") /* @see: https://github.com/swagger-api/swagger-core/issues/2449 */}, | ||
}; | ||
} | ||
|
||
@Test(description = "Setting the values for defined enum", dataProvider = "fromModelEnumTest") | ||
public void fromModelEnumTest(final String modelName, final boolean isInteger, final boolean isFloat, | ||
final String datatype, final List allowableValues) throws NoSuchFieldException, IllegalAccessException { | ||
final Swagger swagger = parseAndPrepareSwagger("src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml"); | ||
final Model model = swagger.getDefinitions().get(modelName); | ||
final CodegenModel codegenModel = new DefaultCodegen().fromModel(modelName, model, swagger.getDefinitions()); | ||
|
||
// test the not set fields | ||
for (final String fieldName : Arrays.asList("parent", "parentSchema", "interfaces", "parentModel", "interfaceModels", | ||
"children", "title", "description", "xmlPrefix", "xmlNamespace", "unescapedDescription", "discriminator", | ||
"defaultValue", "arrayModelType", "externalDocs", "additionalPropertiesType", "xmlName")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
Assert.assertNull(field.get(codegenModel), "Field: " + fieldName); | ||
} | ||
// test the boolean fields | ||
for (final String fieldName : Arrays.asList("isAlias", "hasMoreModels", "hasEnums", | ||
"hasRequired", "isArrayModel", "hasChildren", "hasVars", "hasOptional")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
Assert.assertFalse(field.getBoolean(codegenModel), "Field: " + fieldName); | ||
} | ||
// test the class names which shall all be "${modelName}" | ||
for (final String fieldName : Arrays.asList("name", "classname", "classVarName", "classFilename")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
Assert.assertEquals(field.get(codegenModel), modelName, "Field: " + fieldName); | ||
} | ||
// test the empty collections | ||
for (final String fieldName : Arrays.asList("vars", "optionalVars", "readWriteVars", "allVars", "requiredVars", | ||
"readOnlyVars", "parentVars", "mandatory", "allMandatory", "imports")) { | ||
final Field field = CodegenModel.class.getField(fieldName); | ||
final Collection collection = (Collection) field.get(codegenModel); | ||
Assert.assertTrue(collection.isEmpty(), "Field: " + fieldName); | ||
} | ||
|
||
Assert.assertEquals(codegenModel.dataType, datatype); | ||
Assert.assertEquals(codegenModel.isInteger, isInteger); | ||
Assert.assertEquals(codegenModel.isFloat, isFloat); | ||
Assert.assertTrue(codegenModel.isEnum); | ||
Assert.assertTrue(codegenModel.emptyVars); | ||
Assert.assertTrue(codegenModel.hasOnlyReadOnly); | ||
Assert.assertTrue(codegenModel.vendorExtensions.isEmpty()); | ||
Assert.assertEquals(codegenModel.allowableValues, ImmutableMap.of("values", allowableValues)); | ||
Assert.assertNotNull(codegenModel.modelJson); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This flag names a bit misleading for me (same names exists in CodegenProperty), may be isFloatEnum/isIntegerEnum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this flag also exists in CodegenProperty. And it has the same name as it has the same meaning.
Imaging this definition:
The outer
IntegerValue
is an integer, so the flagisInteger == true
.The inner
IntegerValue
is an integer, so the flagisInteger == true
.Why should I call the flag differently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wing328 plz advice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously we encounter the use case with array of array of enum. To make it easier to check whether the inner type is an integer, we use
isInteger
. If we need to ensure it's not an enum, we can{{^isEnum}} .. {{/isEnum}}
I agree it may be a bit misleading and it's tradeoff to make it easier to handle 2D or 3D array of enum