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 fc98a969d14e..1367ef7e3818 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 @@ -813,7 +813,7 @@ public void postProcessParameter(CodegenParameter parameter) { @Override @SuppressWarnings("unused") public void preprocessOpenAPI(OpenAPI openAPI) { - if (useOneOfInterfaces) { + if (useOneOfInterfaces && openAPI.getComponents() != null) { // we process the openapi schema here to find oneOf schemas and create interface models for them Map schemas = new HashMap(openAPI.getComponents().getSchemas()); if (schemas == null) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index a5d05d09cb69..cfc8f6d5f6a3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -1247,6 +1247,16 @@ public Map postProcessModels(Map objs) { } } + // add implements for serializable/parcelable to all models + List> models = (List>) objs.get("models"); + for (Map mo : models) { + CodegenModel cm = (CodegenModel) mo.get("model"); + if (this.serializableModel) { + cm.getVendorExtensions().putIfAbsent("x-implements", new ArrayList()); + ((ArrayList) cm.getVendorExtensions().get("x-implements")).add("Serializable"); + } + } + return postProcessModelsEnum(objs); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index b0db9a4c124d..19d4fe675b77 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -233,7 +233,7 @@ public void processOpts() { } super.processOpts(); - + useOneOfInterfaces = true; // clear model and api doc template as this codegen // does not support auto-generated markdown doc at the moment //TODO: add doc templates @@ -874,4 +874,37 @@ public void setPerformBeanValidation(boolean performBeanValidation) { public void setUseOptional(boolean useOptional) { this.useOptional = useOptional; } -} + + @Override + public void postProcessParameter(CodegenParameter p) { + // we use a custom version of this function to remove the l, d, and f suffixes from Long/Double/Float + // defaultValues + // remove the l because our users will use Long.parseLong(String defaultValue) + // remove the d because our users will use Double.parseDouble(String defaultValue) + // remove the f because our users will use Float.parseFloat(String defaultValue) + // NOTE: for CodegenParameters we DO need these suffixes because those defaultValues are used as java value + // literals assigned to Long/Double/Float + if (p.defaultValue == null) { + return; + } + Boolean fixLong = (p.isLong && "l".equals(p.defaultValue.substring(p.defaultValue.length()-1))); + Boolean fixDouble = (p.isDouble && "d".equals(p.defaultValue.substring(p.defaultValue.length()-1))); + Boolean fixFloat = (p.isFloat && "f".equals(p.defaultValue.substring(p.defaultValue.length()-1))); + if (fixLong || fixDouble || fixFloat) { + p.defaultValue = p.defaultValue.substring(0, p.defaultValue.length()-1); + } + } + + @Override + public void addImportsToOneOfInterface(List> imports) { + for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) { + Map oneImport = new HashMap() {{ + put("import", importMapping.get(i)); + }}; + if (!imports.contains(oneImport)) { + imports.add(oneImport); + } + } + } + +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/model.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/model.mustache index 78c3c9ae19d4..8b39ebd13c30 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/model.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/model.mustache @@ -37,7 +37,7 @@ import org.springframework.hateoas.RepresentationModel; {{>enumOuterClass}} {{/isEnum}} {{^isEnum}} -{{>pojo}} +{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}} {{/isEnum}} {{/model}} {{/models}} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache new file mode 100644 index 000000000000..4500871766c1 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache @@ -0,0 +1,6 @@ +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}} +public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + {{#discriminator}} + public {{propertyType}} {{propertyGetter}}(); + {{/discriminator}} +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index de2f292341ea..371364e96838 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -3,7 +3,7 @@ */{{#description}} @ApiModel(description = "{{{description}}}"){{/description}} {{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}{{>additionalModelTypeAnnotations}} -public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}} {{#serializableModel}}implements Serializable{{/serializableModel}} { +public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}} {{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}} { {{#serializableModel}} private static final long serialVersionUID = 1L; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index 3e8d89772093..0ded0f75a4c1 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -653,4 +653,43 @@ public void reactiveMapTypeRequestMonoTest() throws IOException { assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApi.java"), "Mono"); assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApiDelegate.java"), "Mono"); } + + @Test + public void oneOf_5381() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/issue_5381.yaml", null, new ParseOptions()).getOpenAPI(); + + SpringCodegen codegen = new SpringCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); + codegen.setUseOneOfInterfaces(true); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + codegen.setHateoas(true); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + //generator.setGeneratorPropertyDefault(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); + + codegen.setUseOneOfInterfaces(true); + codegen.setJava8(true); + codegen.setLegacyDiscriminatorBehavior(false); + + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + + generator.opts(input).generate(); + + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public class Foo implements FooRefOrValue"); + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public class FooRef implements FooRefOrValue"); + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/issue_5381.yaml b/modules/openapi-generator/src/test/resources/3_0/issue_5381.yaml new file mode 100644 index 000000000000..192c958f7245 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/issue_5381.yaml @@ -0,0 +1,134 @@ +openapi: 3.0.1 +info: + title: ByRefOrValue + description: > + This tests for a oneOf interface representation + version: 0.0.1 +servers: + - url: "http://localhost:8080" +tags: + - name: Foo +paths: + /foo: + get: + tags: + - Foo + summary: GET all Foos + operationId: getAllFoos + responses: + '200': + $ref: '#/components/responses/200FooArray' + post: + tags: + - Foo + summary: Create a Foo + operationId: createFoo + requestBody: + $ref: '#/components/requestBodies/Foo' + responses: + '201': + $ref: '#/components/responses/201Foo' + +components: + schemas: + Entity: + type: object + allOf: + - "$ref": "#/components/schemas/Addressable" + - "$ref": "#/components/schemas/Extensible" + + EntityRef: + description: Entity reference schema to be use for all entityRef class. + type: object + properties: + name: + type: string + description: Name of the related entity. + '@referredType': + type: string + description: The actual type of the target instance when needed for disambiguation. + allOf: + - $ref: '#/components/schemas/Addressable' + - "$ref": "#/components/schemas/Extensible" + + + Addressable: + type: object + properties: + href: + type: string + description: Hyperlink reference + id: + type: string + description: unique identifier + description: Base schema for adressable entities + Extensible: + type: object + properties: + "@schemaLocation": + type: string + description: A URI to a JSON-Schema file that defines additional attributes + and relationships + "@baseType": + type: string + description: When sub-classing, this defines the super-class + "@type": + type: string + description: When sub-classing, this defines the sub-class Extensible name + required: + - '@type' + + FooRefOrValue: + type: object + oneOf: + - $ref: "#/components/schemas/Foo" + - $ref: "#/components/schemas/FooRef" + discriminator: + propertyName: "@type" + mapping: + Foo: "#/components/schemas/Foo" + FooRef: "#/components/schemas/FooRef" + + Foo: + type: object + properties: + fooPropA: + type: string + fooPropB: + type: string + allOf: + - $ref: '#/components/schemas/Entity' + + FooRef: + type: object + properties: + foorefPropA: + type: string + allOf: + - $ref: '#/components/schemas/EntityRef' + + requestBodies: + Foo: + description: The Foo to be created + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/Foo' + responses: + '204': + description: Deleted + content: { } + 201Foo: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/FooRefOrValue' + 200FooArray: + description: Success + content: + application/json;charset=utf-8: + schema: + type: array + items: + $ref: '#/components/schemas/FooRefOrValue' \ No newline at end of file