From bac76e76d2a0539016ef75dcb6d9e9a8ca7bc590 Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:20:50 +0900 Subject: [PATCH] [core] Allow oneOf members that declare x-implements (#23577) OneOfImplementorAdditionalData.addToImplementor used putIfAbsent + List.add on the model's x-implements vendor extension. When a oneOf member schema already declares x-implements in the spec, the parsed value is a scalar string or an immutable list, so appending the oneOf interface threw java.lang.UnsupportedOperationException during model post-processing. Normalize the existing value into a fresh mutable list (preserving any user-supplied interfaces) before appending. No behavior change for models without a pre-existing x-implements. --- .../utils/OneOfImplementorAdditionalData.java | 13 +++++++++++-- .../codegen/java/spring/SpringCodegenTest.java | 13 +++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OneOfImplementorAdditionalData.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OneOfImplementorAdditionalData.java index 335546cfea81..395c7229bb99 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OneOfImplementorAdditionalData.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OneOfImplementorAdditionalData.java @@ -103,11 +103,20 @@ public void addFromInterfaceModel(CodegenModel cm, List> mod */ @SuppressWarnings("unchecked") public void addToImplementor(CodegenConfig cc, CodegenModel implcm, List> implImports, boolean addInterfaceImports) { - implcm.getVendorExtensions().putIfAbsent(X_IMPLEMENTS, new ArrayList()); + // The model may already declare x-implements in the spec, in which case the parsed value can be + // a scalar string or an immutable list. Normalize it to a fresh mutable list (preserving any + // existing entries) so the oneOf interfaces below can be appended without failing. + Object existing = implcm.getVendorExtensions().get(X_IMPLEMENTS); + List impl = new ArrayList<>(); + if (existing instanceof Collection) { + impl.addAll((Collection) existing); + } else if (existing instanceof String && !((String) existing).isEmpty()) { + impl.add((String) existing); + } + implcm.getVendorExtensions().put(X_IMPLEMENTS, impl); // Add implemented interfaces for (String intf : additionalInterfaces) { - List impl = (List) implcm.getVendorExtensions().get(X_IMPLEMENTS); impl.add(intf); if (addInterfaceImports) { // Add imports for interfaces 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 14b541cf9f0c..99412ba18704 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 @@ -7995,6 +7995,19 @@ void oneOf_issue_23577() throws IOException { .fileContains("String type;"); } + @Test + void oneOf_issue_23577_userDefinedXImplements() throws IOException { + // Default oneOf-interface generation (without REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING): + // a member schema that already declares its own x-implements must still be able to + // receive the oneOf interface, i.e. the user-supplied x-implements value must remain mutable. + Map files = generateFromContract("src/test/resources/3_0/oneOf_issue_23577.yaml", SPRING_BOOT, + Map.of(GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true)); + JavaFileAssert.assertThat(files.get("CreatedEvent.java")) + .implementsInterfaces("com.example.Notification", "Event"); + JavaFileAssert.assertThat(files.get("UpdatedEvent.java")) + .implementsInterfaces("Event"); + } + @Test void oneof_polymorphism_and_inheritance() throws IOException { Map files = generateFromContract("src/test/resources/3_0/oneof_polymorphism_and_inheritance.yaml", SPRING_BOOT,