Conversation
…ion-level annotations into body parameters
…d parameter vendor extensions Injects vendor extensions onto operations and their parameters from the CLI or config without editing the spec, complementing --inject-model-vendor-extensions. Applied in DefaultCodegen.fromOperation so it works for all generators and flows through Spring's request-body annotation normalization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
x-field-extra-annotation parity for kotlin-spring params + new x-request-body-extra-annotation + --inject-operation-vendor-extensions
…ation placements Adds regression coverage proving x-field-extra-annotation declared on the inline requestBody object and on a reusable components.requestBodies object renders on the generated body parameter in java-spring and kotlin-spring. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 27 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- GeneratorSettings: include injectModelVendorExtensions and injectOperationVendorExtensions in equals() and hashCode() so configs differing only in these maps are no longer treated as equal. - JavaCamelServerCodegen: stop advertising x-request-body-extra-annotation, which its Camel REST DSL templates never render; regenerate java-camel docs. - DefaultCodegen: move the shared parameter vendor-extension normalization (normalizeOperationParameterVendorExtensions) up from AbstractJavaCodegen and reuse it from KotlinSpringServerCodegen, removing the duplicate helpers. - DefaultCodegen.injectOperationVendorExtensions: match the spec-authored operationId (operationIdOriginal) when present, falling back to the generated operationId only when the spec omits one. - Correct docs/help to describe the parameter key segment as the spec name (paramBaseName / baseName) to match the actual matching logic. - Tests: add snake_case operationId injection test and a JavaCamel supported vendor-extension test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 29 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- DefaultCodegen.injectOperationVendorExtensions: treat a blank operationIdOriginal like a missing one and fall back to the generated operationId, so injection is not silently skipped when the spec declares an empty operationId. - DefaultCodegenTest: use expected-first argument order for JUnit assertEquals and add a regression test covering the blank-operationId fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
op.operationId is always non-blank at injection time because getOrGenerateOperationId synthesizes one from the path and HTTP method when the spec omits or blanks it. Replace the unreachable isBlank(matchOperationId) early-return with an explicit Objects.requireNonNull on op.operationId so the invariant is documented and a future regression fails loudly instead of silently dropping injected extensions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mples Add compile coverage for the Spring extra-annotation features by copying the shared petstore specs, adding the extension annotations to the copies, and repointing four existing (already-compiled) samples to them. The originals are left untouched, so there is no ripple to the 140+ other configs and no new build target. Copied specs (originals unchanged): - 3_0/spring/petstore-with-fake-endpoints-models-for-testing-extra-annotation.yaml - 3_0/kotlin/petstore-with-extra-annotation.yaml Repointed samples (cover java/kotlin x reactive/non-reactive): - springboot-useoptional (java, useOptional body branch) - springboot-reactive (java, Mono/Flux body branch) - kotlin-springboot-delegate (kotlin, non-reactive) - kotlin-springboot-reactive (kotlin, Flow/suspend body branch) Exercised, all verified to compile locally (mvn + gradle): - operation-level x-request-body-extra-annotation on addPet (body is a $ref), with updatePet left un-annotated to prove per-operation selectivity - param x-field-extra-annotation on path (getPetById), list-valued query (findPetsByStatus, two annotations), and form (uploadFile) params - kotlin references short names imported via x-extra-imports; java uses fully-qualified Spring @nonnull (java-spring has no x-extra-imports support) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The x-field-extra-annotation section in JavaSpring/bodyParams.mustache emitted
the annotation with a trailing space and no leading space. Because it sits
directly after {{>paramDoc}} (which ends in ")" with no trailing space), the
result glued the annotation to the @parameter(...) close paren and produced a
double space before @Valid, e.g.
...required = true)@org.springframework.lang.NonNull @Valid @RequestBody
Switch to a leading-space style (matching the surrounding binding annotations)
so the output is now:
...required = true) @org.springframework.lang.NonNull @Valid @RequestBody
Only java-spring was affected; the kotlin-spring @parameter block already ends
with a trailing space, so its output was already correct. The section renders
nothing when the extension is absent, so no other samples change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mples Extend the four repointed samples so they also cover the side-loading path: an injectOperationVendorExtensions: block in each sample config (the config-file equivalent of the --inject-operation-vendor-extensions CLI flag) injects the extensions without editing the spec. Injected onto store operations (kept separate from the pet operations used for the spec-declared demo): - placeOrder: operation-level x-request-body-extra-annotation - getOrderById: parameter-level x-field-extra-annotation on the path param The java base spec names that path param order_id while the kotlin base spec names it orderId, so the two configs use different keys. This validates that the parameter segment is matched against the raw spec paramBaseName. Values use the fully-qualified @org.springframework.lang.NonNull, so the injected demo needs no imports and compiles on its own. Regenerated StoreApi for all four samples (java + kotlin, reactive + non-reactive); the injected annotations render before the placeOrder body binding (incl. Mono<Order> in the java reactive sample) and before the getOrderById path param. All four samples compile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…otations The kotlin generator collects x-extra-imports from operation and parameter vendor extensions, and those extensions can themselves be side-loaded. Inject x-extra-imports next to the injected annotations on the two kotlin samples so the injected annotation can use the short name instead of a fully-qualified one: placeOrder.x-request-body-extra-annotation: "@nonnull" placeOrder.x-extra-imports: org.springframework.lang.NonNull getOrderById.orderId.x-field-extra-annotation: "@nonnull" getOrderById.orderId.x-extra-imports: org.springframework.lang.NonNull Regenerated StoreApi for both kotlin samples: the injected import is added to the file and the short @nonnull renders on both the placeOrder body and the getOrderById path param. Both samples compile. The java samples keep the fully-qualified form, since java-spring has no x-extra-imports support. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring CLI/plugin parity for the vendor-extension side-loading feature by exposing injectModelVendorExtensions and injectOperationVendorExtensions on both the Gradle and Maven plugins (previously only reachable via a configFile). - Gradle plugin: new mapProperty extension fields, plugin wiring, and the four GenerateTask mirror points (WorkParameters, execute, task inputs, parameters). - Maven plugin: two List<String> KVP @parameter fields with guarded applyInject*KvpList calls. - Docs: Gradle README.adoc and Maven README.md config tables; CLI help now clarifies that multiple annotations in a single value are space-separated, since an unquoted comma separates different injection targets. - Tests: Gradle ParameterWiringRegressionTest wiring test and Maven CodeGenMojoTest inject-vendor-extensions resource project asserting the injected request-body annotation renders on the generated Spring API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clarify in the Gradle plugin README that multiple annotations in a single injected value are space-separated (emitted verbatim, safe inside parentheses), with a groovy example. Note that commas inside a value need no escaping in the Gradle map form, unlike the comma-separated CLI/Maven KVP form. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…anism Reword the Gradle/Maven/CLI docs to describe injectModelVendorExtensions and injectOperationVendorExtensions as a generic vendor-extension mechanism: values are strings, applied at render time, and overwrite existing values; missing targets are a silent no-op. The space-vs-comma guidance is scoped to the extra-annotation extensions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@cubic-dev-ai, please re-review |
@Picazsoo I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 56 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Strengthen testInjectOperationVendorExtensions so it no longer passes on a mere substring match anywhere in the generated sources. It now locates PetApi.java, asserts the injected @com.example.MyValidation sits on addPet's @RequestBody body parameter, asserts a control operation (updatePet, which also has a body but no injection) does not receive it, and asserts the annotation appears exactly once. This guards the operation-scoped merge against non-selective or wrong-target regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rams The inject-model/operation-vendor-extensions settings are top-level configurator options, not per-generator CliOptions, so a key placed in <configOptions> is never forwarded (CodeGenMojo only forwards keys matching config.cliOptions(), plus SOURCE_FOLDER). The guard therefore protected against an unreachable double-application. Simplify to a plain null check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
please resolve the merge conflicts when you've time and i'll try to get it merged before the upcoming release. |
…dy-param # Conflicts: # modules/openapi-generator/src/main/resources/JavaSpring/bodyParams.mustache # samples/server/petstore/springboot-reactive/src/main/java/org/openapitools/api/PetApi.java # samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/PetApi.java
This reverts commit e3e2043.
There was a problem hiding this comment.
All reported issues were addressed across 56 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- Fix stale copyright year in inject-vendor-extensions/pom.xml test resource - Fix injectOperationVendorExtensions timing: apply operation-level injections right after operationId is resolved, and parameter-level injections before each postProcessParameter call, so consumers see injected values in time - Fix dotted-operationId matching by prefix-matching the operationId instead of blindly splitting the key on '.' - Add ordering assertion in CodeGenMojoTest to verify injected annotation renders before @RequestBody - Replace contradictory @NonNull/@nullable annotation pair with @nonnull + @SiZe(min = 1) in kotlin-springboot-reactive sample spec and regenerate the sample - Clarify --inject-model-vendor-extensions/--inject-operation-vendor-extensions CLI help text to show quoted examples for multi-word annotation values Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ons CLI help Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…I help Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…vendor-extensions help Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Parse each --inject-model-vendor-extensions / --inject-operation-vendor-extensions occurrence as a single key=value pair (split only on the first '='), instead of running it through comma-based multi-tuple splitting. Annotation values containing commas (e.g. @SiZe(min = 1, max = 100)) are now taken literally and no longer get mis-parsed into bogus injection targets or require lossy quoting workarounds. - Repeating the same key across multiple occurrences of either option now appends the values together (space-separated, in call order) instead of the later occurrence silently overwriting the earlier one, so multiple annotations can be layered onto the same target one occurrence at a time. - Update CLI help text and Maven/Gradle plugin READMEs to document both behaviors. - Add CodegenConfiguratorUtilsTest and GeneratorSettingsTest covering literal commas, multiple distinct targets, and same-key append semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…String Convert injectModelVendorExtensions/injectOperationVendorExtensions from Map<String, String> to Map<String, List<String>> across CodegenConfig, DefaultCodegen, GeneratorSettings, CodegenConfigurator, the CLI, and the Gradle plugin DSL. Repeated CLI/Maven occurrences of the same key now append as a new list element (instead of silently overwriting or being space-joined), and the Gradle DSL properties become native MapProperty<String, List<String>>. Mustache templates and DefaultCodegen's normalization helpers already iterate vendor extensions as lists, so no template changes are required. Fix the two Java-side consumers that read an injected vendor extension value as a scalar String and would otherwise break under the new list type: - AbstractCSharpCodegen's x-setter-visibility handling - DefaultCodegen#fromOperation's x-codegen-request-body-name handling Both now unwrap via DefaultCodegen.getObjectAsStringList(...) and use the first element, since these extensions are scalar by nature. Update CLI help text, Maven/Gradle plugin READMEs, and tests (DefaultCodegenTest, GeneratorSettingsTest, SpringCodegenTest, ParameterWiringRegressionTest) accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
2 issues found across 21 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java">
<violation number="1" location="modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java:61">
P1: When a YAML or JSON config uses the existing scalar form for an injected extension, `GeneratorSettings` now fails deserialization because it requires `List<String>` values. Preserve scalar compatibility by accepting a string-or-list value during config binding, or update the binding with single-value-to-array normalization.</violation>
</file>
<file name="modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-with-extra-annotation.yaml">
<violation number="1" location="modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-with-extra-annotation.yaml:99">
P2: The spec swap of @Nullable for @Size(min = 1) was applied, but the non-reactive sample was not regenerated: samples/server/petstore/kotlin-springboot-delegate/src/main/kotlin/org/openapitools/api/PetApi.kt:109 still renders `@NonNull @Nullable @NotNull` and keeps `import org.springframework.lang.Nullable`, while the reactive sample already emits `@Size(min = 1)`. This leaves the delegate sample stale and out of sync with the spec (and would fail the samples-up-to-date check). Regenerate samples/server/petstore/kotlin-springboot-delegate after changing the spec.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| private final Map<String, String> enumNameMappings; | ||
| private final Map<String, String> operationIdNameMappings; | ||
| private final Map<String, String> injectModelVendorExtensions; | ||
| private final Map<String, List<String>> injectModelVendorExtensions; |
There was a problem hiding this comment.
P1: When a YAML or JSON config uses the existing scalar form for an injected extension, GeneratorSettings now fails deserialization because it requires List<String> values. Preserve scalar compatibility by accepting a string-or-list value during config binding, or update the binding with single-value-to-array normalization.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java, line 61:
<comment>When a YAML or JSON config uses the existing scalar form for an injected extension, `GeneratorSettings` now fails deserialization because it requires `List<String>` values. Preserve scalar compatibility by accepting a string-or-list value during config binding, or update the binding with single-value-to-array normalization.</comment>
<file context>
@@ -58,8 +58,8 @@ public final class GeneratorSettings implements Serializable {
private final Map<String, String> operationIdNameMappings;
- private final Map<String, String> injectModelVendorExtensions;
- private final Map<String, String> injectOperationVendorExtensions;
+ private final Map<String, List<String>> injectModelVendorExtensions;
+ private final Map<String, List<String>> injectOperationVendorExtensions;
private final Map<String, String> openapiNormalizer;
</file context>
| - jakarta.validation.constraints.Size | ||
| x-field-extra-annotation: | ||
| - "@NonNull" | ||
| - "@Size(min = 1)" |
There was a problem hiding this comment.
P2: The spec swap of @nullable for @SiZe(min = 1) was applied, but the non-reactive sample was not regenerated: samples/server/petstore/kotlin-springboot-delegate/src/main/kotlin/org/openapitools/api/PetApi.kt:109 still renders @NonNull @Nullable @NotNull and keeps import org.springframework.lang.Nullable, while the reactive sample already emits @Size(min = 1). This leaves the delegate sample stale and out of sync with the spec (and would fail the samples-up-to-date check). Regenerate samples/server/petstore/kotlin-springboot-delegate after changing the spec.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-with-extra-annotation.yaml, line 99:
<comment>The spec swap of @Nullable for @Size(min = 1) was applied, but the non-reactive sample was not regenerated: samples/server/petstore/kotlin-springboot-delegate/src/main/kotlin/org/openapitools/api/PetApi.kt:109 still renders `@NonNull @Nullable @NotNull` and keeps `import org.springframework.lang.Nullable`, while the reactive sample already emits `@Size(min = 1)`. This leaves the delegate sample stale and out of sync with the spec (and would fail the samples-up-to-date check). Regenerate samples/server/petstore/kotlin-springboot-delegate after changing the spec.</comment>
<file context>
@@ -93,10 +93,10 @@ paths:
x-field-extra-annotation:
- "@NonNull"
- - "@Nullable"
+ - "@Size(min = 1)"
schema:
type: array
</file context>
…or injected vendor extensions CLI help text: replace the --inject-model-vendor-extensions/ --inject-operation-vendor-extensions quoting example with @foo(\"some string with spaces\") so it demonstrates both why the whole key=value pair must be quoted (embedded space) and how to escape an embedded double quote, in a single realistic example. Config-file deserialization: injectModelVendorExtensions/ injectOperationVendorExtensions moved from Map<String, String> to Map<String, List<String>>, but existing config files (e.g. bin/configs/csharp-generichost-net10.yaml, already present on master) author each value as a plain scalar string (x-setter-visibility: private). Since GeneratorSettings lives in the openapi-generator-core module (no Jackson dependency), the config-file mapper binds these properties directly to GeneratorSettings.Builder's private fields rather than through their withXxx() setters, which made mixin-based fixes ineffective. Add a Jackson BeanDeserializerModifier (VendorExtensionsMapDeserializerModifier) that replaces the deserializer for any Map<String, List<String>> type with VendorExtensionsMapDeserializer, which accepts either a scalar string or a list of strings per key and always produces a List<String>. Registered via VendorExtensionsCompatModule on the config-file ObjectMapper in CodegenConfigurator, independent of whichever binding path (field vs. builder method) Jackson chooses. Verified end-to-end: batch-generating bin/configs/csharp-generichost-net10.yaml now succeeds again and produces identical C# output (private/internal/public setter visibility and accessor overrides) as before this change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
- Fix jakarta/javax.validation.constraints.Size import conflict in the Kotlin petstore-with-extra-annotation.yaml fixture (samples default to Boot 2.7/javax); regenerate kotlin-springboot-reactive (removes the duplicate conflicting import) and kotlin-springboot-delegate (was never regenerated after the spec change). - GeneratorSettings: deep-copy List<String> values in the Builder's bulk setters, the copy-constructor (newBuilder(copy)) path, and final construction, so injectModelVendorExtensions/injectOperationVendorExtensions lists are never shared/mutated across GeneratorSettings instances or builder copies. - DefaultCodegen.fromOperation: thread the parameter-level injection match id across the fromParameter virtual-dispatch boundary via a short-lived instance field instead of calling the private 3-arg overload directly, so generators overriding the public fromParameter(Parameter, Set<String>) (Dart, TypeScript Fetch, Scala Akka HTTP, Java Helidon, C++ Boost Beast) run correctly again. - CodegenConfigurator: stop setInjectModelVendorExtensions/ setInjectOperationVendorExtensions from sharing the same map reference with generatorSettingsBuilder, which caused a subsequent addInject*VendorExtension call to append its value twice. - Replace the custom VendorExtensionsCompatModule/ VendorExtensionsMapDeserializer/VendorExtensionsMapDeserializerModifier with Jackson's built-in DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, which fixes the same scalar-config-value compatibility case without matching every Map<String, List<String>> config property by shape or silently producing an empty map for non-object nodes. Adds regression tests for the mutability/aliasing, double-append, and virtual-dispatch fixes; re-verified the csharp-generichost-net10.yaml config-file scalar-value repro end-to-end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 14 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java">
<violation number="1" location="modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java:92">
P1: When a config file contains an injected key and the CLI adds another value for that key, generation throws `UnsupportedOperationException`. Deep-copy the lists when `CodegenConfigurator.fromFile` imports both extension maps, or copy an existing list before `addInject*VendorExtension` appends to it.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } | ||
| Map<String, List<String>> copy = new HashMap<>(); | ||
| source.forEach((key, value) -> | ||
| copy.put(key, Collections.unmodifiableList(new ArrayList<>(value)))); |
There was a problem hiding this comment.
P1: When a config file contains an injected key and the CLI adds another value for that key, generation throws UnsupportedOperationException. Deep-copy the lists when CodegenConfigurator.fromFile imports both extension maps, or copy an existing list before addInject*VendorExtension appends to it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java, line 92:
<comment>When a config file contains an injected key and the CLI adds another value for that key, generation throws `UnsupportedOperationException`. Deep-copy the lists when `CodegenConfigurator.fromFile` imports both extension maps, or copy an existing list before `addInject*VendorExtension` appends to it.</comment>
<file context>
@@ -72,6 +72,45 @@ public final class GeneratorSettings implements Serializable {
+ }
+ Map<String, List<String>> copy = new HashMap<>();
+ source.forEach((key, value) ->
+ copy.put(key, Collections.unmodifiableList(new ArrayList<>(value))));
+ return Collections.unmodifiableMap(copy);
+ }
</file context>
There was a problem hiding this comment.
@Picazsoo I have started the AI code review. It will take a few minutes to complete.
…ant copy, thread safety, test coverage - CodegenConfigurator.fromFile: deep-copy injectModelVendorExtensions/injectOperationVendorExtensions list values when importing from parsed GeneratorSettings, instead of sharing unmodifiable list references via putAll. Fixes UnsupportedOperationException when a config-file-defined key is later appended to via addInjectModelVendorExtension/addInjectOperationVendorExtension. - CodegenConfigurator: remove redundant deepCopyInjectVendorExtensions helper; setInjectModel/ OperationVendorExtensions now pass the map directly to the builder, which already deep-copies. - DefaultCodegen: convert currentOperationVendorExtensionMatchOperationId from a plain instance field to a ThreadLocal<String> for thread safety. - DefaultCodegenTest: extend testFromOperationPreservesFromParameterVirtualDispatch to also assert parameter-level vendor-extension injection works through an overriding fromParameter. - CodegenConfiguratorTest: add regression test for the fromFile deep-copy fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The kotlin-spring apiInterface.mustache template unconditionally imports javax.validation.constraints.Size as part of its standard bean-validation import block, so injecting the same import via x-extra-imports caused a 'Conflicting import, imported name Size is ambiguous' Kotlin compile error. Replace the redundant Size import with NotEmpty (not part of the template's unconditional import block) and add @notempty to the field-extra-annotation list, preserving the multi-value list demonstration. Regenerated the kotlin-springboot-delegate and kotlin-springboot-reactive samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
3 issues found across 60 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java">
<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java:4287">
P2: Both new specs tag every operation `employee`, yet the tests assert generated files named `OrgsApi.java` (inject spec, paths `/orgs/...`) and `EmployeesApi.java` (request-body spec, paths `/employees/...`). openapi-generator derives the API class name from the tag via `toApiName(tag)`, so a tag of `employee` yields `EmployeeApi.java` — see also `testHasOperationParameterExtraAnnotation_issue18224` just above, which asserts the tag-derived `TestApi.java`. If the generated file is in fact `EmployeeApi.java`, `files.get("OrgsApi.java")` / `files.get("EmployeesApi.java")` return null and `JavaFileAssert.assertThat(null)` throws, failing both new tests. Please run these two tests and correct the asserted filenames (or the spec tags) to match the actual output.</violation>
</file>
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java:81">
P1: A single injected value is now stored as a list for every vendor extension, so generic scalar extensions such as `x-cpp-type` can fail with `ClassCastException`. Preserve scalar values for scalar extensions and use lists only where the consumer supports them.</violation>
</file>
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java:208">
P2: When existing CLI or Maven configuration supplies multiple model injection targets in one option occurrence, this change silently stops injecting every target after the first. Preserve the established comma-separated model-pair behavior, or introduce an unambiguous compatibility parser while keeping single-pair parsing for operation values that contain commas.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| private Map<String, String> enumNameMappings = new HashMap<>(); | ||
| private Map<String, String> operationIdNameMappings = new HashMap<>(); | ||
| private Map<String, String> injectModelVendorExtensions = new HashMap<>(); | ||
| private Map<String, List<String>> injectModelVendorExtensions = new HashMap<>(); |
There was a problem hiding this comment.
P1: A single injected value is now stored as a list for every vendor extension, so generic scalar extensions such as x-cpp-type can fail with ClassCastException. Preserve scalar values for scalar extensions and use lists only where the consumer supports them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java, line 81:
<comment>A single injected value is now stored as a list for every vendor extension, so generic scalar extensions such as `x-cpp-type` can fail with `ClassCastException`. Preserve scalar values for scalar extensions and use lists only where the consumer supports them.</comment>
<file context>
@@ -74,9 +75,10 @@ public class CodegenConfigurator {
private Map<String, String> enumNameMappings = new HashMap<>();
private Map<String, String> operationIdNameMappings = new HashMap<>();
- private Map<String, String> injectModelVendorExtensions = new HashMap<>();
+ private Map<String, List<String>> injectModelVendorExtensions = new HashMap<>();
private Map<String, String> openapiNormalizer = new HashMap<>();
private Set<String> languageSpecificPrimitives = new HashSet<>();
</file context>
| Map<String, File> files = generator.opts(input).generate().stream() | ||
| .collect(Collectors.toMap(File::getName, Function.identity())); | ||
|
|
||
| JavaFileAssert.assertThat(files.get("OrgsApi.java")) |
There was a problem hiding this comment.
P2: Both new specs tag every operation employee, yet the tests assert generated files named OrgsApi.java (inject spec, paths /orgs/...) and EmployeesApi.java (request-body spec, paths /employees/...). openapi-generator derives the API class name from the tag via toApiName(tag), so a tag of employee yields EmployeeApi.java — see also testHasOperationParameterExtraAnnotation_issue18224 just above, which asserts the tag-derived TestApi.java. If the generated file is in fact EmployeeApi.java, files.get("OrgsApi.java") / files.get("EmployeesApi.java") return null and JavaFileAssert.assertThat(null) throws, failing both new tests. Please run these two tests and correct the asserted filenames (or the spec tags) to match the actual output.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java, line 4287:
<comment>Both new specs tag every operation `employee`, yet the tests assert generated files named `OrgsApi.java` (inject spec, paths `/orgs/...`) and `EmployeesApi.java` (request-body spec, paths `/employees/...`). openapi-generator derives the API class name from the tag via `toApiName(tag)`, so a tag of `employee` yields `EmployeeApi.java` — see also `testHasOperationParameterExtraAnnotation_issue18224` just above, which asserts the tag-derived `TestApi.java`. If the generated file is in fact `EmployeeApi.java`, `files.get("OrgsApi.java")` / `files.get("EmployeesApi.java")` return null and `JavaFileAssert.assertThat(null)` throws, failing both new tests. Please run these two tests and correct the asserted filenames (or the spec tags) to match the actual output.</comment>
<file context>
@@ -4184,7 +4184,126 @@ public void testHasOperationParameterExtraAnnotation_issue18224() throws IOExcep
+ Map<String, File> files = generator.opts(input).generate().stream()
+ .collect(Collectors.toMap(File::getName, Function.identity()));
+
+ JavaFileAssert.assertThat(files.get("OrgsApi.java"))
+ .assertMethod("createEmployee")
+ .assertParameter("employee")
</file context>
| final Map<String, String> map = createMapFromKeyValuePairs(injectModelVendorExtensions); | ||
| for (Map.Entry<String, String> entry : map.entrySet()) { | ||
| configurator.addInjectModelVendorExtension(entry.getKey().trim(), entry.getValue().trim()); | ||
| final Pair<String, String> pair = parseSingleInjectVendorExtensionKvp(injectModelVendorExtensions); |
There was a problem hiding this comment.
P2: When existing CLI or Maven configuration supplies multiple model injection targets in one option occurrence, this change silently stops injecting every target after the first. Preserve the established comma-separated model-pair behavior, or introduce an unambiguous compatibility parser while keeping single-pair parsing for operation values that contain commas.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java, line 208:
<comment>When existing CLI or Maven configuration supplies multiple model injection targets in one option occurrence, this change silently stops injecting every target after the first. Preserve the established comma-separated model-pair behavior, or introduce an unambiguous compatibility parser while keeping single-pair parsing for operation values that contain commas.</comment>
<file context>
@@ -205,10 +205,48 @@ public static void applyInjectModelVendorExtensionsKvpList(List<String> injectMo
- final Map<String, String> map = createMapFromKeyValuePairs(injectModelVendorExtensions);
- for (Map.Entry<String, String> entry : map.entrySet()) {
- configurator.addInjectModelVendorExtension(entry.getKey().trim(), entry.getValue().trim());
+ final Pair<String, String> pair = parseSingleInjectVendorExtensionKvp(injectModelVendorExtensions);
+ if (pair != null) {
+ configurator.addInjectModelVendorExtension(pair.getLeft().trim(), pair.getRight().trim());
</file context>
…n and tighten test assertions
- DefaultCodegen: getInjectedVendorExtensionParts now splits the remainder at the last '.x-'
boundary instead of the first dot, so a parameter or property base name containing a dot
(e.g. 'org.id') is no longer mis-split. The model-level injection loop in
postProcessAllModels now reuses this same helper instead of a naive split(".", 3), fixing
the identical class of bug for dotted property base names.
- DefaultCodegenTest: added regression tests for both the operation/parameter-level and
model/property-level dotted-name fixes.
- Removed the contradictory @nonnull annotation from the optional 'file' property in the
Kotlin extra-annotation fixture (uploadFile's file part is not required, so @nonnull
conflicted with its nullable Part?/MultipartFile type); regenerated kotlin-springboot-delegate
and kotlin-springboot-reactive samples.
- ParameterWiringRegressionTest (gradle plugin): tightened the injectOperationVendorExtensions
wiring test to assert the injected '@deprecated' annotation appears exactly once, so the test
can catch a regression where injection stops being operation-specific.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Hi @wing328 - I think this one should wait for 7.27.0. I will not have enough time to properly address all the issues. |
Summary
Adds custom-annotation support for previously uncovered spots in the Spring generators:
x-field-extra-annotationon kotlin-spring path/query/form/header params: brings kotlin-springto parity with java-spring, which already renders this extension on parameters.
x-request-body-extra-annotation(java-spring and kotlin-spring):lets you attach annotations to the generated request-body parameter, even when the body
$refs ashared model.
x-field-extra-annotationon cookie params (java-spring and kotlin-spring): closes thelast parameter kind that supported it in neither generator.
--inject-operation-vendor-extensionsCLI/config flag (all generators): injects vendorextensions onto operations and their parameters from the command line or config, without editing
the spec. This complements the existing
--inject-model-vendor-extensionsflag and is the naturalway to apply the two extensions above when you cannot or do not want to touch the source contract.
Both accept a string or a list of strings, are applied per operation (selective), and are a
no-op when absent.
Motivation
Users commonly reference reusable ID/model schemas from many operations:
To add a validation/framework annotation to a specific usage (e.g. Hibernate Validator / LSP rules
that require constraints to live on the generated interface, not the impl), you need a placement that
is both ref-safe and per-operation:
Path / query / form / header / cookie params support two placements, and you can pick based
on the scope you want:
OrgId): applies globally to every parameter that$refs that schema. Use this when the annotation should always accompany the type. The generatoralready merges a referenced parameter schema's extensions onto the parameter, so this works for
the simple alias schemas typically used by parameters.
name/in): applies selectively to that singleusage, and keeps the shared
schema: { $ref: ... }clean and reusable for other operations. Usethis when only some usages should carry the annotation.
while kotlin-spring rendered it on none of them. Parts 1 and 3 close those gaps so every parameter
kind behaves consistently in both generators.
Request bodies usually
$refa shared model, so there is no per-usage object to annotate:$refare ignored (OpenAPI 3.0/3.1),Part 2 solves this with an operation-level extension: operations are never
$reftargets, sothe annotation is inherently per-usage and ref-safe.
Example (Part 1: parameter object)
Example (Part 2: request body)
Generates (java-spring):
Request-body annotation placements (three working scopes)
Besides the new operation-level extension, the body param also honors annotations declared on the
RequestBody Object itself (these are merged onto the generated body parameter). This gives three
placements, chosen by the scope you want:
x-request-body-extra-annotation(the new extension): per operation. Workseven when the operation's
requestBodyis a bare$ref.requestBodyobjectx-field-extra-annotation: per operation.components.requestBodiesobjectx-field-extra-annotation: applies to everyoperation that
$refs that reusable request body (a shared subset).Caveat: a key placed as a sibling of
$refin the operation'srequestBodyis ignored (OpenAPI3.0/3.1), so to annotate a
$ref-ed reusable body the extension must live inside the reusablecomponents.requestBodiesobject (option 3), not next to the$ref.Example (injecting the extensions from the CLI, no spec edit)
When you cannot modify the source contract, the same result is achievable via
--inject-operation-vendor-extensions:Key formats:
operationId.x-extension-name=valuetargets the operation (for example the request-bodyextension above).
operationId.paramBaseName.x-extension-name=valuetargets a parameter, matched by its specname (
baseName). TheoperationIdsegment is matched against the spec-authored operationId whenpresent, falling back to the generated operationId only when the spec omits one.
Changes
New extension & wiring
VendorExtension: addedX_REQUEST_BODY_EXTRA_ANNOTATION(OPERATION level).getSupportedVendorExtensions()for bothspringandkotlin-spring.DefaultCodegen: new shared helpermergeOperationVendorExtensionIntoBodyParams(...)that copiesthe operation-level values onto the body parameter's
x-field-extra-annotationlist, so bothextensions render through the existing body-param template path.
List<String>) applied in bothSpringCodegenandKotlinSpringServerCodegen(the latter gains a small parameter-normalization helper, since it doesnot share the Java generator's base class).
Vendor-extension injection flag (all generators)
--inject-operation-vendor-extensionsCLI option (repeatable) plus the matchingCodegenConfigurator.addInjectOperationVendorExtension/GeneratorSettingswiring, mirroring theexisting
--inject-model-vendor-extensionsplumbing.DefaultCodegen.fromOperation, so it runs for every generator and beforegenerator-specific post-processing. This means an injected
x-request-body-extra-annotationflowsthrough the same normalize-and-merge path as one declared in the spec.
Templates
pathParams/queryParams/formParams/headerParams: renderx-field-extra-annotationbefore the parameter binding (java-spring parity).cookieParams: renderx-field-extra-annotationbefore@CookieValue/ the cookie binding (previously unsupported in both).
bodyParams: render the merged annotation before@RequestBody/the body binding. Covers
useOptionaland reactive (Mono/Flux,Flow/suspend) variants.parameter collections to
List<String>, so only the templates were missing.Samples (compile-verified)
shared petstore specs were copied and the copies annotated:
petstore-with-fake-endpoints-models-for-testing-extra-annotation.yaml(java) and3_0/kotlin/petstore-with-extra-annotation.yaml(kotlin). The originals are untouched, so there isno ripple to the 140+ other configs that consume them.
non-reactive:
springboot-useoptional(java,useOptional),springboot-reactive(java,Mono/Flux),kotlin-springboot-delegate(kotlin), andkotlin-springboot-reactive(kotlin,Flow/suspend). All four compile locally.x-request-body-extra-annotationonaddPet(a
$refbody) withupdatePetleft un-annotated (selectivity); paramx-field-extra-annotationon a path, a list-valued query, and a form param; and, on the kotlin copy,
x-extra-importsso theannotations are referenced by short name.
--inject-operation-vendor-extensionsside-loading path(expressed as an
injectOperationVendorExtensions:block in each sample config, the config-fileequivalent of the CLI flag). Without editing the spec, they inject an operation-level
x-request-body-extra-annotationontoplaceOrderand a parameter-levelx-field-extra-annotationonto the
getOrderByIdpath param. Because the java base spec names that paramorder_idwhile thekotlin base spec names it
orderId, the two configs use different keys, which validates that theparameter segment is matched against the raw spec
paramBaseName. On the kotlin samples the configsalso side-load
x-extra-importsnext to the injected annotations, so the injected@NonNullisimported and referenced by short name (the java samples keep the fully-qualified form, since
java-spring has no
x-extra-importssupport). The injected annotations render and compile alongsidethe spec-declared ones.
Docs
docs/generators/spring.mdanddocs/generators/kotlin-spring.md(vendor-extensiontables) via the docs task, not hand-edited. (No table change for the header/cookie additions, which
reuse the existing
x-field-extra-annotation.)Tests
$refbody + a second operation on the same modelwithout the extension (proves selectivity); covers a non-default body branch. Cookie-param
coverage added to the existing parameter-annotation test.
x-field-extra-annotationon path/query/form/header/cookie params (incl.list-valued cases), plus request-body annotation with selectivity.
DefaultCodegenTest(operation-level and parameter-level landing,non-matching operationId is a no-op) plus java-spring and kotlin-spring end-to-end tests that inject
both extensions and assert selective rendering.
x-field-extra-annotationdeclared on the inlinerequestBodyobject and on a reusablecomponents.requestBodiesobject (referenced by two operations) render on the body param.Design notes / trade-offs
x-field-extra-annotationinstead of a new template branch. The operation-levelrequest-body value is merged onto the body parameter's existing annotation list, so rendering
reuses one code path and multiple annotations compose naturally. Existing param-level annotations
are preserved and appear first.
List<String>. A single string and a list are handled uniformly, extravalues can be appended, and an absent extension yields an empty list → renders nothing (no change
when unused).
the shared schema applies it globally to every usage of that schema; placing it on the Parameter
Object applies it selectively to a single usage and keeps the shared
$refreusable. Both arevalid; choose based on whether you want global or per-usage scope.
Known limitation (out of scope)
If a
requestBodydeclares different schemas per content type, the generator still models it as asingle body parameter / single
@RequestBodyargument (Spring itself binds one body per method),so the annotation applies to that one binding, and it cannot be varied per content type. Generating one
method per content type (via
consumesdispatch) would be the fully spec-faithful approach but is abroader change (it breaks the 1:1 operation→method contract across all generators) and is not
addressed here. The operation-level design does not preclude adding a media-type-level extension
later if that path is ever taken.
Acceptance
x-field-extra-annotationon path/query/form/header params (java-springparity), including list values; model-field behavior unchanged.
x-field-extra-annotationrenders on cookie params in both java-spring and kotlin-spring.x-request-body-extra-annotationrenders before the body param in java-spring and kotlin-spring,works with
$refbodies, is selective per operation, and coversuseOptional/ reactive variants.--inject-operation-vendor-extensionsapplies operation- and parameter-level extensions from theCLI/config for any generator, selectively per operation, with an empty/absent map being a no-op.
of the shared petstore specs and compile with the emitted annotations, verifying the feature
end-to-end without adding new build targets. The same samples also inject both extensions via an
injectOperationVendorExtensions:config block (the side-loading path), confirming the CLI/configinjection mechanism renders and compiles too.
Plugin parity (Gradle + Maven)
The
injectModelVendorExtensions/injectOperationVendorExtensionsside-loading maps were onlyreachable from the plugins through a
configFile. They are now first-class properties on both theGradle and Maven plugins, matching the CLI and the existing parity of the other mapping properties
(
nameMappings,globalProperties, etc.):openApiGenerate { ... }): twoMapProperty<String, String>fields wired through theextension, plugin, and
GenerateTaskto the configurator.<configuration>): twoList<String>KVP parameters(
openapi.generator.maven.plugin.inject{Model,Operation}VendorExtensions) applied via the sharedapplyInject*KvpListhelpers. These settings are configurator-level (not generatorCliOptions)and have no
configOptionsbackwards-compat reader, so unlike the legacy mapping options they needno
configOptionsguard; the same review also removed the equally deadconfigOptionsguards fromthe five
*-name-mappingsparameters (theinline-schema-optionsguard is kept, since it does havea compat reader).
README.adocand MavenREADME.mdconfig tables. The CLI help for--inject-*-vendor-extensionsnow clarifies that multiple annotations in a single value arespace-separated (emitted verbatim into source); an unquoted comma is the separator between
different injection targets, so comma-bearing annotation attributes should use the spec-level
extension (string or YAML list) instead.
ParameterWiringRegressionTestcase asserts the injected annotation reachesgenerated sources, and a Maven
CodeGenMojoTestcase (newinject-vendor-extensionsresourceproject) asserts the injected
x-request-body-extra-annotationis merged onto the body parameterand rendered on the generated Spring API.
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.