Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add access to ApiResponses for OperationCustomizer #625

Closed
jaloren opened this issue Apr 28, 2020 · 1 comment
Closed

Add access to ApiResponses for OperationCustomizer #625

jaloren opened this issue Apr 28, 2020 · 1 comment
Labels
bug Something isn't working

Comments

@jaloren
Copy link

jaloren commented Apr 28, 2020

Describe the bug

We have a controller that returns a time.Duration. This means the generated swagger specification shows the 200 response for the controller as having an object schema. We want to customize this response so that the schema is a string instead.

We wanted to use the OperationCustomizer to handle this:

Operation customize(Operation operation, HandlerMethod handlerMethod) 

Our plan was to get the return type of the controller like so handlerMethod.getReturnType().getParameterType() and if it was of a java.time.Duration, get the 200 response schema and set it to a string. The problem is that default API responses are not yet added so operation.getResponses() returns null.

We understand that we can place ApiResponse annotation on the controller to override this however we want to avoid doing that because its a cross cutting concern and we don't to add code to the controller to generate a valid swagger spec.

To Reproduce

Steps to reproduce the behavior:

  • What version of spring-boot you are using?
    2.2.6

  • What modules and versions of springdoc-openapi are you using?

group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.3.2'

  • What is the actual and the expected result using OpenAPI Description (yml or json)?

The actual response is this:

description: default response
content:
  application/json:
    schema:
      type: object
      properties:
        seconds:
          type: integer
          format: int64
        negative:
          type: boolean
        zero:
          type: boolean
        units:
          type: array
          items:
            type: object
            properties:
              duration:
                type: string
                properties: {}
              dateBased:
                type: boolean
              timeBased:
                type: boolean
              durationEstimated:
                type: boolean
        nano:
          type: integer
          format: int32

The expected response is this:

        '200':
          description: ok
          content:
            application/json:
              schema:
                type: string
  • Provide with a sample code (HelloController) or Test that reproduces the problem.

I can't share the actual code but this is a close approximation. The key piece is the return type being Duration in the controller.

import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.Schema;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
​
import java.time.Duration;
import java.util.Map;
​
@SpringBootApplication
class Scratch {
​
    public static void main(String[] args) {
        SpringApplication.run(Scratch.class, args);
    }
​
    @Bean
    public OperationCustomizer convertOperationsToString() {
        return new JavaTimeOperationCustomizer();
    }
​
    private static class JavaTimeOperationCustomizer implements OperationCustomizer {
​
        @Override
        public Operation customize(Operation operation, HandlerMethod handlerMethod) {
​
            if (handlerMethod.getReturnType().getParameterType().isAssignableFrom(Duration.class)) {
                for (Map.Entry<String, io.swagger.v3.oas.models.responses.ApiResponse> entry:  operation.getResponses().entrySet()) {
                    io.swagger.v3.oas.models.responses.ApiResponse response = entry.getValue();
                    Content content = response.getContent();
                    if (content.containsKey(MediaType.APPLICATION_JSON_VALUE)) {
                        Schema schema = content.get(MediaType.APPLICATION_JSON_VALUE).getSchema();
                        schema.getProperties().clear();
                        schema.setType("string");
                    }
                }
            }
            return operation;
        }
    }
}
​
@RestController
class ScratchController {
​
    @GetMapping(value = "/api/v2/timeout",
        consumes = { MediaType.ALL_VALUE },
        produces = { MediaType.APPLICATION_JSON_VALUE })
    public Duration timeouts() {
        return Duration.ofSeconds(5);
    }
}

Expected behavior

operation.getResponses() is non-null / non-empty when the OperationCustomizer is executed

@bnasslahsen
Copy link
Contributor

bnasslahsen commented Apr 28, 2020

@jaloren,

I will see if we can improve the OperationCustimizer to have access to the ApiResponse Object as well.
If you need to replace the schema of Duration, you can just do:

static {
	SpringDocUtils.getConfig().replaceWithSchema(Duration.class, new StringSchema());
}

Or you can use PropertyCustomizer instead by testing on the type of the propery and replacing it with your target schema.

@bnasslahsen bnasslahsen changed the title OperationCustomizer does not have access to ApiResponses Add access to ApiResponses for OperationCustomizer Apr 28, 2020
@bnasslahsen bnasslahsen added the bug Something isn't working label Jan 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants