From 6ee94d636b7a7e06b74faf4423b175a7271d5343 Mon Sep 17 00:00:00 2001 From: xtroce Date: Fri, 25 Apr 2025 11:13:36 +0200 Subject: [PATCH] issue-20718 fixed code generation for jersey2/3 and okhttp-gson when using modelMapping with a package and anyOf/oneOf (#20721) Co-authored-by: Sebastian Droeppelmann --- .../languages/AbstractJavaCodegen.java | 19 +++ .../libraries/jersey2/anyof_model.mustache | 2 +- .../libraries/jersey2/oneof_model.mustache | 2 +- .../libraries/jersey3/anyof_model.mustache | 2 +- .../libraries/jersey3/oneof_model.mustache | 2 +- .../okhttp-gson/anyof_model.mustache | 20 +-- .../okhttp-gson/oneof_model.mustache | 42 +++---- .../codegen/java/AbstractJavaCodegenTest.java | 5 + .../codegen/java/JavaClientCodegenTest.java | 116 ++++++++++++++++++ ...sue_20718-dataType_with_schema_mapping.yml | 43 +++++++ 10 files changed, 218 insertions(+), 35 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml 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 66175b1b954..9d641f52063 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 @@ -667,6 +667,9 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code additionalProperties.put("removeAnnotations", (Mustache.Lambda) (fragment, writer) -> { writer.write(removeAnnotations(fragment.execute())); }); + additionalProperties.put("sanitizeDataType", (Mustache.Lambda) (fragment, writer) -> { + writer.write(sanitizeDataType(fragment.execute())); + }); } /** @@ -1843,6 +1846,22 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code return dataType; } + /** + * Sanitize the datatype. + * This will remove all characters except alphanumeric ones. + * It will also first use {{@link #removeAnnotations(String)}} to remove the annotations added to the datatype + * @param dataType the data type string + * @return the data type string without annotations and any characters except alphanumeric ones + */ + public String sanitizeDataType(String dataType) { + String content = removeAnnotations(dataType); + if (content != null && content.matches(".*\\P{Alnum}.*")) { + content = content.replaceAll("\\P{Alnum}", ""); + } + return content; + } + + @Override public ModelsMap postProcessModels(ModelsMap objs) { // recursively add import for mapping one type to multiple imports diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/anyof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/anyof_model.mustache index 46c2cdc3a66..db8b93e6f60 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/anyof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/anyof_model.mustache @@ -218,7 +218,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * @return The actual instance of `{{{dataType}}}` * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{dataType}}} get{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}() throws ClassCastException { + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { return ({{{dataType}}})super.getActualInstance(); } diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/oneof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/oneof_model.mustache index 5b60d07434b..88e9f0f633f 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/oneof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/oneof_model.mustache @@ -280,7 +280,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * @return The actual instance of `{{{dataType}}}` * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{dataType}}} get{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}() throws ClassCastException { + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { return ({{{dataType}}})super.getActualInstance(); } diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/anyof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/anyof_model.mustache index 46c2cdc3a66..db8b93e6f60 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/anyof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/anyof_model.mustache @@ -218,7 +218,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * @return The actual instance of `{{{dataType}}}` * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{dataType}}} get{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}() throws ClassCastException { + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { return ({{{dataType}}})super.getActualInstance(); } diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/oneof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/oneof_model.mustache index 5b60d07434b..88e9f0f633f 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/oneof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/jersey3/oneof_model.mustache @@ -280,7 +280,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * @return The actual instance of `{{{dataType}}}` * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{dataType}}} get{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}() throws ClassCastException { + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { return ({{{dataType}}})super.getActualInstance(); } diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache index 564c1bb36f0..97bc82c6e6b 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache @@ -54,8 +54,8 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isArray}} {{#isArray}} - final Type typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = new TypeToken<{{{dataType}}}>(){}.getType(); - final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); {{/isArray}} {{/anyOf}} {{/composedSchemas}} @@ -74,7 +74,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im // check if the actual instance is of the type `{{{dataType}}}` if (value.getActualInstance() instanceof {{#isArray}}List{{/isArray}}{{^isArray}}{{{dataType}}}{{/isArray}}) { {{#isPrimitiveType}} - JsonPrimitive primitive = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); + JsonPrimitive primitive = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); elementAdapter.write(out, primitive); return; {{/isPrimitiveType}} @@ -82,7 +82,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{#isArray}} List list = (List) value.getActualInstance(); if (list.get(0) instanceof {{{items.dataType}}}) { - JsonArray array = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); + JsonArray array = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); elementAdapter.write(out, array); return; } @@ -90,7 +90,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isPrimitiveType}} {{^isArray}} {{^isPrimitiveType}} - JsonElement element = adapter{{{dataType}}}.toJsonTree(({{{dataType}}})value.getActualInstance()); + JsonElement element = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()); elementAdapter.write(out, element); return; {{/isPrimitiveType}} @@ -122,20 +122,20 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im if (!jsonElement.getAsJsonPrimitive().isNumber()) { throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); } - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isNumber}} {{^isNumber}} {{#isPrimitiveType}} if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { throw new IllegalArgumentException(String.format("Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); } - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isPrimitiveType}} {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} {{{dataType}}}.validateJsonElement(jsonElement); - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isPrimitiveType}} {{/isNumber}} {{/isArray}} @@ -167,7 +167,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isNumber}} {{/items}} } - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isArray}} {{classname}} ret = new {{classname}}(); ret.setActualInstance(actualAdapter.fromJsonTree(jsonElement)); @@ -291,7 +291,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * @return The actual instance of `{{{dataType}}}` * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{dataType}}} get{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}() throws ClassCastException { + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { return ({{{dataType}}})super.getActualInstance(); } diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/oneof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/oneof_model.mustache index 731b36d5748..8684087c08e 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/oneof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/oneof_model.mustache @@ -50,18 +50,18 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{^isArray}} {{^isMap}} {{^vendorExtensions.x-duplicated-data-type}} - final TypeAdapter<{{{dataType}}}> adapter{{{dataType}}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}.class)); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}.class)); {{/vendorExtensions.x-duplicated-data-type}} {{/isMap}} {{/isArray}} {{#isArray}} - final Type typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = new TypeToken<{{{dataType}}}>(){}.getType(); - final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); {{/isArray}} {{#isMap}} - final Type typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = new TypeToken<{{{dataType}}}>(){}.getType(); - final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); {{/isMap}} {{/oneOf}} {{/composedSchemas}} @@ -81,12 +81,12 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im if (value.getActualInstance() instanceof {{#isArray}}List{{/isArray}}{{#isMap}}Map{{/isMap}}{{^isMap}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isMap}}) { {{#isPrimitiveType}} {{^isMap}} - JsonPrimitive primitive = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); + JsonPrimitive primitive = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); elementAdapter.write(out, primitive); return; {{/isMap}} {{#isMap}} - JsonObject object = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonObject(); + JsonObject object = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonObject(); elementAdapter.write(out, object); return; {{/isMap}} @@ -95,7 +95,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{#isArray}} List list = (List) value.getActualInstance(); if (list.get(0) instanceof {{{items.dataType}}}) { - JsonArray array = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); + JsonArray array = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); elementAdapter.write(out, array); return; } @@ -104,7 +104,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{^isMap}} {{^isArray}} {{^isPrimitiveType}} - JsonElement element = adapter{{{dataType}}}.toJsonTree(({{{dataType}}})value.getActualInstance()); + JsonElement element = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()); elementAdapter.write(out, element); return; {{/isPrimitiveType}} @@ -163,20 +163,20 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im if (!jsonElement.getAsJsonPrimitive().isNumber()) { throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); } - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isNumber}} {{^isNumber}} {{#isPrimitiveType}} if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { throw new IllegalArgumentException(String.format("Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); } - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isPrimitiveType}} {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} - {{{dataType}}}.validateJsonElement(jsonElement); - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(jsonElement); + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isPrimitiveType}} {{/isNumber}} {{/isMap}} @@ -204,12 +204,12 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} - {{{dataType}}}.validateJsonElement(element); + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); {{/isPrimitiveType}} {{/isNumber}} {{/items}} } - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isArray}} {{#isMap}} if (!jsonElement.isJsonObject()) { @@ -235,13 +235,13 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} - {{{dataType}}}.validateJsonElement(element); + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); {{/isPrimitiveType}} {{/isNumber}} {{/items}} } {{/isFreeFormObject}} - actualAdapter = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}; + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; {{/isMap}} match++; log.log(Level.FINER, "Input data matches schema '{{{dataType}}}'"); @@ -369,7 +369,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im * @return The actual instance of `{{{dataType}}}` * @throws ClassCastException if the instance is not `{{{dataType}}}` */ - public {{{dataType}}} get{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}() throws ClassCastException { + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { return ({{{dataType}}})super.getActualInstance(); } @@ -408,7 +408,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} - {{{dataType}}}.validateJsonElement(jsonElement); + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(jsonElement); {{/isPrimitiveType}} {{/isNumber}} {{/isArray}} @@ -435,7 +435,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} - {{{dataType}}}.validateJsonElement(element); + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); {{/isPrimitiveType}} {{/isNumber}} {{/items}} @@ -465,7 +465,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im {{/isNumber}} {{^isNumber}} {{^isPrimitiveType}} - {{{dataType}}}.validateJsonElement(element); + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); {{/isPrimitiveType}} {{/isNumber}} {{/items}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java index b85b4554469..1c69672bcea 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java @@ -975,4 +975,9 @@ public class AbstractJavaCodegenTest { // DateSchema dateSchema = (DateSchema) openAPI.getPaths().get("/thingy/{date}").getPost().getParameters().get(0).getSchema(); // Assert.assertTrue(codegen.escapeQuotationMark(codegen.toExampleValue(dateSchema)).matches("2021-01-01")); // } + + @Test(description = "test sanitizing name of dataType when using schemaMapping and oneOf/allOf (issue 20718)") + public void testSanitizedDataType() { + assertThat(codegen.sanitizeDataType("org.somepkg.DataType")).isEqualTo("orgsomepkgDataType"); + } } 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 e5e14d46851..c303d903430 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 @@ -3476,4 +3476,120 @@ public class JavaClientCodegenTest { JavaFileAssert.assertThat(files.get("Type.java")).fileContains("Type implements java.io.Serializable {"); } + + /** + * This checks bug issue-20718 + * A situation when schemaMapping is used and oneOf also is used with one of the schema-mapped dataTypes and the dataType + * contains a package definition + * The dataType needs to be sanitized to generate compileable code + */ + @Test + public void testClassesAreValidJavaJersey2() { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JERSEY2) + .setSchemaMappings(Map.of( + "A", "some.pkg.A", + "B", "some.pkg.B")) + .setInputSpec("src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml") + .setOutputDir(output.toString().replace("\\", "/")); + + Map files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + File oneOfFile = files.get("ResultObjectOneOf.java"); + assertNotNull(oneOfFile); + + JavaFileAssert.assertThat(oneOfFile).fileContains( + "public some.pkg.A getsomepkgA() throws ClassCastException {", + "public some.pkg.B getsomepkgB() throws ClassCastException {" + ); + File anyOfFile = files.get("ResultObjectAnyOf.java"); + assertNotNull(anyOfFile); + + JavaFileAssert.assertThat(anyOfFile).fileContains( + "public some.pkg.A getsomepkgA() throws ClassCastException {", + "public some.pkg.B getsomepkgB() throws ClassCastException {" + ); + } + + /** + * This checks bug issue-20718 + * A situation when schemaMapping is used and oneOf also is used with one of the schema-mapped dataTypes and the dataType + * contains a package definition + * The dataType needs to be sanitized to generate compileable code + */ + @Test + public void testClassesAreValidJavaJersey3() { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JERSEY3) + .setSchemaMappings(Map.of( + "A", "some.pkg.A", + "B", "some.pkg.B")) + .setInputSpec("src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml") + .setOutputDir(output.toString().replace("\\", "/")); + + Map files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + File oneOfFile = files.get("ResultObjectOneOf.java"); + assertNotNull(oneOfFile); + + JavaFileAssert.assertThat(oneOfFile).fileContains( + "public some.pkg.A getsomepkgA() throws ClassCastException {", + "public some.pkg.B getsomepkgB() throws ClassCastException {" + ); + File anyOfFile = files.get("ResultObjectAnyOf.java"); + assertNotNull(anyOfFile); + + JavaFileAssert.assertThat(anyOfFile).fileContains( + "public some.pkg.A getsomepkgA() throws ClassCastException {", + "public some.pkg.B getsomepkgB() throws ClassCastException {" + ); + } + + /** + * This checks bug issue-20718 + * A situation when schemaMapping is used and oneOf also is used with one of the schema-mapped dataTypes and the dataType + * contains a package definition + * The dataType needs to be sanitized to generate compileable code + */ + @Test + public void testClassesAreValidJavaOkHttpGson() { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(OKHTTP_GSON) + .setSchemaMappings(Map.of( + "A", "some.pkg.A", + "B", "some.pkg.B")) + .setInputSpec("src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml") + .setOutputDir(output.toString().replace("\\", "/")); + + Map files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + File oneOfFile = files.get("ResultObjectOneOf.java"); + assertNotNull(oneOfFile); + + JavaFileAssert.assertThat(oneOfFile).fileContains( + "final TypeAdapter adaptersomepkgA = gson.getDelegateAdapter(this, TypeToken.get(some.pkg.A.class));", + "final TypeAdapter adaptersomepkgB = gson.getDelegateAdapter(this, TypeToken.get(some.pkg.B.class));", + "public some.pkg.A getsomepkgA() throws ClassCastException {", + "public some.pkg.B getsomepkgB() throws ClassCastException {" + ); + + File anyOfFile = files.get("ResultObjectAnyOf.java"); + assertNotNull(anyOfFile); + + JavaFileAssert.assertThat(anyOfFile).fileContains( + "final TypeAdapter adaptersomepkgA = gson.getDelegateAdapter(this, TypeToken.get(some.pkg.A.class));", + "final TypeAdapter adaptersomepkgB = gson.getDelegateAdapter(this, TypeToken.get(some.pkg.B.class));", + "public some.pkg.A getsomepkgA() throws ClassCastException {", + "public some.pkg.B getsomepkgB() throws ClassCastException {" + ); + } } \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml b/modules/openapi-generator/src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml new file mode 100644 index 00000000000..17f5c010cd1 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_20718-dataType_with_schema_mapping.yml @@ -0,0 +1,43 @@ +openapi: 3.0.0 +info: + title: Test generate oneOf with SchemaMapping with package + description: this test shows that the generation has a bug. + version: 1.0.0 +paths: + /testOneOf: + get: + responses: + '201': + $ref: '#/components/responses/ResponseObjectOneOf' + /testAnyOf: + get: + responses: + '201': + $ref: '#/components/responses/ResponseObjectAnyOf' +components: + responses: + ResponseObjectOneOf: + description: 'The validation of the data provided by the client failed.' + content: + 'application/json': + schema: + $ref: '#/components/schemas/ResultObjectOneOf' + ResponseObjectAnyOf: + description: 'The validation of the data provided by the client failed.' + content: + 'application/json': + schema: + $ref: '#/components/schemas/ResultObjectAnyOf' + schemas: + ResultObjectOneOf: + oneOf: + - $ref: '#/components/schemas/A' + - $ref: '#/components/schemas/B' + ResultObjectAnyOf: + oneOf: + - $ref: '#/components/schemas/A' + - $ref: '#/components/schemas/B' + A: + type: object + B: + type: object \ No newline at end of file