From f3f89159f800d0598641db4ea1e3056852029df2 Mon Sep 17 00:00:00 2001 From: Kanna Kim Date: Sun, 20 Sep 2026 20:27:50 -0500 Subject: [PATCH 1/2] Fix UUID serialization in okhttp-gson inner enums --- .../Java/libraries/okhttp-gson/modelInnerEnum.mustache | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/modelInnerEnum.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/modelInnerEnum.mustache index 6d1035aae704..d0113c4978a1 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/modelInnerEnum.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/modelInnerEnum.mustache @@ -49,18 +49,18 @@ public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}> { @Override public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} enumeration) throws IOException { - jsonWriter.value(enumeration.getValue(){{#isUri}}.toASCIIString(){{/isUri}}); + jsonWriter.value(enumeration.getValue(){{#isUri}}.toASCIIString(){{/isUri}}{{#isUuid}}.toString(){{/isUuid}}); } @Override public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { - {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}} {{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}} {{#isUuid}}UUID.fromString({{/isUuid}}{{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{#isUuid}}nextString()){{/isUuid}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{^isUuid}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUuid}}{{/isUri}}{{/isInteger}}{{/isNumber}}; return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); } } public static void validateJsonElement(JsonElement jsonElement) throws IOException { - {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isUri}}URI.create({{/isUri}}jsonElement.{{#isNumber}}getAsString(){{/isNumber}}{{#isInteger}}getAsInt(){{/isInteger}}{{#isUri}}getAsString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}getAs{{{dataType}}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isUuid}}UUID.fromString({{/isUuid}}{{#isUri}}URI.create({{/isUri}}jsonElement.{{#isNumber}}getAsString(){{/isNumber}}{{#isInteger}}getAsInt(){{/isInteger}}{{#isUri}}getAsString()){{/isUri}}{{#isUuid}}getAsString()){{/isUuid}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{^isUuid}}getAs{{{dataType}}}(){{/isUuid}}{{/isUri}}{{/isInteger}}{{/isNumber}}; {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); } } From b351a969ae36497ed04c6c626981d821a7b48850 Mon Sep 17 00:00:00 2001 From: Kanna Kim Date: Sun, 20 Sep 2026 21:00:36 -0500 Subject: [PATCH 2/2] Add runtime regression coverage for okhttp-gson UUID inner enums --- modules/openapi-generator/pom.xml | 6 + .../java/JavaOkHttpGsonUuidEnumTest.java | 112 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaOkHttpGsonUuidEnumTest.java diff --git a/modules/openapi-generator/pom.xml b/modules/openapi-generator/pom.xml index 673188e0fe61..628cbd65703f 100644 --- a/modules/openapi-generator/pom.xml +++ b/modules/openapi-generator/pom.xml @@ -280,6 +280,12 @@ + + com.google.code.gson + gson + 2.10.1 + test + ${swagger-parser-groupid.version} swagger-parser diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaOkHttpGsonUuidEnumTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaOkHttpGsonUuidEnumTest.java new file mode 100644 index 000000000000..4ca42d4a4497 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaOkHttpGsonUuidEnumTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2026 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openapitools.codegen.java; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import javax.tools.ToolProvider; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.openapitools.codegen.TestUtils.newTempFolder; +import static org.testng.Assert.*; + +public class JavaOkHttpGsonUuidEnumTest { + + @DataProvider + public Object[][] caseInsensitiveOptions() { + return new Object[][]{{false}, {true}}; + } + + @Test(dataProvider = "caseInsensitiveOptions") + public void testUuidEnumGsonRoundTrip(boolean caseInsensitive) throws Exception { + Path output = newTempFolder(); + CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary("okhttp-gson") + .setInputSpec("src/test/resources/3_0/issue_20952_inner_enum_comparison.yaml") + .setOutputDir(output.toString()) + .addGlobalProperty("models", "") + .addGlobalProperty("modelDocs", "false") + .addGlobalProperty("modelTests", "false") + .addAdditionalProperty("useEnumCaseInsensitive", caseInsensitive); + File model = new DefaultGenerator().opts(configurator.toClientOptInput()).generate().stream() + .filter(file -> file.getName().equals("Example.java")) + .findFirst().orElseThrow(() -> new AssertionError("Example.java was not generated")); + EnumDeclaration uuidEnum = StaticJavaParser.parse(model).findAll(EnumDeclaration.class).stream() + .filter(declaration -> declaration.getNameAsString().equals("UuidEnum")) + .findFirst().orElseThrow(() -> new AssertionError("UuidEnum was not generated")); + + // Compile the complete generated enum, including its Gson adapter and validation method. + // Only the enclosing model and its unrelated client dependencies are omitted. + Path runtime = Files.createDirectory(output.resolve("runtime")); + Path fixture = runtime.resolve("EnumFixture.java"); + Files.writeString(fixture, "import com.google.gson.*;\n" + + "import com.google.gson.annotations.JsonAdapter;\n" + + "import com.google.gson.stream.*;\n" + + "import java.io.IOException;\n" + + "import java.util.UUID;\n" + + "public class EnumFixture {\n" + uuidEnum + "\n}\n"); + String gsonClasspath = Paths.get(Gson.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toString(); + ByteArrayOutputStream diagnostics = new ByteArrayOutputStream(); + assertNotNull(ToolProvider.getSystemJavaCompiler(), "Runtime regression tests require a JDK"); + int result = ToolProvider.getSystemJavaCompiler().run(null, diagnostics, diagnostics, + "-classpath", gsonClasspath, "-d", runtime.toString(), fixture.toString()); + assertEquals(result, 0, diagnostics.toString(StandardCharsets.UTF_8)); + + try (URLClassLoader loader = new URLClassLoader(new URL[]{runtime.toUri().toURL()}, getClass().getClassLoader())) { + Class enumClass = loader.loadClass("EnumFixture$UuidEnum"); + Method validate = enumClass.getMethod("validateJsonElement", JsonElement.class); + Gson gson = new Gson(); + String[] values = { + "123e4567-e89b-12d3-a456-426614174000", + "123e4567-e89b-12d3-a456-426614174001" + }; + Object[] constants = enumClass.getEnumConstants(); + assertEquals(constants.length, values.length); + for (int i = 0; i < values.length; i++) { + String json = "\"" + values[i] + "\""; + assertEquals(gson.toJson(constants[i]), json); + assertSame(gson.fromJson(json, enumClass), constants[i]); + validate.invoke(null, new JsonPrimitive(values[i])); + } + for (String invalid : new String[]{"not-a-uuid", "123e4567-e89b-12d3-a456-426614174002"}) { + expectThrows(IllegalArgumentException.class, + () -> gson.fromJson(new JsonPrimitive(invalid), enumClass)); + InvocationTargetException exception = expectThrows(InvocationTargetException.class, + () -> validate.invoke(null, new JsonPrimitive(invalid))); + assertTrue(exception.getCause() instanceof IllegalArgumentException, + "Validation should reject malformed or unknown UUIDs: " + exception.getCause()); + } + } + } +}