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

Custom Converters are not excluded if not registered for Http Message Converter #2165

Closed
RittikeGhosh opened this issue Mar 25, 2023 · 6 comments
Labels
bug Something isn't working

Comments

@RittikeGhosh
Copy link

RittikeGhosh commented Mar 25, 2023

Bug Description
Feature to support Custom Spring Converter(#1534), is generating wrong openapi specification. I think all converters are considered as Http Message Converter, which it shouldn't. Hence it is generating the request body schema as the Source Object picked from the converter with target as the actual Object defined in the Request Handler Method in Controller.
This is happening for both WebMvc and WebFlux.

To Reproduce
Using :

  • spring boot 3.0.5
  • webflux
  • org.springdoc:springdoc-openapi-starter-webflux-ui:2.0.4

Code setup:

record ObjectA() {};

record ObjectB() {};

@Component
class BToAConvertor implements Converter<ObjectB, ObjectA> {
    @Override
    public ObjectA convert(ObjectB source) {
        return new ObjectA();
    }
}

@RestController
class Controller {
    @GetMapping("/test")
    public String test(@RequestBody ObjectA request) {
        return "OK!";
    }
}

Output OpenApi Specification:
Generated ObjectB instead of ObjectA as request schema

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
  - url: http://localhost:8080
    description: Generated server url
paths:
  /test:
    get:
      tags:
        - controller
      operationId: test
      parameters:
        - name: request
          in: query
          required: true
          schema:
            $ref: '#/components/schemas/ObjectB'
      responses:
        '200':
          description: OK
          content:
            '*/*':
              schema:
                type: string
components:
  schemas:
    ObjectB:
      type: object

Expected behavior
I expect the converters to be picked only if it is registered for Http Message Converter. Here I didn't register it for that, it is used for other internal purpose.

Expected OpenApi Specification:

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
  - url: http://localhost:8080
    description: Generated server url
paths:
  /test:
    get:
      tags:
        - controller
      operationId: test
      parameters:
        - name: request
          in: query
          required: true
          schema:
            $ref: '#/components/schemas/ObjectA'
      responses:
        '200':
          description: OK
          content:
            '*/*':
              schema:
                type: string
components:
  schemas:
    ObjectA:
      type: object

Workaround Code

@GetMapping(value = "/test")
@Operation(requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
        required = true,
        content = @Content(schema = @Schema(implementation = ObjectA.class)))
)
public String test(@RequestBody ObjectA request) {
    return "OK!";
}
@uc4w6c
Copy link
Collaborator

uc4w6c commented Mar 31, 2023

I think the cause is that BToAConvertor uses @component to create a Bean.
I think the created OpenApi Specification is correct as BToAConvertor is registered as a Converter.

example

public record User(String id) {}

@Component
public class UserConverter implements Converter<String, User> {

  @Override
  public User convert(String userId) {
    User user = new User(userId);
    return user;
  }
}

@RestController
@RequestMapping("converter")
public class ConverterController {
  @GetMapping("/{userId}")
  public User doSomething(@PathVariable("userId") User user) {
    return user;
  }
}
$ curl http://localhost:8080/converter/test
{"id":"test"}

@piotrp
Copy link

piotrp commented Mar 31, 2023

A shorter workaround is to use @Schema(implementation = ObjectA.class) directly on parameter:

@GetMapping(value = "/test")
public String test(@Schema(implementation = ObjectA.class) @RequestBody ObjectA request) {
    return "OK!";
}

@RittikeGhosh
Copy link
Author

@uc4w6c But that is true when we use @Pathvariable, not for @RequestBody. Also the bean may be present in the context, but I do not think it is registered for payload conversion.

Tried testing if string data in body can be converted to ObjectA by setting content type as text/plain, and creating String to A converter. Did not work!

@Component
class StringToAConvertor implements Converter<String, ObjectA> {
    @Override
    public ObjectA convert(String source) {
        return new ObjectA(source);
    }
}

@RestController
class Controller {
    @PostMapping(value = "/test", consumes = MediaType.TEXT_PLAIN_VALUE)
    public ObjectA test(@RequestBody ObjectA request) {
        return request;
    }

    @ExceptionHandler
    public void genericException(Exception e) {
        e.printStackTrace();
    }
}

THROWS
org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'text/plain' not supported for bodyType=com.example.springdoc.ObjectA"

@uc4w6c
Copy link
Collaborator

uc4w6c commented Apr 1, 2023

@RittikeGhosh
I'm sorry. As you pointed out, the converter doesn't work with @RequestBody, so it seems that the OpenApi Specification is incorrect.

@bnasslahsen bnasslahsen added the bug Something isn't working label Apr 1, 2023
@bnasslahsen
Copy link
Contributor

@RittikeGhosh,

i have added a workaround for it.
You can test it with the latest SNAPSHOT and let us know.

@RittikeGhosh
Copy link
Author

This works now. Tested with the latest version 2.1.0
Thanks!

@springdoc springdoc locked as resolved and limited conversation to collaborators Apr 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants