Skip to content

Commit 181456f

Browse files
krumbs8749darw-soptim
authored andcommitted
fix so that tests passed - ensure merger OAS 3.0 and 3.1 compatible
1 parent ee61deb commit 181456f

File tree

1 file changed

+135
-120
lines changed
  • modules/openapi-generator/src/main/java/org/openapitools/codegen/utils

1 file changed

+135
-120
lines changed

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

Lines changed: 135 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,27 +1390,33 @@ public static Schema unaliasSchema(OpenAPI openAPI,
13901390
// generate a model extending array ← leave wrapped
13911391
return schema;
13921392
} else {
1393-
// ↳ unwrap into the array’s item type
1394-
Schema itemSchema = ModelUtils.getSchemaItems(ref);
1395-
Schema copyItem = deepCopy(itemSchema); // deep-copy the inner schema to prevent mutation on wrong component
1396-
copyItem.set$ref(null); // clear $ref so we don’t loop on the same ref
1397-
Schema unwrapped = unaliasSchema(openAPI, copyItem, schemaMappings);
1398-
return mergeSiblingFields(schema, unwrapped); // merge wrapper’s siblings
1393+
// ↳ unwrap the alias but keep the array container
1394+
Schema copyArray = deepCopy(ref); // deep-copy the full ArraySchema so we never mutate the shared registry
1395+
copyArray.set$ref(null); // clear the container’s own $ref
1396+
// recursively unalias its items
1397+
Schema inner = ModelUtils.getSchemaItems(copyArray);
1398+
Schema unaliasedItem = unaliasSchema(openAPI, inner, schemaMappings);
1399+
// do not clear unaliasedItem.$ref – we want downstream to still know this is a component
1400+
copyArray.setItems(unaliasedItem); // restore the container pointer
1401+
return mergeSiblingFields(schema, copyArray);
13991402
}
14001403
} else if (isComposedSchema(ref)) {
14011404
return schema;
14021405
} else if (isMapSchema(ref)) {
14031406
boolean hasProps = ref.getProperties() != null && !ref.getProperties().isEmpty();
14041407
if (hasProps || isGenerateAliasAsModel(ref)) {
1405-
// treat as model OR generate a model extending map ← leave wrapped
1408+
// map‐modeled‐as‐class ← leave wrapped
14061409
return schema;
14071410
} else {
1408-
// ↳ unwrap into the “additionalProperties” value
1409-
Schema addProp = (Schema) ref.getAdditionalProperties();
1410-
Schema copyValue = deepCopy(addProp); // deep-copy the inner map-value schema
1411-
copyValue.set$ref(null); // clear $ref for this inlined type
1412-
Schema unwrapped = unaliasSchema(openAPI, copyValue, schemaMappings);
1413-
return mergeSiblingFields(schema, unwrapped); // merge wrapper’s siblings
1411+
// ↳ unwrap the alias but keep the map container
1412+
Schema copyMap = deepCopy(ref); // deep-copy the full MapSchema so we never mutate the shared registry
1413+
copyMap.set$ref(null); // clear the container’s own $ref
1414+
Object addl = copyMap.getAdditionalProperties();
1415+
if (addl instanceof Schema) {
1416+
Schema unaliasedValue = unaliasSchema(openAPI, (Schema) addl, schemaMappings);
1417+
copyMap.setAdditionalProperties(unaliasedValue);
1418+
}
1419+
return mergeSiblingFields(schema, copyMap);
14141420
}
14151421
} else if (isObjectSchema(ref)) {
14161422
boolean hasProps = ref.getProperties() != null && !ref.getProperties().isEmpty();
@@ -1422,162 +1428,171 @@ public static Schema unaliasSchema(OpenAPI openAPI,
14221428
return schema;
14231429
} else {
14241430
// ↳ free-form object (type: object) : same as map-fallback
1425-
Schema copyObj = deepCopy(ref); // deep-copy free-form object
1426-
copyObj.set$ref(null); // clear lingering $ref
1431+
Schema copyObj = deepCopy(ref); // deep-copy free-form object
1432+
copyObj.set$ref(null); // clear lingering $ref so we don’t recurse
14271433
Schema unwrapped = unaliasSchema(openAPI, copyObj, schemaMappings);
1428-
return mergeSiblingFields(schema, unwrapped); // merge wrapper metadata
1434+
return mergeSiblingFields(schema, unwrapped);
14291435
}
14301436
}
14311437

