Skip to content

[REQ-ALL CODEGENS] Proposal/Offer to implement : Side-Loadable Vendor Extensions for OpenAPI Generator #22979

@Picazsoo

Description

@Picazsoo

Proposal: Side-Loadable Vendor Extensions for OpenAPI Generator

Objective

Introduce a mechanism to side-load arbitrary OpenAPI x- vendor extensions, including annotations, interface implementations, and setter-level customizations, without modifying the base OpenAPI specification.
The mechanism should support adding and removing extensions for models, fields, operations, and operation parameters, fully respecting user-specified syntax (e.g., Kotlin @field: or @return: targets).

It would mean the spec could be fully decoupled from the x-vendor extensions. It would make it possible to efficiently add any extensions when not in control of the original API spec. It would also allow for removal of vendor extensions where it is impossible to implement (e.g. a spec with custom annotations utilizing classes not available on the client side)


Design Overview

I propose four top-level maps to control side-loaded vendor extensions:

Attribute Description
modelVendorExtensionAdd Add extensions to models (class-level) or fields
modelVendorExtensionRemove Remove extensions from models or fields
operationVendorExtensionAdd Add extensions to operations (method-level) or operation parameters
operationVendorExtensionRemove Remove extensions from operations or operation parameters
  • Leaf nodes map x- property names to arbitrary values.
  • Users fully specify the content, including annotations or other custom metadata.
  • Fields and parameters are nested inside the relevant model or operation entry.
  • Supports all currently used vendor extensions. As well as any future ones. So for java spring e.g.:
    • Model-level: x-class-extra-annotation, x-implements, x-discriminator-value
    • Field-level: x-field-extra-annotation, x-setter-extra-annotation, validation messages
    • Operation-level: x-operation-extra-annotation, x-tags, x-accepts, x-content-type, x-spring-paginated, x-spring-api-version
    • Operation parameter-level: x-field-extra-annotation, x-version-param, validation messages

Example YAML Side-Load

modelVendorExtensionAdd:
  User:
    class:
      x-class-extra-annotation:
        - "@Schema(description=\"Side-loaded User model\")"
        - "@JsonInclude(JsonInclude.Include.NON_NULL)"
      x-implements:
        - "Auditable"
        - "Serializable"
    fields:
      id:
        x-field-extra-annotation:
          - "@NotNull"
        x-setter-extra-annotation:
          - "@JsonProperty(\"id\")"
      email:
        x-field-extra-annotation:
          - "@Email"
          - "@JsonProperty(\"email_address\")"
      password:
        x-field-extra-annotation:
          - "@JsonIgnore"
      roles:
        x-setter-extra-annotation:
          - "@JsonDeserialize(as = LinkedHashSet.class)"

modelVendorExtensionRemove:
  User:
    class:
      - "x-old-schema-property"
    fields:
      id:
        - "x-deprecated-field-annotation"
      roles:
        - "x-old-collection-handler"

operationVendorExtensionAdd:
  getUserById:
    method:
      x-operation-extra-annotation:
        - "@Transactional"
        - "@return: @Schema(description=\"Full user returned\")"
      x-tags:
        - "User"
        - "Read"
      x-spring-paginated: true
    parameters:
      userId:
        x-field-extra-annotation:
          - "@NotNull"
          - "@Min(1)"
        x-pattern-message: "userId must be numeric"
  createUser:
    method:
      x-operation-extra-annotation:
        - "@Transactional"
        - "@return: @Schema(description=\"Created user\")"
      x-tags:
        - "User"
        - "Write"
      x-content-type: "application/json"
    parameters:
      body:
        x-field-extra-annotation:
          - "@Valid"
        x-size-message: "User payload exceeds allowed size"

operationVendorExtensionRemove:
  getUserById:
    method:
      - "x-old-operation-flag"
    parameters:
      userId:
        - "x-previous-validation-annotation"
  createUser:
    parameters:
      body:
        - "x-old-body-annotation"

or as a part of build.gradle.kts file:

