Skip to content

Conversation

@TAEWOOKK
Copy link

@TAEWOOKK TAEWOOKK commented Nov 1, 2025

Description

This PR implements automatic enum description injection into OpenAPI operation descriptions.
It addresses the issue described in #3116

What's Changed

  • Added @EnumDescription annotation to mark enum fields for automatic description generation
  • Automatically extracts enum constants and their descriptions from annotated fields
  • Formats enum information as Markdown and appends to operation descriptions

Usage Example

public enum UserStatus {
    ACTIVE("Represents an active user"),
    INACTIVE("Represents an inactive user"),
    PENDING("Represents a user pending activation");

    private final String description;

    UserStatus(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

public class UserRequest {
    @EnumDescription  // or @EnumDescription(fieldName = "label")
    private UserStatus status;
    
    private String name;
}
image image

Technical Details

  • Customizer: EnumDescriptionOperationCustomizer implements GlobalOperationCustomizer
  • Bean Registration: Automatically registered in SpringDocConfiguration
  • Field Scanning: Recursively scans DTO classes including parent classes
  • Reflection: Uses reflection to read enum field values safely

Future Improvements

As mentioned in the original issue, we considered adding support for schema-level descriptions as well.
This could be implemented using PropertyCustomizer to inject enum descriptions into schema property descriptions.
This enhancement could be added in a future PR if there's interest.

@Mattias-Sehlstedt
Copy link
Contributor

I would suggest adding a test case for when the annotation is used. This both as an illustrative case for how to use it, but also to show that the current implementation actually behaves as expected when utilized. Use the current tests as a suggestion for how they usually are structured.

@TAEWOOKK
Copy link
Author

TAEWOOKK commented Nov 2, 2025

Thanks for the feedback!
I'd like to clarify where to add the test case. I notice there are two different test patterns in the project:

  1. springdoc-openapi-starter-common/src/test/java/org/springdoc/core/

    • Unit tests using WebApplicationContextRunner (like SpringDocHateoasConfigurationTest)
  2. springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/appXXX/

    • Integration tests following the appXXX pattern

Note that the EnumDescription annotation and EnumDescriptionOperationCustomizer are both implemented in the common module, which is why I'm unsure which test pattern to follow.

Which directory structure should I use for the @EnumDescription test case?

@Mattias-Sehlstedt
Copy link
Contributor

I prefer the second approach with an integration test as per appXXX. So one can easily see exactly how the generated OpenAPI specification looks.

@TAEWOOKK
Copy link
Author

TAEWOOKK commented Nov 3, 2025

This PR adds test cases to verify the EnumDescription annotation functionality:

  • Test for default description field
  • Test for custom fieldName (e.g., label)

The tests verify that enum information is properly added to OpenAPI operation descriptions.

Please review when convenient. Thanks!

@Mattias-Sehlstedt
Copy link
Contributor

I see now that there exists two different ways of doing integration tests, either in the x-api or the x-ui folder. Could we replicate your test so that it is also present in as a x-api test?

The difference is that the assertion is made against the entire apixxx.json, so it is easy to see exactly how the exposed specification looks like.


A bit off topic, but what is the reason for deriving the name from a potential enum value, rather than explicitly having the values in a @Schema annotation on the class that carries the information? Or are we sending a domain object out directly with the api? I always try to segment the domain and the api with pure DTO objects, that are responsible for de(serialization) and carrying any meaningful information to the client through the specification (so @Schema annotation).

So the domain is:

public enum UserStatus {
    ACTIVE,
    INACTIVE,
    PENDING;
}

while the DTO is

@Schema(name = "UserStatus", description = "...", example = "...", ....)
public enum UserStatusDTO {
    ACTIVE,
    INACTIVE,
    PENDING;
}

The current approach seems like it could potentially cause minor "leakage", since it is not intuitive that anything that goes into an enum's value could reach the client. Sure it is semi-safe, since it is opt-in, but I would always argue for a more strict domain/api segmentation.

@TAEWOOKK TAEWOOKK closed this Nov 9, 2025
@TAEWOOKK TAEWOOKK reopened this Nov 9, 2025
@TAEWOOKK
Copy link
Author

TAEWOOKK commented Nov 9, 2025

Thank you for the detailed feedback. I completely agree with your point about the importance of strict Domain-API segmentation.

My primary motivation for implementing @EnumDescription was to solve a clear pain point for frontend developers. They often find it difficult to check the available values and descriptions for an Enum used in API requests/responses. This feature automates embedding this information directly into the operation's description, allowing them to understand and use the API much faster.

I fully understand your concern about potential information 'leakage'. As you rightly pointed out, the ideal and safest approach is to separate the internal Domain Enum (like UserStatus) from the DTO Enum (like UserStatusDTO) used for API specifications.

This feature was designed with exactly that pattern in mind. The intention is that developers will use @EnumDescription on the DTO Enum field, not the internal Domain Enum.

While this still "exposes" information, it's no longer an uncontrolled leak, but rather a controlled specification. The developer explicitly decides which Enum (the DTO-safe one) to expose. I believe the benefit of providing clear, in-spec documentation to frontend developers outweighs the risk of this controlled exposure.

This is also precisely why I added the methodName (or fieldName) option—to provide granular control. An Enum can have multiple methods:

An internal-logic method (e.g., getInternalReason())

A public-facing description method (e.g., getLabel() or getDescription())

By allowing developers to specify @EnumDescription(methodName = "getLabel"), they can explicitly choose the "safe" method for documentation. This mechanism directly prevents the accidental leakage of internal data and ensures only public-facing information is used.

In summary, this feature doesn't prevent or discourage good architecture (like Domain/DTO separation); it enhances it by bridging the information gap between a well-defined DTO (Enum) and the frontend developer who needs to consume it.

Thank you again for the insightful feedback, and I look forward to hearing your thoughts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants