Skip to content

Commit 25036e4

Browse files
jimschubertfokot
andauthored
[scala] Support for Set when array has uniqueItems=true (#4926)
* [scala] Set support for unique arrays This includes and builds upon community contribution for better Set support in Scala. It makes property + model work as expected with Set and default values across all Scala generators. Included tests to account for new changes. This also reverts the community contribution to remove ListBuffer imports and change the default for array to ListBuffer. Users should use the instantiation types map to modify the desired array instantiation type. Any new default should target a new minor release after community discussion, as it affects all existing SDKs generated with openapi-generator. * [scala] Improve default handling of monadic collection type * [scala] Regenerate samples * Update ScalaPlayFrameworkServerCodegen.java Scala Play defaulted to List and should continue to do so. Co-authored-by: František Kocun <[email protected]>
1 parent 965efdd commit 25036e4

File tree

52 files changed

+705
-495
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+705
-495
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ public void processOpts() {
426426

427427
additionalProperties.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, getSortParamsByRequiredFlag());
428428
additionalProperties.put(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG, getSortModelPropertiesByRequiredFlag());
429-
429+
430430
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage());
431431
additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage());
432432

@@ -674,7 +674,7 @@ private String getArrayTypeDeclaration(ArraySchema arr) {
674674
// TODO: collection type here should be fully qualified namespace to avoid model conflicts
675675
// This supports arrays of arrays.
676676
String arrayType = typeMapping.get("array");
677-
if (Boolean.TRUE.equals(arr.getUniqueItems())) {
677+
if (ModelUtils.isSet(arr)) {
678678
arrayType = typeMapping.get("set");
679679
}
680680
StringBuilder instantiationType = new StringBuilder(arrayType);

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
import io.swagger.v3.oas.models.media.Schema;
2424
import org.apache.commons.io.FilenameUtils;
2525
import org.apache.commons.lang3.StringUtils;
26-
import org.openapitools.codegen.CliOption;
27-
import org.openapitools.codegen.CodegenConstants;
28-
import org.openapitools.codegen.DefaultCodegen;
26+
import org.openapitools.codegen.*;
2927
import org.openapitools.codegen.utils.ModelUtils;
3028
import org.slf4j.Logger;
3129
import org.slf4j.LoggerFactory;
@@ -106,6 +104,14 @@ public AbstractScalaCodegen() {
106104
"yield"
107105
));
108106

107+
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
108+
// although Seq is a predef, before Scala 2.13, it _could_ refer to a mutable Seq in some cases.
109+
importMapping.put("Seq", "scala.collection.immutable.Seq");
110+
importMapping.put("Set", "scala.collection.immutable.Set");
111+
importMapping.put("ListSet", "scala.collection.immutable.ListSet");
112+
113+
instantiationTypes.put("set", "Set");
114+
109115
cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC));
110116
cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC));
111117
cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC));
@@ -199,7 +205,11 @@ public String getTypeDeclaration(Schema p) {
199205
@Override
200206
public String getSchemaType(Schema p) {
201207
String openAPIType = super.getSchemaType(p);
202-
String type = null;
208+
if (ModelUtils.isSet(p)) {
209+
openAPIType = "set";
210+
}
211+
212+
String type;
203213
if (typeMapping.containsKey(openAPIType)) {
204214
type = typeMapping.get(openAPIType);
205215
if (languageSpecificPrimitives.contains(type)) {
@@ -219,7 +229,7 @@ public String toInstantiationType(Schema p) {
219229
} else if (ModelUtils.isArraySchema(p)) {
220230
ArraySchema ap = (ArraySchema) p;
221231
String inner = getSchemaType(ap.getItems());
222-
return instantiationTypes.get("array") + "[" + inner + "]";
232+
return ( ModelUtils.isSet(ap) ? instantiationTypes.get("set") : instantiationTypes.get("array") ) + "[" + inner + "]";
223233
} else {
224234
return null;
225235
}
@@ -248,14 +258,54 @@ public String toDefaultValue(Schema p) {
248258
} else if (ModelUtils.isArraySchema(p)) {
249259
ArraySchema ap = (ArraySchema) p;
250260
String inner = getSchemaType(ap.getItems());
251-
return "new ListBuffer[" + inner + "]() ";
261+
String genericType;
262+
if (ModelUtils.isSet(ap)) {
263+
genericType = instantiationTypes.get("set");
264+
} else {
265+
genericType = instantiationTypes.get("array");
266+
}
267+
268+
// test for immutable Monoids with .empty method for idiomatic defaults
269+
if ("List".equals(genericType) ||
270+
"Set".equals(genericType) ||
271+
"Seq".equals(genericType) ||
272+
"Array".equals(genericType) ||
273+
"Vector".equals(genericType) ||
274+
"IndexedSeq".equals(genericType) ||
275+
"Iterable".equals(genericType) ||
276+
"ListSet".equals(genericType)
277+
) {
278+
return genericType + "[" + inner + "].empty ";
279+
}
280+
281+
// Assume that any other generic types can be new'd up.
282+
return "new " + genericType + "[" + inner + "]() ";
252283
} else if (ModelUtils.isStringSchema(p)) {
253284
return null;
254285
} else {
255286
return null;
256287
}
257288
}
258289

290+
/**
291+
* Convert OAS Property object to Codegen Property object
292+
*
293+
* @param name name of the property
294+
* @param p OAS property object
295+
* @return Codegen Property object
296+
*/
297+
@Override
298+
public CodegenProperty fromProperty(String name, Schema p) {
299+
CodegenProperty prop = super.fromProperty(name, p);
300+
if (ModelUtils.isArraySchema(p)) {
301+
ArraySchema as = (ArraySchema) p;
302+
if (ModelUtils.isSet(as)) {
303+
prop.containerType = "set";
304+
}
305+
}
306+
return prop;
307+
}
308+
259309
@Override
260310
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
261311
// remove model imports to avoid warnings for importing class in the same package in Scala

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaClientCodegen.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ public String toDefaultValue(Schema p) {
265265
} else if (ModelUtils.isArraySchema(p)) {
266266
ArraySchema ap = (ArraySchema) p;
267267
String inner = getSchemaType(ap.getItems());
268+
if (ModelUtils.isSet(ap)) {
269+
return "Set[" + inner + "].empty ";
270+
}
268271
return "Seq[" + inner + "].empty ";
269272
} else if (ModelUtils.isStringSchema(p)) {
270273
return null;

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaGatlingCodegen.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ public ScalaGatlingCodegen() {
151151
importMapping.remove("Map");
152152

153153
importMapping.put("Date", "java.util.Date");
154-
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
155154

156155
typeMapping = new HashMap<String, String>();
157156
typeMapping.put("enum", "NSString");

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttpClientCodegen.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ public ScalaHttpClientCodegen() {
110110
importMapping.remove("Map");
111111

112112
importMapping.put("Date", "java.util.Date");
113-
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
114113

115114
typeMapping = new HashMap<String, String>();
116115
typeMapping.put("enum", "NSString");

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaLagomServerCodegen.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ public ScalaLagomServerCodegen() {
7676
importMapping.remove("Map");
7777

7878
importMapping.put("DateTime", "org.joda.time.DateTime");
79-
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
8079

8180
typeMapping = new HashMap<>();
8281
typeMapping.put("Integer", "Int");

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaPlayFrameworkServerCodegen.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ public String toDefaultValue(Schema p) {
348348
if (ModelUtils.isArraySchema(p)) {
349349
Schema items = ((ArraySchema) p).getItems();
350350
String inner = getSchemaType(items);
351+
if (ModelUtils.isSet(p)) {
352+
return "Set.empty[" + inner + "]";
353+
}
351354
return "List.empty[" + inner + "]";
352355
}
353356

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalatraServerCodegen.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,27 @@ public ScalatraServerCodegen() {
6363
"Integer",
6464
"Long",
6565
"Float",
66-
"List",
6766
"Set",
6867
"Map")
6968
);
7069

70+
typeMapping = new HashMap<>();
71+
typeMapping.put("array", "List");
72+
typeMapping.put("set", "Set");
73+
typeMapping.put("boolean", "Boolean");
74+
typeMapping.put("string", "String");
75+
typeMapping.put("int", "Int");
7176
typeMapping.put("integer", "Int");
7277
typeMapping.put("long", "Long");
78+
typeMapping.put("float", "Float");
79+
typeMapping.put("byte", "Byte");
80+
typeMapping.put("short", "Short");
81+
typeMapping.put("char", "Char");
82+
typeMapping.put("double", "Double");
83+
typeMapping.put("object", "Any");
84+
typeMapping.put("file", "File");
7385
typeMapping.put("binary", "File");
86+
typeMapping.put("number", "Double");
7487

7588
additionalProperties.put("appName", "OpenAPI Sample");
7689
additionalProperties.put("appDescription", "A sample openapi server");
@@ -95,7 +108,6 @@ public ScalatraServerCodegen() {
95108
supportingFiles.add(new SupportingFile("project/plugins.sbt", "project", "plugins.sbt"));
96109
supportingFiles.add(new SupportingFile("sbt", "", "sbt"));
97110

98-
instantiationTypes.put("array", "ArrayList");
99111
instantiationTypes.put("map", "HashMap");
100112

101113
importMapping = new HashMap<String, String>();
@@ -113,6 +125,11 @@ public ScalatraServerCodegen() {
113125
importMapping.put("LocalDateTime", "org.joda.time.LocalDateTime");
114126
importMapping.put("LocalDate", "org.joda.time.LocalDate");
115127
importMapping.put("LocalTime", "org.joda.time.LocalTime");
128+
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
129+
importMapping.put("Set", "scala.collection.immutable.Set");
130+
importMapping.put("ListSet", "scala.collection.immutable.ListSet");
131+
132+
instantiationTypes.put("set", "Set");
116133
}
117134

118135
@Override

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalazClientCodegen.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ public ScalazClientCodegen() {
7676
importMapping.remove("List");
7777
importMapping.remove("Set");
7878
importMapping.remove("Map");
79-
80-
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
8179

8280
// Overrides defaults applied in DefaultCodegen which don't apply cleanly to Scala.
8381
importMapping.put("Date", "java.util.Date");
@@ -214,7 +212,7 @@ public String toDefaultValue(Schema p) {
214212
} else if (ModelUtils.isArraySchema(p)) {
215213
ArraySchema ap = (ArraySchema) p;
216214
String inner = getSchemaType(ap.getItems());
217-
String collectionType = typeMapping.get("array");
215+
String collectionType = ModelUtils.isSet(ap) ? typeMapping.get("set") : typeMapping.get("array");
218216

219217
// We assume that users would map these collections to a monoid with an identity function
220218
// There's no reason to assume mutable structure here (which may make consumption more difficult)

modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ public static boolean isArraySchema(Schema schema) {
386386
return (schema instanceof ArraySchema);
387387
}
388388

389+
public static boolean isSet(Schema schema) {
390+
return ModelUtils.isArraySchema(schema) && Boolean.TRUE.equals(schema.getUniqueItems());
391+
}
392+
389393
public static boolean isStringSchema(Schema schema) {
390394
if (schema instanceof StringSchema || SchemaTypeUtil.STRING_TYPE.equals(schema.getType())) {
391395
return true;

modules/openapi-generator/src/test/java/org/openapitools/codegen/scalaakka/ScalaAkkaClientCodegenTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,37 @@ public void complexListPropertyTest() {
212212
Assert.assertTrue(property1.isContainer);
213213
}
214214

215+
@Test(description = "convert a model with set (unique array) property")
216+
public void complexSetPropertyTest() {
217+
final Schema model = new Schema()
218+
.description("a sample model")
219+
.addProperties("children", new ArraySchema()
220+
.items(new Schema().$ref("#/definitions/Children"))
221+
.uniqueItems(Boolean.TRUE));
222+
final DefaultCodegen codegen = new ScalaAkkaClientCodegen();
223+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
224+
codegen.setOpenAPI(openAPI);
225+
final CodegenModel cm = codegen.fromModel("sample", model);
226+
227+
Assert.assertEquals(cm.name, "sample");
228+
Assert.assertEquals(cm.classname, "Sample");
229+
Assert.assertEquals(cm.description, "a sample model");
230+
Assert.assertEquals(cm.vars.size(), 1);
231+
232+
final CodegenProperty property1 = cm.vars.get(0);
233+
Assert.assertEquals(property1.baseName, "children");
234+
Assert.assertEquals(property1.complexType, "Children");
235+
Assert.assertEquals(property1.getter, "getChildren");
236+
Assert.assertEquals(property1.setter, "setChildren");
237+
Assert.assertEquals(property1.dataType, "Set[Children]");
238+
Assert.assertEquals(property1.name, "children");
239+
Assert.assertEquals(property1.defaultValue, "Set[Children].empty ");
240+
Assert.assertEquals(property1.baseType, "Set");
241+
Assert.assertEquals(property1.containerType, "set");
242+
Assert.assertFalse(property1.required);
243+
Assert.assertTrue(property1.isContainer);
244+
}
245+
215246
@Test(description = "convert a model with complex map property")
216247
public void complexMapPropertyTest() {
217248
final Schema model = new Schema()
@@ -258,10 +289,33 @@ public void arrayModelTest() {
258289
Assert.assertEquals(cm.description, "an array model");
259290
Assert.assertEquals(cm.vars.size(), 0);
260291
Assert.assertEquals(cm.parent, "ListBuffer[Children]");
292+
Assert.assertEquals(cm.arrayModelType, "Children");
261293
Assert.assertEquals(cm.imports.size(), 2);
262294
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("ListBuffer", "Children")).size(), 2);
263295
}
264296

297+
@Test(description = "convert an array model with unique items to set")
298+
public void arrayAsSetModelTest() {
299+
final Schema schema = new ArraySchema()
300+
.items(new Schema().$ref("#/definitions/Children"))
301+
.description("a set of Children models");
302+
schema.setUniqueItems(true);
303+
304+
final DefaultCodegen codegen = new ScalaAkkaClientCodegen();
305+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema);
306+
codegen.setOpenAPI(openAPI);
307+
final CodegenModel cm = codegen.fromModel("sample", schema);
308+
309+
Assert.assertEquals(cm.name, "sample");
310+
Assert.assertEquals(cm.classname, "Sample");
311+
Assert.assertEquals(cm.description, "a set of Children models");
312+
Assert.assertEquals(cm.vars.size(), 0);
313+
Assert.assertEquals(cm.parent, "Set[Children]");
314+
Assert.assertEquals(cm.arrayModelType, "Children");
315+
Assert.assertEquals(cm.imports.size(), 2);
316+
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Set", "Children")).size(), 2);
317+
}
318+
265319
@Test(description = "convert a map model")
266320
public void mapModelTest() {
267321
final Schema model = new Schema()

modules/openapi-generator/src/test/java/org/openapitools/codegen/scalahttpclient/ScalaHttpClientModelTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.openapitools.codegen.CodegenProperty;
2626
import org.openapitools.codegen.DefaultCodegen;
2727
import org.openapitools.codegen.TestUtils;
28+
import org.openapitools.codegen.languages.ScalaAkkaClientCodegen;
2829
import org.openapitools.codegen.languages.ScalaHttpClientCodegen;
2930
import org.testng.Assert;
3031
import org.testng.annotations.Test;
@@ -119,6 +120,37 @@ public void listPropertyTest() {
119120
Assert.assertTrue(property1.isContainer);
120121
}
121122

123+
@Test(description = "convert a model with set (unique array) property")
124+
public void complexSetPropertyTest() {
125+
final Schema model = new Schema()
126+
.description("a sample model")
127+
.addProperties("children", new ArraySchema()
128+
.items(new Schema().$ref("#/definitions/Children"))
129+
.uniqueItems(Boolean.TRUE));
130+
final DefaultCodegen codegen = new ScalaHttpClientCodegen();
131+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
132+
codegen.setOpenAPI(openAPI);
133+
final CodegenModel cm = codegen.fromModel("sample", model);
134+
135+
Assert.assertEquals(cm.name, "sample");
136+
Assert.assertEquals(cm.classname, "Sample");
137+
Assert.assertEquals(cm.description, "a sample model");
138+
Assert.assertEquals(cm.vars.size(), 1);
139+
140+
final CodegenProperty property1 = cm.vars.get(0);
141+
Assert.assertEquals(property1.baseName, "children");
142+
Assert.assertEquals(property1.complexType, "Children");
143+
Assert.assertEquals(property1.getter, "getChildren");
144+
Assert.assertEquals(property1.setter, "setChildren");
145+
Assert.assertEquals(property1.dataType, "Set[Children]");
146+
Assert.assertEquals(property1.name, "children");
147+
Assert.assertEquals(property1.defaultValue, "Set[Children].empty ");
148+
Assert.assertEquals(property1.baseType, "Set");
149+
Assert.assertEquals(property1.containerType, "set");
150+
Assert.assertFalse(property1.required);
151+
Assert.assertTrue(property1.isContainer);
152+
}
153+
122154
@Test(description = "convert a model with a map property")
123155
public void mapPropertyTest() {
124156
final Schema model = new Schema()
@@ -256,6 +288,28 @@ public void arrayModelTest() {
256288
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("ListBuffer", "Children")).size(), 2);
257289
}
258290

291+
@Test(description = "convert an array model with unique items to set")
292+
public void arrayAsSetModelTest() {
293+
final Schema schema = new ArraySchema()
294+
.items(new Schema().$ref("#/definitions/Children"))
295+
.description("a set of Children models");
296+
schema.setUniqueItems(true);
297+
298+
final DefaultCodegen codegen = new ScalaHttpClientCodegen();
299+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema);
300+
codegen.setOpenAPI(openAPI);
301+
final CodegenModel cm = codegen.fromModel("sample", schema);
302+
303+
Assert.assertEquals(cm.name, "sample");
304+
Assert.assertEquals(cm.classname, "Sample");
305+
Assert.assertEquals(cm.description, "a set of Children models");
306+
Assert.assertEquals(cm.vars.size(), 0);
307+
Assert.assertEquals(cm.parent, "Set[Children]");
308+
Assert.assertEquals(cm.arrayModelType, "Children");
309+
Assert.assertEquals(cm.imports.size(), 2);
310+
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Set", "Children")).size(), 2);
311+
}
312+
259313
@Test(description = "convert a map model")
260314
public void mapModelTest() {
261315
final Schema model = new Schema()

0 commit comments

Comments
 (0)