Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2820b40
feat: add support for x-request-body-extra-annotation to merge operat…
Picazsoo Aug 26, 2026
813b7a4
update comments and docs
Picazsoo Aug 26, 2026
55e317c
support also header params and cookie params
Picazsoo Aug 26, 2026
c977ee8
update docs
Picazsoo Aug 26, 2026
eda2dd1
feat: add --inject-operation-vendor-extensions to inject operation an…
Picazsoo Aug 26, 2026
f8cc18c
test: cover requestBody-object and reusable requestBodies extra-annot…
Picazsoo Aug 26, 2026
22a9d98
Address review findings on vendor-extension work
Picazsoo Aug 26, 2026
9f45ec5
Handle blank operationId and fix test assertion order
Picazsoo Aug 26, 2026
d0aafd2
Assert operationId invariant instead of silent blank guard
Picazsoo Aug 26, 2026
57e96c9
Document getOrGenerateOperationId never returns null or blank
Picazsoo Aug 26, 2026
87fec9e
test(spring): compile-verify extra-annotation features in existing sa…
Picazsoo Aug 26, 2026
704d4eb
fix(java-spring): correct spacing around request-body extra annotation
Picazsoo Aug 26, 2026
5f9764e
test(spring): exercise --inject-operation-vendor-extensions in the sa…
Picazsoo Aug 26, 2026
78406bc
test(kotlin-spring): side-load x-extra-imports alongside injected ann…
Picazsoo Aug 26, 2026
7953207
Add inject-vendor-extensions properties to Gradle and Maven plugins
Picazsoo Aug 26, 2026
8902338
docs(gradle): expand inject-vendor-extensions note with space example
Picazsoo Aug 26, 2026
8b93f41
docs: clarify inject-vendor-extensions as a generic string-typed mech…
Picazsoo Aug 26, 2026
e730b88
test(maven): assert injected annotation is scoped to addPet body param
Picazsoo Aug 26, 2026
8e9585d
Remove dead configOptions guards on maven inject-vendor-extensions pa…
Picazsoo Aug 26, 2026
578b006
Remove dead configOptions guards on maven *-name-mappings params
Picazsoo Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion bin/configs/kotlin-spring-boot-delegate.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-springboot-delegate
library: spring-boot
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-with-extra-annotation.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
injectOperationVendorExtensions:
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
additionalProperties:
generateJsonIncludeAnnotations: "true"
generateJsonSetterNullsAnnotations: "true"
Expand Down
7 changes: 6 additions & 1 deletion bin/configs/kotlin-spring-boot-reactive.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-springboot-reactive
library: spring-boot
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-with-extra-annotation.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
injectOperationVendorExtensions:
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
additionalProperties:
generateJsonIncludeAnnotations: "true"
generateJsonSetterNullsAnnotations: "true"
Expand Down
5 changes: 4 additions & 1 deletion bin/configs/spring-boot-reactive.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
generatorName: spring
outputDir: samples/server/petstore/springboot-reactive
inputSpec: modules/openapi-generator/src/test/resources/3_0/spring/petstore-with-fake-endpoints-models-for-testing.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/spring/petstore-with-fake-endpoints-models-for-testing-extra-annotation.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
injectOperationVendorExtensions:
placeOrder.x-request-body-extra-annotation: "@org.springframework.lang.NonNull"
getOrderById.order_id.x-field-extra-annotation: "@org.springframework.lang.NonNull"
additionalProperties:
generateJsonIncludeAnnotations: "true"
generateJsonSetterNullsAnnotations: "true"
Expand Down
5 changes: 4 additions & 1 deletion bin/configs/spring-boot-useoptional.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
generatorName: spring
outputDir: samples/server/petstore/springboot-useoptional
inputSpec: modules/openapi-generator/src/test/resources/3_0/spring/petstore-with-fake-endpoints-models-for-testing.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/spring/petstore-with-fake-endpoints-models-for-testing-extra-annotation.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
injectOperationVendorExtensions:
placeOrder.x-request-body-extra-annotation: "@org.springframework.lang.NonNull"
getOrderById.order_id.x-field-extra-annotation: "@org.springframework.lang.NonNull"
additionalProperties:
generateJsonIncludeAnnotations: "true"
generateJsonSetterNullsAnnotations: "true"
Expand Down
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|x-discriminator-value|Used with model inheritance to specify value for discriminator that identifies current model|MODEL|
|x-field-extra-annotation|Custom annotation(s) to be added to property; accepts a string or list of strings|FIELD, OPERATION_PARAMETER|null
|x-operation-extra-annotation|Custom annotation(s) to be added to operation; accepts a string or list of strings|OPERATION|null
|x-request-body-extra-annotation|Custom annotation(s) to be added to the request body parameter; accepts a string or list of strings. Declared on the operation because the request body typically `$ref`s a shared model (so the annotation cannot be placed next to the `$ref`); the value is rendered by being merged into the body parameter's `x-field-extra-annotation`|OPERATION|null
|x-extra-imports|Custom import(s) to add to the generated file that declares the annotated model, property, operation, or parameter (e.g. so custom annotations can be referenced by their short name); accepts a string or list of strings. Values are emitted verbatim (Kotlin alias imports supported) and only exact duplicates are removed|MODEL, FIELD, OPERATION, OPERATION_PARAMETER|null
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
|x-size-message|Add this property whenever you need to customize the invalidation error message for the size or length of a variable|FIELD, OPERATION_PARAMETER|null
Expand Down
1 change: 1 addition & 0 deletions docs/generators/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|x-class-extra-annotation|Custom annotation(s) to be added to model; accepts a string or list of strings|MODEL|null
|x-field-extra-annotation|Custom annotation(s) to be added to property; accepts a string or list of strings|FIELD, OPERATION_PARAMETER|null
|x-operation-extra-annotation|Custom annotation(s) to be added to operation; accepts a string or list of strings|OPERATION|null
|x-request-body-extra-annotation|Custom annotation(s) to be added to the request body parameter; accepts a string or list of strings. Declared on the operation because the request body typically `$ref`s a shared model (so the annotation cannot be placed next to the `$ref`); the value is rendered by being merged into the body parameter's `x-field-extra-annotation`|OPERATION|null
|x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object. Applies when `library=spring-boot` or `library=spring-cloud`; ignored for other (client) libraries.|OPERATION|false
|x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,26 @@ public class Generate extends OpenApiGeneratorCommand {
description = "injects vendor extensions into model classes or their properties."
+ " Class-level format: ModelName.x-extension-name=value."
+ " Property-level format: ModelName.propertyBaseName.x-extension-name=value."
+ " Values are strings, applied at render time, and overwrite existing values."
+ " For the extra-annotation extensions, separate multiple annotations in a single"
+ " value with spaces (e.g. ModelName.x-class-extra-annotation=@Foo @Bar), not"
+ " commas, since an unquoted comma separates different injection targets."
+ " You can also have multiple occurrences of this option.")
private List<String> injectModelVendorExtensions = new ArrayList<>();

