OpenAPITools / OpenAPITools/openapi-generator

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

Open
#22,979 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Enhancement: Feature
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue names no implementation files or tests. Start by tracing how OpenAPI Generator reads additionalProperties and exposes vendor extensions across model, field, operation, and parameter objects. Compare removal-before-addition behavior with the YAML and Gradle examples; done means the four maps work across codegens while inline schemas remain out of scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin
Domain
api, devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.