additionalProperties.set(
    mapOf(
        "useTags" to "true",
        "hideGenerationTimestamp" to "true",
        "modelVendorExtensionAdd" to mapOf(
            "User" to mapOf(
                "class" to mapOf(
                    "x-class-extra-annotation" to listOf(
                        "@Schema(description=\"Side-loaded User model\")",
                        "@JsonInclude(JsonInclude.Include.NON_NULL)"
                    ),
                    "x-implements" to listOf(
                        "Auditable",
                        "Serializable"
                    )
                ),
                "fields" to mapOf(
                    "id" to mapOf(
                        "x-field-extra-annotation" to listOf("@NotNull"),
                        "x-setter-extra-annotation" to listOf("@JsonProperty(\"id\")")
                    ),
                    "email" to mapOf(
                        "x-field-extra-annotation" to listOf("@Email", "@JsonProperty(\"email_address\")")
                    ),
                    "password" to mapOf(
                        "x-field-extra-annotation" to listOf("@JsonIgnore")
                    ),
                    "roles" to mapOf(
                        "x-setter-extra-annotation" to listOf("@JsonDeserialize(as = LinkedHashSet.class)")
                    )
                )
            )
        ),
        "modelVendorExtensionRemove" to mapOf(
            "User" to mapOf(
                "class" to listOf("x-old-schema-property"),
                "fields" to mapOf(
                    "id" to listOf("x-deprecated-field-annotation"),
                    "roles" to listOf("x-old-collection-handler")
                )
            )
        ),
        "operationVendorExtensionAdd" to mapOf(
            "getUserById" to mapOf(
                "method" to mapOf(
                    "x-operation-extra-annotation" to listOf(
                        "@Transactional",
                        "@return: @Schema(description=\"Full user returned\")"
                    ),
                    "x-tags" to listOf("User", "Read"),
                    "x-spring-paginated" to true
                ),
                "parameters" to mapOf(
                    "userId" to mapOf(
                        "x-field-extra-annotation" to listOf("@NotNull", "@Min(1)"),
                        "x-pattern-message" to "userId must be numeric"
                    )
                )
            ),
            "createUser" to mapOf(
                "method" to mapOf(
                    "x-operation-extra-annotation" to listOf(
                        "@Transactional",
                        "@return: @Schema(description=\"Created user\")"
                    ),
                    "x-tags" to listOf("User", "Write"),
                    "x-content-type" to "application/json"
                ),
                "parameters" to mapOf(
                    "body" to mapOf(
                        "x-field-extra-annotation" to listOf("@Valid"),
                        "x-size-message" to "User payload exceeds allowed size"
                    )
                )
            )
        ),
        "operationVendorExtensionRemove" to mapOf(
            "getUserById" to mapOf(
                "method" to listOf("x-old-operation-flag"),
                "parameters" to mapOf(
                    "userId" to listOf("x-previous-validation-annotation")
                )
            ),
            "createUser" to mapOf(
                "parameters" to mapOf(
                    "body" to listOf("x-old-body-annotation")
                )
            )
        )
    )
)

Workflow

  1. Load Base OpenAPI Spec
    Load the original OpenAPI specification as the starting point for code generation.

  2. Apply Removal Maps
    Apply all modelVendorExtensionRemove and operationVendorExtensionRemove entries first to remove deprecated or unwanted properties from the base spec.

  3. Apply Addition Maps
    Apply all modelVendorExtensionAdd and operationVendorExtensionAdd entries to inject new vendor extensions into models, fields, operations, and operation parameters.

  4. Generate Code

    • Annotations and other x- properties are applied exactly as specified.
    • Templates receive the merged x- properties for runtime, documentation, or generation purposes.

Issue with inline schemas

This proposal intentionally targets only named OpenAPI elements
(models, operations, parameters, and fields).

Inline schemas do not have stable identifiers and are thus
out of scope for direct side-loading. Users who need to apply
vendor extensions to inline schemas would be expected to promote them
to named models using existing OpenAPI Generator mechanisms
(e.g. use useInlineModelResolver with inlineSchemaNameMapping or just do explicit schema extraction).

This constraint would keep the side-loading mechanism deterministic,
language-agnostic, and compatible with existing generators.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions