Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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;
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
@Getter @Setter
public List<String> _enum;
@Getter @Setter
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 @@ -998,6 +998,8 @@ private Schema makeSchemaInComponents(String name, Schema schema) {
Schema refSchema;
if (existing != null) {
refSchema = new Schema().$ref(existing);
// Store the name this schema would have had if not deduplicated
refSchema.addExtension("x-alias-name", name);
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
} 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
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 @@ -24,13 +24,18 @@
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.InlineModelResolver;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.languages.GoClientCodegen;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;

@SuppressWarnings("static-method")
public class GoModelTest {
Expand Down Expand Up @@ -324,4 +329,168 @@ public void modelNameMappingsTest(String name, String expectedName, String expec
Assert.assertEquals(cm.name, name);
Assert.assertEquals(cm.classname, expectedName);
}

@Test(description = "test that direct $ref usage does NOT create aliases")
public void directRefNoAliasTest() {
final Schema phoneNumberSchema = new Schema()
.type("object")
.addProperty("countryCode", new StringSchema())
.addProperty("number", new StringSchema())
.addRequiredItem("number");

final Schema personSchema = new Schema()
.type("object")
.addProperty("name", new StringSchema())
.addProperty("mobile", new Schema().$ref("#/components/schemas/PhoneNumber"))
.addProperty("home", new Schema().$ref("#/components/schemas/PhoneNumber"))
.addRequiredItem("name");

final DefaultCodegen codegen = new GoClientCodegen();
OpenAPI openAPI = TestUtils.createOpenAPI();
openAPI.getComponents().addSchemas("PhoneNumber", phoneNumberSchema);
openAPI.getComponents().addSchemas("Person", personSchema);
codegen.setOpenAPI(openAPI);

final CodegenModel personModel = codegen.fromModel("Person", personSchema);
Assert.assertEquals(personModel.name, "Person");
Assert.assertEquals(personModel.vars.size(), 3);

// Direct $refs should not have aliases (only deduplicated inline schemas get aliases)
final CodegenProperty mobileProperty = personModel.vars.stream()
.filter(v -> v.baseName.equals("mobile"))
.findFirst()
.orElse(null);
Assert.assertNotNull(mobileProperty);
Assert.assertNull(mobileProperty.dataTypeAlias);
Assert.assertEquals(mobileProperty.dataType, "PhoneNumber");

final CodegenProperty homeProperty = personModel.vars.stream()
.filter(v -> v.baseName.equals("home"))
.findFirst()
.orElse(null);
Assert.assertNotNull(homeProperty);
Assert.assertNull(homeProperty.dataTypeAlias);
Assert.assertEquals(homeProperty.dataType, "PhoneNumber");
}

@Test(description = "test type aliases for deduplicated inline schemas")
public void typeAliasForDeduplicatedInlineSchemasTest() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/inline-deduplicated-schemas.yaml");
final GoClientCodegen codegen = new GoClientCodegen();
codegen.setOpenAPI(openAPI);

Schema demoResponseSchema = openAPI.getComponents().getSchemas().get("DemoResponse");
CodegenModel demoModel = codegen.fromModel("DemoResponse", demoResponseSchema);

// Call postProcessModels to trigger Go-specific dataType/dataTypeAlias swapping
ModelsMap modelsMap = new ModelsMap();
ModelMap modelMap = new ModelMap();
modelMap.setModel(demoModel);
modelsMap.setModels(Arrays.asList(modelMap));
modelsMap.setImports(new ArrayList<>());
modelsMap = codegen.postProcessModels(modelsMap);
demoModel = modelsMap.getModels().get(0).getModel();

Assert.assertEquals(demoModel.name, "DemoResponse");

// inlinePhone1 is the original (not deduplicated)
final CodegenProperty inlinePhone1 = demoModel.vars.stream()
.filter(v -> v.baseName.equals("inlinePhone1"))
.findFirst()
.orElse(null);
Assert.assertNotNull(inlinePhone1);
Assert.assertNull(inlinePhone1.dataTypeAlias);
Assert.assertEquals(inlinePhone1.dataType, "DemoResponseInlinePhone1");

// inlinePhone2 is deduplicated with inlinePhone1
final CodegenProperty inlinePhone2 = demoModel.vars.stream()
.filter(v -> v.baseName.equals("inlinePhone2"))
.findFirst()
.orElse(null);
Assert.assertNotNull(inlinePhone2);
Assert.assertNotNull(inlinePhone2.dataTypeAlias);
Assert.assertEquals(inlinePhone2.dataType, "DemoResponseInlinePhone2");
Assert.assertEquals(inlinePhone2.dataTypeAlias, "DemoResponseInlinePhone1");

// nestedPhones array items are deduplicated
final CodegenProperty nestedPhones = demoModel.vars.stream()
.filter(v -> v.baseName.equals("nestedPhones"))
.findFirst()
.orElse(null);
Assert.assertNotNull(nestedPhones);
Assert.assertTrue(nestedPhones.isContainer);
Assert.assertNotNull(nestedPhones.items);
Assert.assertNotNull(nestedPhones.items.dataTypeAlias);
Assert.assertEquals(nestedPhones.items.dataType, "DemoResponseNestedPhonesInner");
Assert.assertEquals(nestedPhones.items.dataTypeAlias, "DemoResponseInlinePhone1");
Assert.assertEquals(nestedPhones.dataType, "[]DemoResponseNestedPhonesInner");

// phoneHistory array items are deduplicated
final CodegenProperty phoneHistory = demoModel.vars.stream()
.filter(v -> v.baseName.equals("phoneHistory"))
.findFirst()
.orElse(null);
Assert.assertNotNull(phoneHistory);
Assert.assertTrue(phoneHistory.isContainer);
Assert.assertNotNull(phoneHistory.items);
Assert.assertNotNull(phoneHistory.items.dataTypeAlias);
Assert.assertEquals(phoneHistory.items.dataType, "DemoResponsePhoneHistoryInner");
Assert.assertEquals(phoneHistory.items.dataTypeAlias, "DemoResponseInlinePhone1");
Assert.assertEquals(phoneHistory.dataType, "[]DemoResponsePhoneHistoryInner");

// optionalNumber has different structure (no required fields)
final CodegenProperty optionalNumber = demoModel.vars.stream()
.filter(v -> v.baseName.equals("optionalNumber"))
.findFirst()
.orElse(null);
Assert.assertNotNull(optionalNumber);
Assert.assertNull(optionalNumber.dataTypeAlias);
Assert.assertEquals(optionalNumber.dataType, "DemoResponseOptionalNumber");

// requiredNumber is deduplicated with inlinePhone1
final CodegenProperty requiredNumber = demoModel.vars.stream()
.filter(v -> v.baseName.equals("requiredNumber"))
.findFirst()
.orElse(null);
Assert.assertNotNull(requiredNumber);
Assert.assertNotNull(requiredNumber.dataTypeAlias);
Assert.assertEquals(requiredNumber.dataType, "DemoResponseRequiredNumber");
Assert.assertEquals(requiredNumber.dataTypeAlias, "DemoResponseInlinePhone1");

// transactOptions array items are deduplicated (MapSchema with additionalProperties: true)
final CodegenProperty transactOptions = demoModel.vars.stream()
.filter(v -> v.baseName.equals("transactOptions"))
.findFirst()
.orElse(null);
Assert.assertNotNull(transactOptions);
Assert.assertTrue(transactOptions.isContainer);
Assert.assertNotNull(transactOptions.items);
// transactOptions is the ORIGINAL, so items should NOT have dataTypeAlias
Assert.assertNull(transactOptions.items.dataTypeAlias);
Assert.assertEquals(transactOptions.items.dataType, "DemoResponseTransactOptionsInner");
Assert.assertEquals(transactOptions.dataType, "[]DemoResponseTransactOptionsInner");

// otherOptions array items are DEDUPLICATED with transactOptions (MapSchema with additionalProperties: true)
final CodegenProperty otherOptions = demoModel.vars.stream()
.filter(v -> v.baseName.equals("otherOptions"))
.findFirst()
.orElse(null);
Assert.assertNotNull(otherOptions);
Assert.assertTrue(otherOptions.isContainer);
Assert.assertNotNull(otherOptions.items);
Assert.assertNotNull(otherOptions.items.dataTypeAlias);
Assert.assertEquals(otherOptions.items.dataType, "DemoResponseOtherOptionsInner");
Assert.assertEquals(otherOptions.items.dataTypeAlias, "DemoResponseTransactOptionsInner");
Assert.assertEquals(otherOptions.dataType, "[]DemoResponseOtherOptionsInner");

// directMapObject is DEDUPLICATED with transactOptions items (MapSchema with additionalProperties: true)
final CodegenProperty directMapObject = demoModel.vars.stream()
.filter(v -> v.baseName.equals("directMapObject"))
.findFirst()
.orElse(null);
Assert.assertNotNull(directMapObject);
Assert.assertNotNull(directMapObject.dataTypeAlias);
Assert.assertEquals(directMapObject.dataType, "DemoResponseDirectMapObject");
Assert.assertEquals(directMapObject.dataTypeAlias, "DemoResponseTransactOptionsInner");
}
}
Loading