Skip to content

Commit c33c646

Browse files
committed
Merge remote-tracking branch 'upstream/master' into remove-barrel-imports
2 parents 463ad1f + b443080 commit c33c646

77 files changed

Lines changed: 458 additions & 43 deletions

File tree

Some content is hidden

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

docs/customization.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,3 +723,10 @@ Into this securityScheme:
723723
scheme: bearer
724724
type: http
725725
```
726+
727+
- `SORT_MODEL_PROPERTIES`: When set to true, model properties will be sorted alphabetically by name. This ensures deterministic code generation output regardless of property ordering in the source spec.
728+
729+
Example:
730+
```
731+
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -o /tmp/java-okhttp/ --openapi-normalizer SORT_MODEL_PROPERTIES=true
732+
```

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ public class OpenAPINormalizer {
151151
boolean updateNumberToNullable;
152152
boolean updateBooleanToNullable;
153153

154+
// when set to true, sort model properties by name to ensure deterministic output
155+
final String SORT_MODEL_PROPERTIES = "SORT_MODEL_PROPERTIES";
156+
154157
// ============= end of rules =============
155158

156159
/**
@@ -209,6 +212,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
209212
ruleNames.add(SET_PRIMITIVE_TYPES_TO_NULLABLE);
210213
ruleNames.add(SIMPLIFY_ONEOF_ANYOF_ENUM);
211214
ruleNames.add(REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT);
215+
ruleNames.add(SORT_MODEL_PROPERTIES);
212216

213217
// rules that are default to true
214218
rules.put(SIMPLIFY_ONEOF_ANYOF, true);
@@ -768,7 +772,7 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
768772
}
769773

770774
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
771-
normalizeProperties(schema.getProperties(), visitedSchemas);
775+
normalizeProperties(schema, visitedSchemas);
772776
}
773777

774778
if (schema.getAdditionalProperties() != null) {
@@ -777,7 +781,7 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
777781

778782
return schema;
779783
} else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
780-
normalizeProperties(schema.getProperties(), visitedSchemas);
784+
normalizeProperties(schema, visitedSchemas);
781785
} else if (schema.getAdditionalProperties() instanceof Schema) { // map
782786
normalizeMapSchema(schema);
783787
normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas);
@@ -880,10 +884,19 @@ protected void normalizeIntegerSchema(Schema schema, Set<Schema> visitedSchemas)
880884
processSetPrimitiveTypesToNullable(schema);
881885
}
882886

883-
protected void normalizeProperties(Map<String, Schema> properties, Set<Schema> visitedSchemas) {
887+
protected void normalizeProperties(Schema schema, Set<Schema> visitedSchemas) {
888+
Map<String, Schema> properties = schema.getProperties();
884889
if (properties == null) {
885890
return;
886891
}
892+
893+
// Sort properties by name if rule is enabled
894+
if (getRule(SORT_MODEL_PROPERTIES)) {
895+
Map<String, Schema> sortedProperties = new TreeMap<>(properties);
896+
schema.setProperties(sortedProperties);
897+
properties = sortedProperties;
898+
}
899+
887900
for (Map.Entry<String, Schema> propertiesEntry : properties.entrySet()) {
888901
Schema property = propertiesEntry.getValue();
889902

@@ -1089,7 +1102,7 @@ protected Schema normalizeAnyOf(Schema schema, Set<Schema> visitedSchemas) {
10891102
protected Schema normalizeComplexComposedSchema(Schema schema, Set<Schema> visitedSchemas) {
10901103
// loop through properties, if any
10911104
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
1092-
normalizeProperties(schema.getProperties(), visitedSchemas);
1105+
normalizeProperties(schema, visitedSchemas);
10931106
}
10941107

10951108
processRemoveAnyOfOneOfAndKeepPropertiesOnly(schema);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,17 +601,25 @@ public String toOperationId(String operationId) {
601601
public ModelsMap postProcessModels(ModelsMap objs) {
602602
for (ModelMap mo : objs.getModels()) {
603603
CodegenModel cm = mo.getModel();
604+
boolean needsExtractSimpleType = false;
604605
for (CodegenProperty var : cm.vars) {
605606
// check to see if base name is an empty string
606607
if ("".equals(var.baseName)) {
607608
LOGGER.debug("Empty baseName `` (empty string) in the model `{}` has been renamed to `empty_string` to avoid compilation errors.", cm.classname);
608609
var.baseName = "empty_string";
609610
}
610611

612+
if (!var.isPrimitiveType) {
613+
needsExtractSimpleType = true;
614+
}
615+
611616
// create extension x-r-doc-type to store the data type in r doc format
612617
var.vendorExtensions.put("x-r-doc-type", constructRdocType(var));
613618
}
614619

620+
// create extension x-r-has-non-primitive-field to indicate whether generated models need special handling for complex types
621+
cm.vendorExtensions.put("x-r-has-non-primitive-field", needsExtractSimpleType);
622+
615623
// apply the same fix, enhancement for allVars
616624
for (CodegenProperty var : cm.allVars) {
617625
// check to see if base name is an empty string

modules/openapi-generator/src/main/resources/JavaJaxRS/spec/enumOuterClass.mustache

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import com.fasterxml.jackson.annotation.JsonValue;
66
/**
77
* {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}}
88
*/
9+
{{>generatedAnnotation}}
10+
911
{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} {
1012
{{#gson}}
1113
{{#allowableValues}}{{#enumVars}}

modules/openapi-generator/src/main/resources/r/modelGeneric.mustache

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,15 +242,15 @@
242242
self$`{{name}}`
243243
{{/isPrimitiveType}}
244244
{{^isPrimitiveType}}
245-
lapply(self$`{{name}}`, function(x) x$toSimpleType())
245+
self$extractSimpleType(self$`{{name}}`)
246246
{{/isPrimitiveType}}
247247
{{/isArray}}
248248
{{#isMap}}
249249
{{#isPrimitiveType}}
250250
self$`{{name}}`
251251
{{/isPrimitiveType}}
252252
{{^isPrimitiveType}}
253-
lapply(self$`{{name}}`, function(x) x$toSimpleType())
253+
self$extractSimpleType(self$`{{name}}`)
254254
{{/isPrimitiveType}}
255255
{{/isMap}}
256256
{{/isContainer}}
@@ -259,7 +259,7 @@
259259
self$`{{name}}`
260260
{{/isPrimitiveType}}
261261
{{^isPrimitiveType}}
262-
self$`{{name}}`$toSimpleType()
262+
self$extractSimpleType(self$`{{name}}`)
263263
{{/isPrimitiveType}}
264264
{{/isContainer}}
265265
}
@@ -272,6 +272,31 @@
272272
{{/isAdditionalPropertiesTrue}}
273273
return({{classname}}Object)
274274
},
275+
{{#vendorExtensions.x-r-has-non-primitive-field}}
276+
277+
extractSimpleType = function(x) {
278+
if (R6::is.R6(x)) {
279+
return(x$toSimpleType())
280+
} else if (!self$hasNestedR6(x)) {
281+
return(x)
282+
}
283+
lapply(x, self$extractSimpleType)
284+
},
285+
286+
hasNestedR6 = function(x) {
287+
if (R6::is.R6(x)) {
288+
return(TRUE)
289+
}
290+
if (is.list(x)) {
291+
for (item in x) {
292+
if (self$hasNestedR6(item)) {
293+
return(TRUE)
294+
}
295+
}
296+
}
297+
FALSE
298+
},
299+
{{/vendorExtensions.x-r-has-non-primitive-field}}
275300
276301
#' @description
277302
#' Deserialize JSON string into an instance of {{{classname}}}

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,59 @@ public void testRemoveXInternalFromInlineProperties() {
13061306
assertNotNull(inlinePropertyAfter.getProperties().get("nestedNumber"));
13071307
}
13081308

1309+
@Test
1310+
public void testSortModelProperties() {
1311+
// Create a schema with properties in non-alphabetical order
1312+
Schema schema = new ObjectSchema()
1313+
.addProperty("zebra", new StringSchema())
1314+
.addProperty("apple", new StringSchema())
1315+
.addProperty("mango", new IntegerSchema());
1316+
1317+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("TestModel", schema);
1318+
1319+
// Verify original order (LinkedHashMap preserves insertion order)
1320+
List<String> originalOrder = new ArrayList<>(schema.getProperties().keySet());
1321+
assertEquals(originalOrder.get(0), "zebra");
1322+
assertEquals(originalOrder.get(1), "apple");
1323+
assertEquals(originalOrder.get(2), "mango");
1324+
1325+
// Apply normalizer with SORT_MODEL_PROPERTIES=true
1326+
Map<String, String> options = new HashMap<>();
1327+
options.put("SORT_MODEL_PROPERTIES", "true");
1328+
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
1329+
openAPINormalizer.normalize();
1330+
1331+
// Verify properties are now sorted alphabetically
1332+
Schema normalizedSchema = openAPI.getComponents().getSchemas().get("TestModel");
1333+
List<String> sortedOrder = new ArrayList<>(normalizedSchema.getProperties().keySet());
1334+
assertEquals(sortedOrder.get(0), "apple");
1335+
assertEquals(sortedOrder.get(1), "mango");
1336+
assertEquals(sortedOrder.get(2), "zebra");
1337+
}
1338+
1339+
@Test
1340+
public void testSortModelPropertiesDisabledByDefault() {
1341+
// Create a schema with properties in non-alphabetical order
1342+
Schema schema = new ObjectSchema()
1343+
.addProperty("zebra", new StringSchema())
1344+
.addProperty("apple", new StringSchema())
1345+
.addProperty("mango", new IntegerSchema());
1346+
1347+
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("TestModel", schema);
1348+
1349+
// Apply normalizer without SORT_MODEL_PROPERTIES (default is false)
1350+
Map<String, String> options = new HashMap<>();
1351+
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
1352+
openAPINormalizer.normalize();
1353+
1354+
// Verify properties retain original order
1355+
Schema normalizedSchema = openAPI.getComponents().getSchemas().get("TestModel");
1356+
List<String> order = new ArrayList<>(normalizedSchema.getProperties().keySet());
1357+
assertEquals(order.get(0), "zebra");
1358+
assertEquals(order.get(1), "apple");
1359+
assertEquals(order.get(2), "mango");
1360+
}
1361+
13091362
public static class RemoveRequiredNormalizer extends OpenAPINormalizer {
13101363

13111364
public RemoveRequiredNormalizer(OpenAPI openAPI, Map<String, String> inputRules) {

samples/client/echo_api/r/R/default_value.R

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ DefaultValue <- R6::R6Class(
119119
DefaultValueObject <- list()
120120
if (!is.null(self$`array_string_enum_ref_default`)) {
121121
DefaultValueObject[["array_string_enum_ref_default"]] <-
122-
lapply(self$`array_string_enum_ref_default`, function(x) x$toSimpleType())
122+
self$extractSimpleType(self$`array_string_enum_ref_default`)
123123
}
124124
if (!is.null(self$`array_string_enum_default`)) {
125125
DefaultValueObject[["array_string_enum_default"]] <-
@@ -152,6 +152,29 @@ DefaultValue <- R6::R6Class(
152152
return(DefaultValueObject)
153153
},
154154

155+
extractSimpleType = function(x) {
156+
if (R6::is.R6(x)) {
157+
return(x$toSimpleType())
158+
} else if (!self$hasNestedR6(x)) {
159+
return(x)
160+
}
161+
lapply(x, self$extractSimpleType)
162+
},
163+
164+
hasNestedR6 = function(x) {
165+
if (R6::is.R6(x)) {
166+
return(TRUE)
167+
}
168+
if (is.list(x)) {
169+
for (item in x) {
170+
if (self$hasNestedR6(item)) {
171+
return(TRUE)
172+
}
173+
}
174+
}
175+
FALSE
176+
},
177+
155178
#' @description
156179
#' Deserialize JSON string into an instance of DefaultValue
157180
#'

samples/client/echo_api/r/R/pet.R

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,15 @@ Pet <- R6::R6Class(
115115
}
116116
if (!is.null(self$`category`)) {
117117
PetObject[["category"]] <-
118-
self$`category`$toSimpleType()
118+
self$extractSimpleType(self$`category`)
119119
}
120120
if (!is.null(self$`photoUrls`)) {
121121
PetObject[["photoUrls"]] <-
122122
self$`photoUrls`
123123
}
124124
if (!is.null(self$`tags`)) {
125125
PetObject[["tags"]] <-
126-
lapply(self$`tags`, function(x) x$toSimpleType())
126+
self$extractSimpleType(self$`tags`)
127127
}
128128
if (!is.null(self$`status`)) {
129129
PetObject[["status"]] <-
@@ -132,6 +132,29 @@ Pet <- R6::R6Class(
132132
return(PetObject)
133133
},
134134

135+
extractSimpleType = function(x) {
136+
if (R6::is.R6(x)) {
137+
return(x$toSimpleType())
138+
} else if (!self$hasNestedR6(x)) {
139+
return(x)
140+
}
141+
lapply(x, self$extractSimpleType)
142+
},
143+
144+
hasNestedR6 = function(x) {
145+
if (R6::is.R6(x)) {
146+
return(TRUE)
147+
}
148+
if (is.list(x)) {
149+
for (item in x) {
150+
if (self$hasNestedR6(item)) {
151+
return(TRUE)
152+
}
153+
}
154+
}
155+
FALSE
156+
},
157+
135158
#' @description
136159
#' Deserialize JSON string into an instance of Pet
137160
#'

samples/client/others/typescript-angular-v20/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

samples/client/others/typescript-angular/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)