springdoc / springdoc-openapi

Library for OpenAPI 3 with spring-boot
https://springdoc.org
Apache License 2.0
3.27k stars 494 forks source link

The operationId is unnecessarily deduplicated for a requestBody with multiple content types #2646

Closed scrhartley closed 3 weeks ago

scrhartley commented 3 months ago

Describe the bug

In a Spring Boot RestController that wants to support POST request bodies of both form data or JSON, to use the built-in functionality, multiple endpoint methods must be used to support both content types. The JSON accepting method expects the Spring MVC @RequestBody annotation to be used, while the form data version expects Spring MVC @RequestBody to not be used, so the Swagger annotation must be used instead.

Although the output OpenAPI JSON correctly puts the two as the same request body with different supported content, the operationId is incorrectly deduplicated, changing its value. n.b. This does require the operationId to be set explicitly on both methods, because otherwise the last automatically generated operationId will be picked to represent both methods.

To Reproduce

}



**Expected behavior**

- When the different content types are merged for a single requestBody, this should not trigger deduplication and the originally specified operationId should be used.
grimly commented 3 months ago

Hello, The same apply for multiple response content type.

grimly commented 3 months ago

Here is a customizer that can circumvent the issue until resolved but then, be careful, it removes any deduplication capabilities:


@Component
public class PreserveOperationIdOpenApiCustomizer implements GlobalOperationCustomizer {
  @Override
  public Operation customize(Operation operation, HandlerMethod handlerMethod) {
    operation.setOperationId(
      Optional
        .ofNullable(handlerMethod.getMethodAnnotation(io.swagger.v3.oas.annotations.Operation.class))
        .map(io.swagger.v3.oas.annotations.Operation::operationId)
        .orElseGet(handlerMethod.getMethod()::getName)
    );
    return operation;
  }
}
scrhartley commented 1 month ago

I ended up with a less aggressive customizer:

@Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
    Optional
        .ofNullable(handlerMethod.getMethodAnnotation(io.swagger.v3.oas.annotations.Operation.class))
        .map(io.swagger.v3.oas.annotations.Operation::operationId)
        .ifPresent(operation::setOperationId);
    return operation;
}
scrhartley commented 3 weeks ago

Thank you, I'll look forward to the next release.