Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -187,6 +187,12 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
public boolean isDiscriminator;
public boolean isNew; // true when this property overrides an inherited property
public Boolean isOverridden; // true if the property is a parent property (not defined in child/current schema)
/**
* The type alias name to use when this property references a deduplicated inline model.
* When non-null, code generators may emit a type alias declaration.
*/
@Getter @Setter
public String dataTypeAlias;
@Getter @Setter
public List<String> _enum;
@Getter @Setter
Expand Down Expand Up @@ -980,6 +986,7 @@ public String toString() {
sb.append(", setter='").append(setter).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append(", dataType='").append(dataType).append('\'');
sb.append(", dataTypeAlias='").append(dataTypeAlias).append('\'');
sb.append(", datatypeWithEnum='").append(datatypeWithEnum).append('\'');
sb.append(", dataFormat='").append(dataFormat).append('\'');
sb.append(", name='").append(name).append('\'');
Expand Down Expand Up @@ -1167,6 +1174,7 @@ public boolean equals(Object o) {
Objects.equals(setter, that.setter) &&
Objects.equals(description, that.description) &&
Objects.equals(dataType, that.dataType) &&
Objects.equals(dataTypeAlias, that.dataTypeAlias) &&
Objects.equals(datatypeWithEnum, that.datatypeWithEnum) &&
Objects.equals(dataFormat, that.dataFormat) &&
Objects.equals(name, that.name) &&
Expand Down Expand Up @@ -1211,7 +1219,7 @@ public boolean equals(Object o) {
public int hashCode() {

return Objects.hash(openApiType, baseName, complexType, getter, setter, description,
dataType, datatypeWithEnum, dataFormat, name, min, max, defaultValue,
dataType, dataTypeAlias, datatypeWithEnum, dataFormat, name, min, max, defaultValue,
defaultValueWithParam, baseType, containerType, containerTypeMapped, title, unescapedDescription,
maxLength, minLength, pattern, example, jsonSchema, minimum, maximum,
exclusiveMinimum, exclusiveMaximum, required, deprecated,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4193,6 +4193,15 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
String type = getSchemaType(p);
setNonArrayMapProperty(property, type);
property.isModel = (ModelUtils.isComposedSchema(referencedSchema) || ModelUtils.isObjectSchema(referencedSchema)) && ModelUtils.isModel(referencedSchema);

// Check if this property is reusing a model type that was generated/deduplicated by InlineModelResolver
// InlineModelResolver marks deduplicated schemas with x-alias-name vendor extension
if (p.get$ref() != null && original != null && original.getExtensions() != null) {
String dedupedName = (String) original.getExtensions().get("x-alias-name");
if (dedupedName != null && ModelUtils.isModel(referencedSchema)) {
property.dataTypeAlias = toModelName(dedupedName);
}
}
}

// restore original schema with default value, nullable, readonly etc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ private void flattenComposedChildren(String key, List<Schema> children, boolean
listIterator.set(schema);
} else {
Schema schema = new Schema().$ref(existing);
schema.addExtension("x-alias-name", applyInlineSchemaNameMapping(innerModelName));
schema.setRequired(component.getRequired());
listIterator.set(schema);
}
Expand Down Expand Up @@ -826,6 +827,7 @@ private void flattenProperties(OpenAPI openAPI, Map<String, Schema> properties,
String existing = matchGenerated(model);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.addExtension("x-alias-name", applyInlineSchemaNameMapping(modelName));
schema.setRequired(op.getRequired());
propsToUpdate.put(key, schema);
} else {
Expand All @@ -846,6 +848,7 @@ private void flattenProperties(OpenAPI openAPI, Map<String, Schema> properties,
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.addExtension("x-alias-name", applyInlineSchemaNameMapping(modelName));
schema.setRequired(op.getRequired());
property.setItems(schema);
} else {
Expand Down Expand Up @@ -876,6 +879,7 @@ private void flattenProperties(OpenAPI openAPI, Map<String, Schema> properties,
String existing = matchGenerated(innerModel);
if (existing != null) {
Schema schema = new Schema().$ref(existing);
schema.addExtension("x-alias-name", applyInlineSchemaNameMapping(modelName));
schema.setRequired(op.getRequired());
property.setAdditionalProperties(schema);
} else {
Expand Down Expand Up @@ -985,6 +989,19 @@ private Schema modelFromProperty(OpenAPI openAPI, Schema object, String path) {
return model;
}

/**
* Apply inlineSchemaNameMapping if configured.
*
* @param name the inline schema name to map
* @return the mapped name if mapping exists, otherwise the original name
*/
private String applyInlineSchemaNameMapping(String name) {
if (inlineSchemaNameMapping.containsKey(name)) {
return inlineSchemaNameMapping.get(name);
}
return name;
}

/**
* Move schema to components (if new) and return $ref to schema or
* existing schema.
Expand All @@ -998,6 +1015,7 @@ private Schema makeSchemaInComponents(String name, Schema schema) {
Schema refSchema;
if (existing != null) {
refSchema = new Schema().$ref(existing);
refSchema.addExtension("x-alias-name", applyInlineSchemaNameMapping(name));
} else {
if (resolveInlineEnums && schema.getEnum() != null && schema.getEnum().size() > 0) {
LOGGER.warn("Model " + name + " promoted to its own schema due to resolveInlineEnums=true");
Expand Down Expand Up @@ -1035,6 +1053,10 @@ private void copyVendorExtensions(Schema source, Schema target) {
return;
}
for (String extName : vendorExtensions.keySet()) {
// Don't overwrite x-alias-name that was set during deduplication
if ("x-alias-name".equals(extName) && target.getExtensions() != null && target.getExtensions().containsKey("x-alias-name")) {
continue;
}
target.addExtension(extName, vendorExtensions.get(extName));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,21 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
property.vendorExtensions.put("x-golang-is-container", true);
}
}

private void swapDataTypeAndAlias(CodegenProperty property, Map<String, String> typeAliasesMap) {
if (property.dataTypeAlias != null) {
String dedupedType = property.dataType;
String aliasName = property.dataTypeAlias;
// Swap: field uses the alias, alias definition points to deduplicated type
property.dataType = aliasName;
property.dataTypeAlias = dedupedType;

// Collect type alias (after swap, dataType is alias name, dataTypeAlias is target)
if (!property.isContainer && !typeAliasesMap.containsKey(property.dataType)) {
typeAliasesMap.put(property.dataType, property.dataTypeAlias);
}
}
}

@Override
public ModelsMap postProcessModels(ModelsMap objs) {
Expand Down Expand Up @@ -775,7 +790,25 @@ public ModelsMap postProcessModels(ModelsMap objs) {
codegenProperties.addAll(inheritedProperties);
}

// Collect reused model properties for type alias generation
Map<String, String> typeAliasesMap = new LinkedHashMap<>();

for (CodegenProperty cp : codegenProperties) {
// Swap dataType and dataTypeAlias so fields use the alias name
swapDataTypeAndAlias(cp, typeAliasesMap);

// Also swap for array items and update the array's dataType
if (cp.items != null && cp.items.dataTypeAlias != null) {
String oldItemsDataType = cp.items.dataType;
swapDataTypeAndAlias(cp.items, typeAliasesMap);
String newItemsDataType = cp.items.dataType;

// Update the array's dataType to use the new items dataType
if (cp.dataType != null && cp.dataType.contains(oldItemsDataType)) {
cp.dataType = cp.dataType.replace(oldItemsDataType, newItemsDataType);
}
}

if (!addedTimeImport && ("time.Time".equals(cp.dataType) || (cp.items != null && "time.Time".equals(cp.items.complexType)))) {
imports.add(createMapping("import", "time"));
addedTimeImport = true;
Expand Down Expand Up @@ -855,6 +888,23 @@ public ModelsMap postProcessModels(ModelsMap objs) {
if (generateUnmarshalJSON) {
model.vendorExtensions.putIfAbsent("x-go-generate-unmarshal-json", true);
}

// Convert type aliases map to list for template usage
if (!typeAliasesMap.isEmpty()) {
List<Map<String, String>> typeAliases = new ArrayList<>();
for (Map.Entry<String, String> entry : typeAliasesMap.entrySet()) {
if (!entry.getKey().equals(entry.getValue())) {
Map<String, String> aliasMap = new HashMap<>();
aliasMap.put("aliasName", entry.getKey());
aliasMap.put("originalType", entry.getValue());
typeAliases.add(aliasMap);
}
}
if (!typeAliases.isEmpty()) {
model.vendorExtensions.put("x-go-type-aliases", typeAliases);
model.vendorExtensions.put("x-go-has-type-aliases", true);
}
}
}

// recursively add import for mapping one type to multiple imports
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
{{#vendorExtensions.x-go-has-type-aliases}}
// Type aliases for reused model types
{{#vendorExtensions.x-go-type-aliases}}
type {{aliasName}} = {{originalType}}
{{/vendorExtensions.x-go-type-aliases}}

{{/vendorExtensions.x-go-has-type-aliases}}
// checks if the {{classname}} type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &{{classname}}{}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1205,4 +1205,98 @@ public void doNotWrapSingleAllOfRefs() {
assertNotNull(allOfRefWithDescriptionAndReadonly.getAllOf());
assertEquals(numberRangeRef, ((Schema) allOfRefWithDescriptionAndReadonly.getAllOf().get(0)).get$ref());
}

@Test
public void testDeduplicationAddsAliasName() {
// Test that when inline schemas are deduplicated, the x-alias-name extension is set
OpenAPI openapi = new OpenAPI();
openapi.setComponents(new Components());

// Create two models with identical inline schemas that will be deduplicated
openapi.getComponents().addSchemas("ModelA", new ObjectSchema()
.addProperty("name", new StringSchema())
.addProperty("details", new ObjectSchema()
.addProperty("field1", new StringSchema())
.addProperty("field2", new IntegerSchema())));

openapi.getComponents().addSchemas("ModelB", new ObjectSchema()
.addProperty("title", new StringSchema())
.addProperty("info", new ObjectSchema()
.addProperty("field1", new StringSchema())
.addProperty("field2", new IntegerSchema())));

new InlineModelResolver().flatten(openapi);

// Check ModelA's property reference
Schema modelA = openapi.getComponents().getSchemas().get("ModelA");
assertNotNull(modelA);
Schema detailsRef = (Schema) modelA.getProperties().get("details");
assertNotNull(detailsRef);
assertNotNull(detailsRef.get$ref());
assertEquals("#/components/schemas/ModelA_details", detailsRef.get$ref());

// Check ModelB's property reference - should be deduplicated to ModelA_details
Schema modelB = openapi.getComponents().getSchemas().get("ModelB");
assertNotNull(modelB);
Schema infoRef = (Schema) modelB.getProperties().get("info");
assertNotNull(infoRef);
assertNotNull(infoRef.get$ref());
// The ref should point to the first schema created (ModelA_details)
assertEquals("#/components/schemas/ModelA_details", infoRef.get$ref());

// Verify x-alias-name extension is set on the deduplicated reference
assertNotNull(infoRef.getExtensions());
assertTrue(infoRef.getExtensions().containsKey("x-alias-name"));
assertEquals("ModelB_info", infoRef.getExtensions().get("x-alias-name"));

// Verify the first reference does not have x-alias-name (it's the original)
assertNull(detailsRef.getExtensions() != null ? detailsRef.getExtensions().get("x-alias-name") : null);
}

@Test
public void testDeduplicationWithInlineSchemaNameMapping() {
// Test that x-alias-name respects inlineSchemaNameMapping configuration
OpenAPI openapi = new OpenAPI();
openapi.setComponents(new Components());

// Create two models with identical inline schemas that will be deduplicated
openapi.getComponents().addSchemas("ModelA", new ObjectSchema()
.addProperty("name", new StringSchema())
.addProperty("details", new ObjectSchema()
.addProperty("field1", new StringSchema())
.addProperty("field2", new IntegerSchema())));

openapi.getComponents().addSchemas("ModelB", new ObjectSchema()
.addProperty("title", new StringSchema())
.addProperty("info", new ObjectSchema()
.addProperty("field1", new StringSchema())
.addProperty("field2", new IntegerSchema())));

// Configure inlineSchemaNameMapping to rename ModelB_info to CustomInfoName
InlineModelResolver resolver = new InlineModelResolver();
Map<String, String> inlineSchemaNames = new HashMap<>();
inlineSchemaNames.put("ModelB_info", "CustomInfoName");
resolver.setInlineSchemaNameMapping(inlineSchemaNames);
resolver.flatten(openapi);

// Check ModelB's property reference - should be deduplicated to ModelA_details
Schema modelB = openapi.getComponents().getSchemas().get("ModelB");
assertNotNull(modelB);
Schema infoRef = (Schema) modelB.getProperties().get("info");
assertNotNull(infoRef);
assertNotNull(infoRef.get$ref());
// The ref should point to the first schema created (ModelA_details)
assertEquals("#/components/schemas/ModelA_details", infoRef.get$ref());

// Verify x-alias-name extension uses the MAPPED name, not the original name
assertNotNull(infoRef.getExtensions());
assertTrue(infoRef.getExtensions().containsKey("x-alias-name"));
assertEquals("CustomInfoName", infoRef.getExtensions().get("x-alias-name"));

// Verify CustomInfoName schema was NOT created (since it was deduplicated)
assertNull(openapi.getComponents().getSchemas().get("CustomInfoName"));

// Verify ModelA_details schema was created (the deduplicated schema)
assertNotNull(openapi.getComponents().getSchemas().get("ModelA_details"));
}
}
Loading