diff --git a/docs/generators/java-microprofile.md b/docs/generators/java-microprofile.md index 240e822a7f8c..013416bfa432 100644 --- a/docs/generators/java-microprofile.md +++ b/docs/generators/java-microprofile.md @@ -95,6 +95,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi| |useAbstractionForFiles|Use alternative types instead of java.io.File to allow passing bytes without a file on disk. Available on resttemplate, webclient, restclient, libraries| |false| |useBeanValidation|Use BeanValidation API annotations| |false| +|useDeductionForOneOfInterfaces|whether to use deduction for generated oneOf interfaces| |false| |useEnumCaseInsensitive|Use `equalsIgnoreCase` when String for enum comparison| |false| |useGzipFeature|Send gzip-encoded requests| |false| |useJackson3|Use Jackson 3 instead of Jackson 2. Supported for 'native' and 'apache-httpclient' libraries (requires Java 17+) and for Spring 'resttemplate', 'webclient', and 'restclient' libraries (require useSpringBoot4=true).| |false| diff --git a/docs/generators/java.md b/docs/generators/java.md index b1046f765a78..7b96612253e6 100644 --- a/docs/generators/java.md +++ b/docs/generators/java.md @@ -95,6 +95,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi| |useAbstractionForFiles|Use alternative types instead of java.io.File to allow passing bytes without a file on disk. Available on resttemplate, webclient, restclient, libraries| |false| |useBeanValidation|Use BeanValidation API annotations| |false| +|useDeductionForOneOfInterfaces|whether to use deduction for generated oneOf interfaces| |false| |useEnumCaseInsensitive|Use `equalsIgnoreCase` when String for enum comparison| |false| |useGzipFeature|Send gzip-encoded requests| |false| |useJackson3|Use Jackson 3 instead of Jackson 2. Supported for 'native' and 'apache-httpclient' libraries (requires Java 17+) and for Spring 'resttemplate', 'webclient', and 'restclient' libraries (require useSpringBoot4=true).| |false| 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 874b28d0bcca..145177d79a89 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 @@ -118,6 +118,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code public static final String DEFAULT_TEST_FOLDER = "${project.build.directory}/generated-test-sources/openapi"; public static final String GENERATE_CONSTRUCTOR_WITH_ALL_ARGS = "generateConstructorWithAllArgs"; public static final String GENERATE_BUILDERS = "generateBuilders"; + public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces"; @Getter @Setter protected String dateLibrary = "java8"; @@ -225,6 +226,8 @@ protected enum ENUM_PROPERTY_NAMING_TYPE {MACRO_CASE, legacy, original} @Setter protected boolean useJspecify; protected JSpecifyNullableLambda jSpecifyNullableLambda; + @Getter @Setter + protected boolean useDeductionForOneOfInterfaces = false; private Map schemaKeyToModelNameCache = new HashMap<>(); @@ -608,6 +611,7 @@ public void processOpts() { convertPropertyToBooleanAndWriteBack(USE_ONE_OF_INTERFACES, this::setUseOneOfInterfaces); convertPropertyToStringAndWriteBack(CodegenConstants.ENUM_PROPERTY_NAMING, this::setEnumPropertyNaming); convertPropertyToBooleanAndWriteBack(USE_JSPECIFY, this::setUseJspecify); + convertPropertyToBooleanAndWriteBack(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, this::setUseDeductionForOneOfInterfaces); if (!StringUtils.isEmpty(parentGroupId) && !StringUtils.isEmpty(parentArtifactId) && !StringUtils.isEmpty(parentVersion)) { additionalProperties.put("parentOverridden", true); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java index 26f1b6bd3abe..2357579a7de2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java @@ -285,6 +285,7 @@ public JavaClientCodegen() { cliOptions.add(CliOption.newBoolean(USE_SEALED_ONE_OF_INTERFACES, "Generate the oneOf interfaces as sealed interfaces. Only supported for WebClient and RestClient.", this.useSealedOneOfInterfaces)); cliOptions.add(CliOption.newBoolean(USE_UNARY_INTERCEPTOR, "If true it will generate ResponseInterceptors using a UnaryOperator. This can be usefull for manipulating the request before it gets passed, for example doing your own decryption", this.useUnaryInterceptor)); cliOptions.add(CliOption.newBoolean(USE_JSPECIFY, "Use Jspecify for null checks. Only supported for " + JSPECIFY_SUPPORTED_LIBRARIES, useJspecify)); + cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces)); supportedLibraries.put(JERSEY2, "HTTP client: Jersey client 2.25.1. JSON processing: Jackson 2.17.1"); supportedLibraries.put(JERSEY3, "HTTP client: Jersey client 3.1.1. JSON processing: Jackson 2.17.1"); 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 76e20d09be36..0f7ab324f5c4 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 @@ -110,7 +110,6 @@ public class SpringCodegen extends AbstractJavaCodegen public static final String USE_SEALED = "useSealed"; public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable"; public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation"; - public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces"; public static final String SPRING_API_VERSION = "springApiVersion"; public static final String USE_JACKSON_3 = "useJackson3"; public static final String JACKSON2_PACKAGE = "com.fasterxml.jackson"; @@ -187,8 +186,6 @@ public enum RequestMappingMode { @Getter @Setter protected boolean useSpringBuiltInValidation = false; @Getter @Setter - protected boolean useDeductionForOneOfInterfaces = false; - @Getter @Setter protected boolean useJackson3 = false; @Getter @Setter protected boolean additionalNotNullAnnotations = false; @@ -557,7 +554,6 @@ public void processOpts() { } convertPropertyToBooleanAndWriteBack(OPTIONAL_ACCEPT_NULLABLE, this::setOptionalAcceptNullable); convertPropertyToBooleanAndWriteBack(USE_SPRING_BUILT_IN_VALIDATION, this::setUseSpringBuiltInValidation); - convertPropertyToBooleanAndWriteBack(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, this::setUseDeductionForOneOfInterfaces); additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda()); diff --git a/modules/openapi-generator/src/main/resources/Java/deductionAnnotation.mustache b/modules/openapi-generator/src/main/resources/Java/deductionAnnotation.mustache new file mode 100644 index 000000000000..c59bc4cbfd17 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Java/deductionAnnotation.mustache @@ -0,0 +1,12 @@ +{{^discriminator}} +{{#jackson}} +{{#useDeductionForOneOfInterfaces}} + @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) + @JsonSubTypes({ + {{#interfaceModels}} + @JsonSubTypes.Type(value = {{classname}}.class){{^-last}}, {{/-last}} + {{/interfaceModels}} + }) +{{/useDeductionForOneOfInterfaces}} +{{/jackson}} +{{/discriminator}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/Java/oneof_interface.mustache b/modules/openapi-generator/src/main/resources/Java/oneof_interface.mustache index 7a77b67ae169..742de98954c7 100644 --- a/modules/openapi-generator/src/main/resources/Java/oneof_interface.mustache +++ b/modules/openapi-generator/src/main/resources/Java/oneof_interface.mustache @@ -1,4 +1,4 @@ -{{>additionalOneOfTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}} +{{>additionalOneOfTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>deductionAnnotation}}{{>xmlAnnotation}} {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index 69fb7de07ada..298f4c8b059f 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -4452,4 +4452,18 @@ void oneOf_issue_912() { .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Source.class", "name", "\"source\"")); } + @Test + public void testUseDeductionForOneInterfaces() { + final Map files = generateFromContract("src/test/resources/3_1/oneof_polymorphism_and_inheritance.yaml", RESTCLIENT, + Map.of(USE_ONE_OF_INTERFACES, "true", USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "true")); + JavaFileAssert.assertThat(files.get("Animal.java")).fileContains("@JsonSubTypes") + .isInterface() + .assertTypeAnnotations().containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Dog.class")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Cat.class")) + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("use", "JsonTypeInfo.Id.DEDUCTION")); + + } + + }