1432-
// Priimitive fallback alias
1438+
// Primitive fallback alias
14331439
{
14341440
// ↳ fully unwrap a simple/primitive alias
1435-
Schema copyPrim = deepCopy(ref); // deep-copy primitive definition
1436-
copyPrim.set$ref(null); // must clear $ref to avoid recursion
1441+
Schema copyPrim = deepCopy(ref); // deep-copy primitive definition
1442+
if (copyPrim == null) return ref;
1443+
copyPrim.set$ref(null); // clear its $ref to avoid recursion
14371444
Schema unwrapped = unaliasSchema(openAPI, copyPrim, schemaMappings);
1438-
return mergeSiblingFields(schema, unwrapped); // and copy siblings
1445+
return mergeSiblingFields(schema, unwrapped);
14391446
}
14401447
}
14411448

14421449
// no $ref → nothing to unwrap
14431450
return schema;
14441451
}
14451452

1446-
/**
1447-
* Copy any non-null “sibling” fields from the original $ref-wrapper onto the actual definition.
1448-
*/
14491453
/**
14501454
* Copy any non-null “sibling” fields from the original $ref-wrapper
1451-
* onto the actual definition. This now handles the full OAS 3.1 set of
1452-
* keywords that may appear alongside a $ref.
1455+
* onto the actual definition. This covers the full OAS 3.1 spec.
14531456
*/
14541457
private static Schema mergeSiblingFields(Schema original, Schema actual) {
1455-
//--- core title/description/example/default → exactly as before
1458+
// stash away any container‐specific pointers on the "actual" schema
1459+
Schema preservedItems = actual.getItems();
1460+
Object preservedAddlProps = actual.getAdditionalProperties();
1461+
Map<String, Schema> preservedProps = actual.getProperties();
1462+
1463+
// --- core metadata
14561464
if (original.getTitle() != null) actual.setTitle(original.getTitle());
14571465
if (original.getDescription() != null) actual.setDescription(original.getDescription());
14581466
if (original.getExample() != null) actual.setExample(original.getExample());
14591467
if (original.getDefault() != null) actual.setDefault(original.getDefault());
14601468

1461-
//--- readOnly/writeOnly/deprecated/nullable → preserve access flags
1469+
// --- read/write flags & deprecation
14621470
if (original.getReadOnly() != null) actual.setReadOnly(original.getReadOnly());
14631471
if (original.getWriteOnly() != null) actual.setWriteOnly(original.getWriteOnly());
14641472
if (original.getDeprecated() != null) actual.setDeprecated(original.getDeprecated());
14651473
if (original.getNullable() != null) actual.setNullable(original.getNullable());
14661474

1467-
//--- numeric intervals
1468-
if (original.getMaximum() != null)
1469-
actual.setMaximum(original.getMaximum()); // preserve max
1470-
1471-
if (original.getExclusiveMaximum() != null)
1472-
actual.setExclusiveMaximum(original.getExclusiveMaximum()); // preserve exclusiveMax
1473-
if (original.getMinimum() != null) {
1474-
actual.setMinimum(original.getMinimum()); // preserve min
1475-
}
1476-
if (original.getExclusiveMinimum() != null)
1477-
actual.setExclusiveMinimum(original.getExclusiveMinimum()); // preserve exclusiveMin
1478-
if (original.getMultipleOf() != null) actual.setMultipleOf(original.getMultipleOf()); // preserve multipleOf
1479-
1480-
//--- string/array length constraints
1481-
if (original.getMaxLength() != null) actual.setMaxLength(original.getMaxLength()); // preserve maxLength
1482-
if (original.getMinLength() != null) actual.setMinLength(original.getMinLength()); // preserve minLength
1483-
if (original.getMaxItems() != null) actual.setMaxItems(original.getMaxItems()); // preserve maxItems
1484-
if (original.getMinItems() != null) actual.setMinItems(original.getMinItems()); // preserve minItems
1485-
1486-
//--- uniqueItems, maxProperties/minProperties → JSON-schema siblings
1487-
if (original.getUniqueItems() != null) actual.setUniqueItems(original.getUniqueItems()); // preserve uniqueItems
1488-
if (original.getMaxProperties() != null)
1489-
actual.setMaxProperties(original.getMaxProperties()); // preserve maxProperties
1490-
if (original.getMinProperties() != null)
1491-
actual.setMinProperties(original.getMinProperties()); // preserve minProperties
1492-
1493-
//--- pattern, enum → constrain values
1494-
if (original.getPattern() != null) actual.setPattern(original.getPattern()); // preserve pattern
1495-
if (original.getEnum() != null) {
1496-
actual.setEnum(new ArrayList<>(original.getEnum())); // preserve enum list
1497-
}
1498-
1499-
//--- required (object-only) → keep required array if present
1500-
if (original.getRequired() != null) {
1501-
actual.setRequired(new ArrayList<>(original.getRequired())); // preserve required props
1502-
}
1503-
1504-
//--- prefixItems & patternProperties (OAS 3.1)
1505-
if (original.getPrefixItems() != null) {
1506-
actual.setPrefixItems(new ArrayList<>(original.getPrefixItems())); // preserve tuple-style items
1507-
}
1508-
if (original.getPatternProperties() != null) {
1509-
actual.setPatternProperties(new LinkedHashMap<>(original.getPatternProperties())); // preserve patternProperties
1510-
}
1511-
1512-
//--- content-encoding/mediaType/schema (OAS 3.1 media-type siblings)
1475+
// --- numeric constraints
1476+
if (original.getMaximum() != null) actual.setMaximum(original.getMaximum());
1477+
if (original.getExclusiveMaximum() != null) actual.setExclusiveMaximum(original.getExclusiveMaximum());
1478+
if (original.getMinimum() != null) actual.setMinimum(original.getMinimum());
1479+
if (original.getExclusiveMinimum() != null) actual.setExclusiveMinimum(original.getExclusiveMinimum());
1480+
if (original.getMultipleOf() != null) actual.setMultipleOf(original.getMultipleOf());
1481+
1482+
// --- length / size constraints
1483+
if (original.getMaxLength() != null) actual.setMaxLength(original.getMaxLength());
1484+
if (original.getMinLength() != null) actual.setMinLength(original.getMinLength());
1485+
if (original.getPattern() != null) actual.setPattern(original.getPattern());
1486+
if (original.getMaxItems() != null) actual.setMaxItems(original.getMaxItems());
1487+
if (original.getMinItems() != null) actual.setMinItems(original.getMinItems());
1488+
if (original.getUniqueItems() != null) actual.setUniqueItems(original.getUniqueItems());
1489+
if (original.getMaxProperties() != null) actual.setMaxProperties(original.getMaxProperties());
1490+
if (original.getMinProperties() != null) actual.setMinProperties(original.getMinProperties());
1491+
1492+
// --- enum & required (object-only)
1493+
if (original.getEnum() != null) actual.setEnum(new ArrayList<>(original.getEnum()));
1494+
if (original.getRequired() != null) actual.setRequired(new ArrayList<>(original.getRequired()));
1495+
1496+
// --- OAS 3.1 array siblings
1497+
if (original.getAdditionalItems() != null) // tuple-style additionalItems
1498+
actual.setAdditionalItems(deepCopy(original.getAdditionalItems()));
1499+
if (original.getUnevaluatedItems() != null) // unevaluatedItems
1500+
actual.setUnevaluatedItems(deepCopy(original.getUnevaluatedItems()));
1501+
if (original.getPrefixItems() != null) // tuple prefixItems
1502+
actual.setPrefixItems(new ArrayList<>(original.getPrefixItems()));
1503+
1504+
// --- OAS 3.1 object siblings
1505+
if (original.getPatternProperties() != null) // patternProperties
1506+
actual.setPatternProperties(new LinkedHashMap<>(original.getPatternProperties()));
1507+
if (original.getPropertyNames() != null) // propertyNames
1508+
actual.setPropertyNames(deepCopy(original.getPropertyNames()));
1509+
if (original.getUnevaluatedProperties() != null)// unevaluatedProperties
1510+
actual.setUnevaluatedProperties(deepCopy(original.getUnevaluatedProperties()));
1511+
1512+
// --- OAS 3.1 conditional / dependency siblings
1513+
if (original.getContains() != null) // contains
1514+
actual.setContains(deepCopy(original.getContains()));
1515+
if (original.getIf() != null) // if
1516+
actual.setIf(deepCopy(original.getIf()));
1517+
if (original.getThen() != null) // then
1518+
actual.setThen(deepCopy(original.getThen()));
1519+
if (original.getElse() != null) // else
1520+
actual.setElse(deepCopy(original.getElse()));
1521+
if (original.getDependentSchemas() != null) // dependentSchemas
1522+
actual.setDependentSchemas(new LinkedHashMap<>(original.getDependentSchemas()));
1523+
if (original.getDependentRequired() != null)// dependentRequired
1524+
actual.setDependentRequired(new LinkedHashMap<>(original.getDependentRequired()));
1525+
1526+
// --- OAS 3.1 media-type siblings (for contentEncoding / contentMediaType)
15131527
if (original.getContentEncoding() != null)
1514-
actual.setContentEncoding(original.getContentEncoding()); // preserve content-encoding
1528+
actual.setContentEncoding(original.getContentEncoding());
15151529
if (original.getContentMediaType() != null)
1516-
actual.setContentMediaType(original.getContentMediaType()); // preserve contentMediaType
1530+
actual.setContentMediaType(original.getContentMediaType());
15171531
if (original.getContentSchema() != null)
1518-
actual.setContentSchema(deepCopy(original.getContentSchema())); // preserve contentSchema
1519-
1520-
//--- additionalItems / unevaluatedItems (OAS 3.1 array siblings)
1521-
if (original.getAdditionalItems() != null)
1522-
actual.setAdditionalItems(deepCopy(original.getAdditionalItems())); // preserve additionalItems
1523-
if (original.getUnevaluatedItems() != null)
1524-
actual.setUnevaluatedItems(deepCopy(original.getUnevaluatedItems())); // preserve unevaluatedItems
1525-
1526-
//--- propertyNames / unevaluatedProperties (OAS 3.1 object siblings)
1527-
if (original.getPropertyNames() != null)
1528-
actual.setPropertyNames(deepCopy(original.getPropertyNames())); // preserve propertyNames
1529-
if (original.getUnevaluatedProperties() != null)
1530-
actual.setUnevaluatedProperties(deepCopy(original.getUnevaluatedProperties())); // preserve unevaluatedProperties
1531-
1532-
//--- contains / if / then / else (OAS 3.1 conditional siblings)
1533-
if (original.getContains() != null)
1534-
actual.setContains(deepCopy(original.getContains())); // preserve contains
1535-
if (original.getIf() != null) actual.setIf(deepCopy(original.getIf())); // preserve if
1536-
if (original.getThen() != null) actual.setThen(deepCopy(original.getThen())); // preserve then
1537-
if (original.getElse() != null) actual.setElse(deepCopy(original.getElse())); // preserve else
1538-
1539-
//--- dependentSchemas / dependentRequired (OAS 3.1 dependency siblings)
1540-
if (original.getDependentSchemas() != null)
1541-
actual.setDependentSchemas(new LinkedHashMap<>(original.getDependentSchemas())); // preserve dependentSchemas
1542-
if (original.getDependentRequired() != null)
1543-
actual.setDependentRequired(new LinkedHashMap<>(original.getDependentRequired())); // preserve dependentRequired
1544-
1545-
//--- types (OAS 3.1 JSON-schema `type` as array of strings)
1532+
actual.setContentSchema(deepCopy(original.getContentSchema()));
1533+
1534+
// --- JSON-schema type array (OAS 3.1)
15461535
if (original.getTypes() != null)
1547-
actual.setTypes(new LinkedHashSet<>(original.getTypes())); // preserve type array
1536+
actual.setTypes(new LinkedHashSet<>(original.getTypes()));
15481537

1549-
//--- examples (OAS 3.1 multiple examples)
1538+
// --- multiple examples (OAS 3.1)
15501539
if (original.getExamples() != null)
1551-
actual.setExamples(new ArrayList<>(original.getExamples())); // preserve examples
1540+
actual.setExamples(new ArrayList<>(original.getExamples()));
15521541

1553-
//--- booleanSchemaValue (3.1 “boolean” schemas)
1542+
// --- boolean schemas (OAS 3.1 “booleanSchemaValue”)
15541543
if (original.getBooleanSchemaValue() != null)
1555-
actual.setBooleanSchemaValue(original.getBooleanSchemaValue()); // preserve boolean contents
1544+
actual.setBooleanSchemaValue(original.getBooleanSchemaValue());
15561545

1557-
//--- xml / externalDocs / discriminator – these are always siblings of any Schema
1558-
if (original.getXml() != null)
1559-
actual.setXml(original.getXml()); // preserve xml config
1560-
if (original.getExternalDocs() != null)
1561-
actual.setExternalDocs(original.getExternalDocs()); // preserve externalDocs
1562-
if (original.getDiscriminator() != null)
1563-
actual.setDiscriminator(original.getDiscriminator()); // preserve discriminator
1546+
// --- always-allowed siblings on any schema
1547+
if (original.getXml() != null) actual.setXml(original.getXml()); // XML metadata
1548+
if (original.getExternalDocs() != null) actual.setExternalDocs(original.getExternalDocs()); // externalDocs
1549+
if (original.getDiscriminator() != null) actual.setDiscriminator(original.getDiscriminator()); // discriminator
15641550

1565-
//--- extensions (x-*)
1551+
// --- finally, any vendor extensions
15661552
if (original.getExtensions() != null && !original.getExtensions().isEmpty()) {
15671553
if (actual.getExtensions() == null) {
1568-
actual.setExtensions(new LinkedHashMap<>()); // ensure extensions map exists
1554+
actual.setExtensions(new LinkedHashMap<>()); // ensure non-null map
15691555
}
1570-
actual.getExtensions().putAll(original.getExtensions()); // copy custom x-extensions
1556+
actual.getExtensions().putAll(original.getExtensions()); // copy all x-*
15711557
}
15721558

1559+
// restore the three container fields we stashed at the top:
1560+
actual.setItems(preservedItems);
1561+
actual.setAdditionalProperties(preservedAddlProps);
1562+
actual.setProperties(preservedProps);
1563+
15731564
return actual;
15741565
}
15751566