@Option(
name = {"--inject-operation-vendor-extensions"},
title = "inject operation vendor extensions",
description = "injects vendor extensions into operations or their parameters."
+ " Operation-level format: operationId.x-extension-name=value."
+ " Parameter-level format: operationId.paramBaseName.x-extension-name=value."
+ " Values are strings, applied at render time, and overwrite existing values."
+ " For the extra-annotation extensions, separate multiple annotations in a single"
+ " value with spaces (e.g. operationId.x-operation-extra-annotation=@Foo @Bar), not"
+ " commas, since an unquoted comma separates different injection targets."
+ " You can also have multiple occurrences of this option.")
private List<String> injectOperationVendorExtensions = new ArrayList<>();

@Option(
name = {"--openapi-normalizer"},
title = "OpenAPI normalizer rules",
Expand Down Expand Up @@ -616,6 +633,7 @@ public void execute() {
applyEnumNameMappingsKvpList(enumNameMappings, configurator);
applyOperationIdNameMappingsKvpList(operationIdNameMappings, configurator);
applyInjectModelVendorExtensionsKvpList(injectModelVendorExtensions, configurator);
applyInjectOperationVendorExtensionsKvpList(injectOperationVendorExtensions, configurator);
applyOpenapiNormalizerKvpList(openapiNormalizer, configurator);
applyTypeMappingsKvpList(typeMappings, configurator);
applyAdditionalPropertiesKvpList(additionalProperties, configurator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public final class GeneratorSettings implements Serializable {
private final Map<String, String> enumNameMappings;
private final Map<String, String> operationIdNameMappings;
private final Map<String, String> injectModelVendorExtensions;
private final Map<String, String> injectOperationVendorExtensions;
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
private final Map<String, String> openapiNormalizer;
private final Set<String> languageSpecificPrimitives;
private final Set<String> openapiGeneratorIgnoreList;
Expand Down Expand Up @@ -337,6 +338,15 @@ public Map<String, String> getInjectModelVendorExtensions() {
return injectModelVendorExtensions;
}

/**
* Gets inject operation vendor extensions.
*
* @return a map of operationId.x-extension-name or operationId.paramBaseName.x-extension-name to extension value
*/
public Map<String, String> getInjectOperationVendorExtensions() {
return injectOperationVendorExtensions;
}

/**
* Gets OpenAPI normalizer rules
*
Expand Down Expand Up @@ -480,6 +490,7 @@ private GeneratorSettings(Builder builder) {
enumNameMappings = Collections.unmodifiableMap(builder.enumNameMappings);
operationIdNameMappings = Collections.unmodifiableMap(builder.operationIdNameMappings);
injectModelVendorExtensions = Collections.unmodifiableMap(builder.injectModelVendorExtensions);
injectOperationVendorExtensions = Collections.unmodifiableMap(builder.injectOperationVendorExtensions);
openapiNormalizer = Collections.unmodifiableMap(builder.openapiNormalizer);
languageSpecificPrimitives = Collections.unmodifiableSet(builder.languageSpecificPrimitives);
openapiGeneratorIgnoreList = Collections.unmodifiableSet(builder.openapiGeneratorIgnoreList);
Expand Down Expand Up @@ -562,6 +573,7 @@ public GeneratorSettings() {
enumNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
operationIdNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
injectModelVendorExtensions = Collections.unmodifiableMap(new HashMap<>(0));
injectOperationVendorExtensions = Collections.unmodifiableMap(new HashMap<>(0));
openapiNormalizer = Collections.unmodifiableMap(new HashMap<>(0));
languageSpecificPrimitives = Collections.unmodifiableSet(new HashSet<>(0));
openapiGeneratorIgnoreList = Collections.unmodifiableSet(new HashSet<>(0));
Expand Down Expand Up @@ -645,6 +657,9 @@ public static Builder newBuilder(GeneratorSettings copy) {
if (copy.getInjectModelVendorExtensions() != null) {
builder.injectModelVendorExtensions.putAll(copy.getInjectModelVendorExtensions());
}
if (copy.getInjectOperationVendorExtensions() != null) {
builder.injectOperationVendorExtensions.putAll(copy.getInjectOperationVendorExtensions());
}
if (copy.getOpenapiNormalizer() != null) {
builder.openapiNormalizer.putAll(copy.getOpenapiNormalizer());
}
Expand Down Expand Up @@ -700,6 +715,7 @@ public static final class Builder {
private Map<String, String> enumNameMappings;
private Map<String, String> operationIdNameMappings;
private Map<String, String> injectModelVendorExtensions;
private Map<String, String> injectOperationVendorExtensions;
private Map<String, String> openapiNormalizer;
private Set<String> languageSpecificPrimitives;
private Set<String> openapiGeneratorIgnoreList;
Expand Down Expand Up @@ -729,6 +745,7 @@ public Builder() {
enumNameMappings = new HashMap<>();
operationIdNameMappings = new HashMap<>();
injectModelVendorExtensions = new HashMap<>();
injectOperationVendorExtensions = new HashMap<>();
openapiNormalizer = new HashMap<>();
languageSpecificPrimitives = new HashSet<>();
openapiGeneratorIgnoreList = new HashSet<>();
Expand Down Expand Up @@ -1236,6 +1253,32 @@ public Builder withInjectModelVendorExtension(String key, String value) {
return this;
}

/**
* Sets the {@code injectOperationVendorExtensions} and returns a reference to this Builder so that the methods can be chained together.
*
* @param injectOperationVendorExtensions the {@code injectOperationVendorExtensions} to set
* @return a reference to this Builder
*/
public Builder withInjectOperationVendorExtensions(Map<String, String> injectOperationVendorExtensions) {
this.injectOperationVendorExtensions = injectOperationVendorExtensions;
return this;
}

/**
* Sets a single {@code injectOperationVendorExtension} and returns a reference to this Builder so that the methods can be chained together.
*
* @param key A key in the format operationId.x-extension-name or operationId.paramBaseName.x-extension-name
* @param value The extension value
* @return a reference to this Builder
*/
public Builder withInjectOperationVendorExtension(String key, String value) {
if (this.injectOperationVendorExtensions == null) {
this.injectOperationVendorExtensions = new HashMap<>();
}
this.injectOperationVendorExtensions.put(key, value);
return this;
}

/**
* Sets the {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together.
*
Expand Down Expand Up @@ -1482,6 +1525,8 @@ public boolean equals(Object o) {
Objects.equals(getModelNameMappings(), that.getModelNameMappings()) &&
Objects.equals(getEnumNameMappings(), that.getEnumNameMappings()) &&
Objects.equals(getOperationIdNameMappings(), that.getOperationIdNameMappings()) &&
Objects.equals(getInjectModelVendorExtensions(), that.getInjectModelVendorExtensions()) &&
Objects.equals(getInjectOperationVendorExtensions(), that.getInjectOperationVendorExtensions()) &&
Objects.equals(getOpenapiNormalizer(), that.getOpenapiNormalizer()) &&
Objects.equals(getLanguageSpecificPrimitives(), that.getLanguageSpecificPrimitives()) &&
Objects.equals(getOpenapiGeneratorIgnoreList(), that.getOpenapiGeneratorIgnoreList()) &&
Expand Down Expand Up @@ -1521,6 +1566,8 @@ public int hashCode() {
getModelNameMappings(),
getEnumNameMappings(),
getOperationIdNameMappings(),
getInjectModelVendorExtensions(),
getInjectOperationVendorExtensions(),
getOpenapiNormalizer(),
getLanguageSpecificPrimitives(),
getOpenapiGeneratorIgnoreList(),
Expand Down
25 changes: 25 additions & 0 deletions modules/openapi-generator-gradle-plugin/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ apply plugin: 'org.openapi.generator'
|None
|Sets specified global properties.

|injectModelVendorExtensions
|Map / Provider<Map>
|None
|Injects vendor extensions into models or their properties without editing the input spec. Keys use the form `modelName.x-extension-name` (model) or `modelName.propertyBaseName.x-extension-name` (model property). See the note below on supplying multiple annotations in a single value.

|injectOperationVendorExtensions
|Map / Provider<Map>
|None
|Injects vendor extensions into operations or their parameters without editing the input spec. Keys use the form `operationId.x-extension-name` (operation) or `operationId.paramBaseName.x-extension-name` (parameter, matched by its raw spec name). See the note below on supplying multiple annotations in a single value.

|configFile
|String / Provider<RegularFile>
|None
Expand Down Expand Up @@ -564,6 +574,21 @@ models: "User:Pet"
----
====

[NOTE]
====
`injectModelVendorExtensions` and `injectOperationVendorExtensions` are a generic mechanism for
setting *any* vendor extension on a model, property, operation, or parameter without editing the spec.
Keep in mind:

* Values are always strings, applied *late* (at codegen/render time) and **overwrite** any existing
value; missing targets are a silent no-op. It is best suited to extensions consumed by templates,
such as the Spring extra-annotation extensions.
* For the extra-annotation extensions, put multiple annotations in one value separated by *spaces*
(as in source), e.g. `"addPet.x-request-body-extra-annotation": "@com.example.MyValidation @Valid"`.
In the Gradle map form commas inside a value need no escaping, so `@Size(min = 0, max = 10)` works
directly (unlike the comma-separated CLI/Maven form).
====

=== openApiValidate

.Options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ class OpenApiGeneratorPlugin : Plugin<Project> {
templateResourcePath.set(generate.templateResourcePath)
auth.set(generate.auth)
globalProperties.set(generate.globalProperties)
injectModelVendorExtensions.set(generate.injectModelVendorExtensions)
injectOperationVendorExtensions.set(generate.injectOperationVendorExtensions)
configFile.set(generate.configFile)
skipOverwrite.set(generate.skipOverwrite)
packageName.set(generate.packageName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@ open class OpenApiGeneratorGenerateExtension(private val project: Project) {
*/
val globalProperties = project.objects.mapProperty<String, String>()

/**
* Injects vendor extensions into models or their properties without editing the input spec.
*
* Keys use the form {@code modelName.x-extension-name} for a model or
* {@code modelName.propertyBaseName.x-extension-name} for a model property; the value is the
* extension value.
*/
val injectModelVendorExtensions = project.objects.mapProperty<String, String>()

/**
* Injects vendor extensions into operations or their parameters without editing the input spec.
*
* Keys use the form {@code operationId.x-extension-name} for an operation or
* {@code operationId.paramBaseName.x-extension-name} for a parameter (matched by its raw spec
* name); the value is the extension value.
*/
val injectOperationVendorExtensions = project.objects.mapProperty<String, String>()

/**
* Path to json configuration file.
* File content should be in a json format { "optionKey":"optionValue", "optionKey1":"optionValue1"...}
Expand Down
Loading
Loading