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 0e56b31cf6f0..caeb59e4132d 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 @@ -786,14 +786,18 @@ public ModelsMap postProcessModels(ModelsMap objs) { } } + // Determine which properties the generated model will actually render. Models rendered + // as a oneOf/anyOf union (model_oneof.mustache / model_anyof.mustache) only emit the + // composed (inherited) schemas, while models rendered as a simple struct + // (model_simple.mustache) emit their own properties. Only derive imports/tags from the + // properties that are actually emitted, otherwise unused imports (e.g. "time") leak into + // the generated code. + boolean isUnionModel = (model.oneOf != null && !model.oneOf.isEmpty()) + || (model.anyOf != null && !model.anyOf.isEmpty()); List codegenProperties = new ArrayList<>(); - if (model.getComposedSchemas() == null || (model.getComposedSchemas() != null && model.getComposedSchemas().getAllOf() != null)) { - // If the model is an allOf or does not have any composed schemas, then we can use the model's properties. + if (!isUnionModel && model.vars != null && !model.vars.isEmpty()) { codegenProperties.addAll(model.vars); } else { - // If the model is no model, but is a - // anyOf or oneOf, add all first level options - // from anyOf or oneOf. codegenProperties.addAll(inheritedProperties); } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java index 6b04bf8e8703..df112c3c827d 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientCodegenTest.java @@ -562,4 +562,61 @@ public void testOneOfUnmarshalJSONGeneratedByDefault() throws IOException { "validator.Validate", "gopkg.in/validator.v2"); } + + @Test(description = "schema with both properties and oneOf must emit json tags on properties (#24916)") + public void testOneOfWithPropertiesEmitsJsonTags() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("go") + .setInputSpec("src/test/resources/3_0/go/oneof-with-properties.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + Path modelFile = Paths.get(output + "/model_thing.go"); + TestUtils.assertFileExists(modelFile); + + // Properties must have json tags even though oneOf is present + TestUtils.assertFileContains(modelFile, + "Kind string `json:\"kind\"`"); + TestUtils.assertFileContains(modelFile, + "FirstValue []float32 `json:\"first_value,omitempty\"`"); + TestUtils.assertFileContains(modelFile, + "SecondValue []float32 `json:\"second_value,omitempty\"`"); + + // Must not be rendered as a oneOf union struct + TestUtils.assertFileNotContains(modelFile, + "GetActualInstance"); + } + + @Test(description = "anyOf union model must not derive imports from its own properties (#24916)") + public void testUnionAnyOfWithPropertiesDoesNotImportTime() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("go") + .setInputSpec("src/test/resources/3_0/go/union-anyof-with-properties.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + Path modelFile = Paths.get(output + "/model_filter_any.go"); + TestUtils.assertFileExists(modelFile); + + // Rendered as an anyOf union struct, not a simple struct of its own properties + TestUtils.assertFileContains(modelFile, + "data failed to match schemas in anyOf(FilterAny)"); + + // The union template never emits the model's own "date" property, so the + // "time" import must not be added (it would be unused and break compilation) + TestUtils.assertFileNotContains(modelFile, + "\"time\""); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/go/oneof-with-properties.yaml b/modules/openapi-generator/src/test/resources/3_0/go/oneof-with-properties.yaml new file mode 100644 index 000000000000..9c020b37fbe8 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/go/oneof-with-properties.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.3 +info: + title: oneOf with properties regression test + version: 1.0.0 +paths: + /thing: + get: + operationId: getThing + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Thing' +components: + schemas: + Thing: + type: object + required: + - kind + properties: + kind: + type: string + first_value: + type: array + items: + type: number + second_value: + type: array + items: + type: number + oneOf: + - required: + - first_value + not: + required: + - second_value + - required: + - second_value + not: + required: + - first_value diff --git a/modules/openapi-generator/src/test/resources/3_0/go/union-anyof-with-properties.yaml b/modules/openapi-generator/src/test/resources/3_0/go/union-anyof-with-properties.yaml new file mode 100644 index 000000000000..3d65963de257 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/go/union-anyof-with-properties.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.3 +info: + title: union anyOf with properties regression test + version: 1.0.0 +paths: + /filter: + get: + operationId: getFilter + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/FilterAny' +components: + schemas: + FilterAny: + anyOf: + - $ref: '#/components/schemas/FilterTypeRegex' + - $ref: '#/components/schemas/FilterTypeRange' + discriminator: + mapping: + range: '#/components/schemas/FilterTypeRange' + set: '#/components/schemas/FilterTypeRegex' + propertyName: type + properties: + type: + enum: + - set + - range + type: string + date: + type: string + format: date-time + FilterTypeRegex: + type: object + properties: + regex: + type: string + required: + - type + FilterTypeRange: + type: object + properties: + data: + type: array + items: + type: string + required: + - type \ No newline at end of file