15761567
/**
15771568
* Deep-copy via Jackson so we never touch the registry’s original Schema.
1569+
* This version preserves the concrete subtype (ArraySchema, ObjectSchema, etc.),
1570+
* which is critical for all the places that cast back to the original schema class.
15781571
*/
1579-
private static Schema deepCopy(Schema schema) {
1580-
return Json.mapper().convertValue(schema, Schema.class);
1572+
private static <T extends Schema> T deepCopy(T schema) {
1573+
if (schema == null) {
1574+
return null;
1575+
}
1576+
// pull off additionalProperties (could be Boolean or Schema)
1577+
Object addl = schema.getAdditionalProperties();
1578+
// clear it so Jackson won't choke
1579+
schema.setAdditionalProperties(null);
1580+
1581+
// do the normal convertValue into the exact same subtype
1582+
T copy = (T) Json.mapper().convertValue(schema, schema.getClass());
1583+
1584+
// restore the original on the source
1585+
schema.setAdditionalProperties(addl);
1586+
1587+
// 5) put it back on the clone, deep-copying if it's itself a Schema
1588+
if (addl instanceof Schema) {
1589+
copy.setAdditionalProperties(deepCopy((Schema) addl));
1590+
} else if (addl != null) {
1591+
// could be Boolean true/false or other
1592+
copy.setAdditionalProperties(addl);
1593+
}
1594+
1595+
return copy;
15811596
}
15821597

15831598
/**

0 commit comments

Comments
 (0)