diff --git a/docs/generators/go.md b/docs/generators/go.md index 5ad8f47d10d4..b788f4d6a27d 100644 --- a/docs/generators/go.md +++ b/docs/generators/go.md @@ -23,6 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response. With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|
**false**
No changes to the enums are made, this is the default option.
**true**
With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.
|false| |generateInterfaces|Generate interfaces for api classes| |false| |generateMarshalJSON|Generate MarshalJSON method| |true| +|generateTypeAliasesForDedupedSchemas|Generate type aliases for deduplicated inline model schemas. When the InlineModelResolver deduplicates inline schemas with identical structures, this option creates type aliases using the original inline model names, preserving naming context from the OpenAPI specification.| |true| |generateUnmarshalJSON|Generate UnmarshalJSON method| |true| |hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true| |isGoSubmodule|whether the generated Go module is a submodule| |false| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index 7251cb4dadb4..c8497b403232 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -449,6 +449,9 @@ public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, public static final String GENERATE_UNMARSHAL_JSON = "generateUnmarshalJSON"; public static final String GENERATE_UNMARSHAL_JSON_DESC = "Generate UnmarshalJSON method"; + + public static final String GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS = "generateTypeAliasesForDedupedSchemas"; + public static final String GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS_DESC = "Generate type aliases for deduplicated inline model schemas (Go only)"; public static final String MAX_ATTEMPTS_FOR_RETRY = "maxAttemptsForRetry"; public static final String WAIT_TIME_OF_THREAD = "waitTimeMillis"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java index c854389be7b2..8ae213842acf 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java @@ -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 _enum; @Getter @Setter @@ -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('\''); @@ -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) && @@ -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, diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index f91f22a93ca3..5b833ddeb770 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -4252,6 +4252,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 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java index 777fed9b2cb4..64f81ff3137d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java @@ -667,6 +667,7 @@ private void flattenComposedChildren(String key, List 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); } @@ -826,6 +827,7 @@ private void flattenProperties(OpenAPI openAPI, Map 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 { @@ -846,6 +848,7 @@ private void flattenProperties(OpenAPI openAPI, Map 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 { @@ -876,6 +879,7 @@ private void flattenProperties(OpenAPI openAPI, Map 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 { @@ -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. @@ -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"); @@ -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)); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java index 1f66d31e75f5..ec6cf38d9056 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java @@ -58,6 +58,8 @@ public abstract class AbstractGoCodegen extends DefaultCodegen implements Codege @Setter protected boolean generateUnmarshalJSON = true; @Setter + protected boolean generateTypeAliasesForDedupedSchemas = true; + @Setter protected boolean useDefaultValuesForRequiredVars = false; @Setter @@ -736,6 +738,21 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert property.vendorExtensions.put("x-golang-is-container", true); } } + + private void swapDataTypeAndAlias(CodegenProperty property, Map 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) { @@ -775,7 +792,27 @@ public ModelsMap postProcessModels(ModelsMap objs) { codegenProperties.addAll(inheritedProperties); } + // Collect reused model properties for type alias generation + Map typeAliasesMap = new LinkedHashMap<>(); + for (CodegenProperty cp : codegenProperties) { + // Swap dataType and dataTypeAlias so fields use the alias name (only if feature is enabled) + if (generateTypeAliasesForDedupedSchemas) { + 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; @@ -855,6 +892,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 (only if feature is enabled) + if (generateTypeAliasesForDedupedSchemas && !typeAliasesMap.isEmpty()) { + List> typeAliases = new ArrayList<>(); + for (Map.Entry entry : typeAliasesMap.entrySet()) { + if (!entry.getKey().equals(entry.getValue())) { + Map 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 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java index cb3e32f00b90..56f879c52517 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java @@ -153,6 +153,7 @@ public GoClientCodegen() { cliOptions.add(CliOption.newBoolean(WITH_GO_MOD, "Generate go.mod and go.sum", true)); cliOptions.add(CliOption.newBoolean(CodegenConstants.GENERATE_MARSHAL_JSON, CodegenConstants.GENERATE_MARSHAL_JSON_DESC, true)); cliOptions.add(CliOption.newBoolean(CodegenConstants.GENERATE_UNMARSHAL_JSON, CodegenConstants.GENERATE_UNMARSHAL_JSON_DESC, true)); + cliOptions.add(CliOption.newBoolean(CodegenConstants.GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS, CodegenConstants.GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS_DESC, true)); CliOption enumUnknownDefaultCaseOpt = CliOption.newBoolean( CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, @@ -312,6 +313,10 @@ public void processOpts() { setGenerateUnmarshalJSON(Boolean.parseBoolean(additionalProperties.get(CodegenConstants.GENERATE_UNMARSHAL_JSON).toString())); } + if (additionalProperties.containsKey(CodegenConstants.GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS)) { + setGenerateTypeAliasesForDedupedSchemas(Boolean.parseBoolean(additionalProperties.get(CodegenConstants.GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS).toString())); + } + // add lambda for mustache templates to handle oneOf/anyOf naming // e.g. []string => ArrayOfString diff --git a/modules/openapi-generator/src/main/resources/go/model_simple.mustache b/modules/openapi-generator/src/main/resources/go/model_simple.mustache index 2690fb9bbf5d..011cccb79071 100644 --- a/modules/openapi-generator/src/main/resources/go/model_simple.mustache +++ b/modules/openapi-generator/src/main/resources/go/model_simple.mustache @@ -1,3 +1,10 @@ +{{#vendorExtensions.x-go-has-type-aliases}} +// Type aliases for deduplicated inline model schemas +{{#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}}{} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/InlineModelResolverTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/InlineModelResolverTest.java index 0670942f8fd0..cb4a112754ca 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/InlineModelResolverTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/InlineModelResolverTest.java @@ -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 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")); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java index 3c39998ff0aa..1340d6bccc63 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java @@ -54,6 +54,7 @@ protected void verifyOptions() { verify(clientCodegen).setWithGoMod(GoClientOptionsProvider.WITH_GO_MOD_VALUE); verify(clientCodegen).setGenerateMarshalJSON(GoClientOptionsProvider.GENERATE_MARSHAL_JSON_VALUE); verify(clientCodegen).setGenerateUnmarshalJSON(GoClientOptionsProvider.GENERATE_UNMARSHAL_JSON_VALUE); + verify(clientCodegen).setGenerateTypeAliasesForDedupedSchemas(GoClientOptionsProvider.GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS_VALUE); verify(clientCodegen).setUseDefaultValuesForRequiredVars(GoClientOptionsProvider.USE_DEFAULT_VALUES_FOR_REQUIRED_VARS_VALUE); verify(clientCodegen).setEnumUnknownDefaultCase(Boolean.parseBoolean(GoClientOptionsProvider.ENUM_UNKNOWN_DEFAULT_CASE_VALUE)); } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java index e0829f2e0a6f..43a867e62ec5 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java @@ -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 { @@ -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"); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/GoClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/GoClientOptionsProvider.java index cfb8ab1c47a7..6b60690bfa91 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/GoClientOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/GoClientOptionsProvider.java @@ -39,6 +39,7 @@ public class GoClientOptionsProvider implements OptionsProvider { public static final boolean WITH_GO_MOD_VALUE = true; public static final boolean GENERATE_MARSHAL_JSON_VALUE = true; public static final boolean GENERATE_UNMARSHAL_JSON_VALUE = true; + public static final boolean GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS_VALUE = true; public static final boolean USE_DEFAULT_VALUES_FOR_REQUIRED_VARS_VALUE = true; public static final String ENUM_UNKNOWN_DEFAULT_CASE_VALUE = "false"; @@ -64,6 +65,7 @@ public Map createOptions() { .put(CodegenConstants.WITH_GO_MOD, "true") .put(CodegenConstants.GENERATE_MARSHAL_JSON, "true") .put(CodegenConstants.GENERATE_UNMARSHAL_JSON, "true") + .put(CodegenConstants.GENERATE_TYPE_ALIASES_FOR_DEDUPED_SCHEMAS, "true") .put("generateInterfaces", "true") .put("structPrefix", "true") .put(CodegenConstants.USE_DEFAULT_VALUES_FOR_REQUIRED_VARS, "true") diff --git a/modules/openapi-generator/src/test/resources/3_0/inline-deduplicated-schemas.yaml b/modules/openapi-generator/src/test/resources/3_0/inline-deduplicated-schemas.yaml new file mode 100644 index 000000000000..533d412bf46c --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/inline-deduplicated-schemas.yaml @@ -0,0 +1,144 @@ +openapi: 3.0.0 +info: + title: OpenAPI Deduplication Demo + version: 1.0.0 + description: > + This spec demonstrates OpenAPI Generator schema deduplication and type alias generation in Go: + inline objects (direct properties), nested inline objects (arrays), and objects with different property requirements. + +paths: + /demo: + get: + summary: Demo endpoint + operationId: getDemo + responses: + '200': + description: Demo response + content: + application/json: + schema: + $ref: '#/components/schemas/DemoResponse' + +components: + schemas: + PhoneNumber: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + + FaxNumber: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + + DemoResponse: + type: object + properties: + inlinePhone1: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + inlinePhone2: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + + nestedPhones: + type: array + items: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + + phoneHistory: + type: array + items: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + + optionalNumber: + type: object + properties: + countryCode: + type: string + number: + type: string + requiredNumber: + type: object + required: + - number + properties: + countryCode: + type: string + number: + type: string + + # Test cases for additionalProperties: true (MapSchema) + # These should also generate type aliases even though isModel=false + transactOptions: + type: array + items: + type: object + additionalProperties: true + properties: + code: + type: string + description: + type: string + name: + type: string + + otherOptions: + type: array + items: + type: object + additionalProperties: true + properties: + code: + type: string + description: + type: string + name: + type: string + + directMapObject: + type: object + additionalProperties: true + properties: + code: + type: string + description: + type: string + name: + type: string diff --git a/samples/server/petstore/java-undertow/src/main/java/org/openapitools/handler/PathHandlerInterface.java b/samples/server/petstore/java-undertow/src/main/java/org/openapitools/handler/PathHandlerInterface.java index d5faaeaaa5fe..d43b1a007b8e 100644 --- a/samples/server/petstore/java-undertow/src/main/java/org/openapitools/handler/PathHandlerInterface.java +++ b/samples/server/petstore/java-undertow/src/main/java/org/openapitools/handler/PathHandlerInterface.java @@ -574,13 +574,13 @@ public interface PathHandlerInterface { *

* * - *

Response headers: [CodegenProperty{openApiType='string', baseName='Set-Cookie', complexType='null', getter='getSetCookie', setter='setSetCookie', description='Cookie authentication key for use with the `api_key` apiKey authentication.', dataType='String', datatypeWithEnum='String', dataFormat='null', name='setCookie', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.Set-Cookie;', baseType='String', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='Cookie authentication key for use with the `api_key` apiKey authentication.', maxLength=null, minLength=null, pattern='null', example='AUTH_KEY=abcde12345; Path=/; HttpOnly', jsonSchema='{ + *

Response headers: [CodegenProperty{openApiType='string', baseName='Set-Cookie', complexType='null', getter='getSetCookie', setter='setSetCookie', description='Cookie authentication key for use with the `api_key` apiKey authentication.', dataType='String', dataTypeAlias='null', datatypeWithEnum='String', dataFormat='null', name='setCookie', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.Set-Cookie;', baseType='String', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='Cookie authentication key for use with the `api_key` apiKey authentication.', maxLength=null, minLength=null, pattern='null', example='AUTH_KEY=abcde12345; Path=/; HttpOnly', jsonSchema='{ "example" : "AUTH_KEY=abcde12345; Path=/; HttpOnly", "type" : "string" -}', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, isPrimitiveType=true, isModel=false, isContainer=false, isString=true, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='setCookie', nameInPascalCase='SetCookie', nameInSnakeCase='SET_COOKIE', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=true, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=null, dependentRequired=null, contains=null}, CodegenProperty{openApiType='integer', baseName='X-Rate-Limit', complexType='null', getter='getxRateLimit', setter='setxRateLimit', description='calls per hour allowed by the user', dataType='Integer', datatypeWithEnum='Integer', dataFormat='int32', name='xRateLimit', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.X-Rate-Limit;', baseType='Integer', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='calls per hour allowed by the user', maxLength=null, minLength=null, pattern='null', example='null', jsonSchema='{ +}', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, isPrimitiveType=true, isModel=false, isContainer=false, isString=true, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='setCookie', nameInPascalCase='SetCookie', nameInSnakeCase='SET_COOKIE', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=true, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=null, dependentRequired=null, contains=null}, CodegenProperty{openApiType='integer', baseName='X-Rate-Limit', complexType='null', getter='getxRateLimit', setter='setxRateLimit', description='calls per hour allowed by the user', dataType='Integer', dataTypeAlias='null', datatypeWithEnum='Integer', dataFormat='int32', name='xRateLimit', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.X-Rate-Limit;', baseType='Integer', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='calls per hour allowed by the user', maxLength=null, minLength=null, pattern='null', example='null', jsonSchema='{ "format" : "int32", "type" : "integer" -}', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, isPrimitiveType=true, isModel=false, isContainer=false, isString=false, isNumeric=true, isInteger=true, isShort=true, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='xRateLimit', nameInPascalCase='XRateLimit', nameInSnakeCase='X_RATE_LIMIT', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=true, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=int32, dependentRequired=null, contains=null}, CodegenProperty{openApiType='string', baseName='X-Expires-After', complexType='Date', getter='getxExpiresAfter', setter='setxExpiresAfter', description='date in UTC when token expires', dataType='Date', datatypeWithEnum='Date', dataFormat='date-time', name='xExpiresAfter', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.X-Expires-After;', baseType='Date', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='date in UTC when token expires', maxLength=null, minLength=null, pattern='null', example='null', jsonSchema='{ +}', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, isPrimitiveType=true, isModel=false, isContainer=false, isString=false, isNumeric=true, isInteger=true, isShort=true, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='xRateLimit', nameInPascalCase='XRateLimit', nameInSnakeCase='X_RATE_LIMIT', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=true, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=int32, dependentRequired=null, contains=null}, CodegenProperty{openApiType='string', baseName='X-Expires-After', complexType='Date', getter='getxExpiresAfter', setter='setxExpiresAfter', description='date in UTC when token expires', dataType='Date', dataTypeAlias='null', datatypeWithEnum='Date', dataFormat='date-time', name='xExpiresAfter', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.X-Expires-After;', baseType='Date', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='date in UTC when token expires', maxLength=null, minLength=null, pattern='null', example='null', jsonSchema='{ "format" : "date-time", "type" : "string" }', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, isPrimitiveType=false, isModel=false, isContainer=false, isString=false, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=true, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='xExpiresAfter', nameInPascalCase='XExpiresAfter', nameInSnakeCase='X_EXPIRES_AFTER', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=true, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=date-time, dependentRequired=null, contains=null}]