OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.81k stars 6.58k forks source link

[Spring] [Reactive] Controller parameters wrapped in Mono #4273

Open ilyas-keser opened 5 years ago

ilyas-keser commented 5 years ago

A generated controller method for Webflux (spring, reactive) look like this:

public Mono<ResponseEntity<MyModel>> createModel(
    @Valid @RequestBody Mono<MyCreateModel> myCreateModel,
        ServerWebExchange exchange) throws Exception { ...

Q1: Why is myCreateModel parameter wrapped with a Mono?

Q2: Do I need ServerWebExchange param? If not, can I disable the generation?

Thx

Nankh commented 4 years ago

Same pb.

We have temporary created our own custom template...

But we need offical support now...

ssternal commented 3 years ago

At first glance this looks a bit odd to me as well. However, I'm okay with it since I can wrap all my logic in the pipe of the parameters. I still have an issue when it comes to serialization with Jackson as I'm using REST. Whenever I try to wrap a parameter within a reactive type (Mono/Flux), Jackson is unable to deserialize the parameter:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class reactor.core.publisher.Mono]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `reactor.core.publisher.Mono` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (PushbackInputStream); line: 1, column: 1]] with root cause
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `reactor.core.publisher.Mono` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (PushbackInputStream); line: 1, column: 1]
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601) ~[jackson-databind-2.12.3.jar:2.12.3]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:378) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:185) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.8.jar:5.3.8]
    [...]

This surely isn't the fault of the OpenAPI generator, but as it encapsulates the parameters it forces me to turn of the reactive option and build around it to be able to still have it reactive.

Edit:

For those looking for a quick workaround you may either use the async option or the responseWrapper option instead.

Using the async option you can keep pretty everything as you have and convert the Mono/Flux afterwards to a CompletableFuture using the toFuture() method:

@RestController
public class MyController implements MyApi {
    @Override
    public CompletableFuture<ResponseEntity<MyResult>> myEndpoint(final MyDto myDto) {
        return Mono.just(myDto)
                .map(myService::myProcessor)
                .map(myResult -> ResponseEntity.ok(myResult))
                .toFuture();
    }
}

When using the responseWrapper option your controller's implementation can return any Mono/Flux because they implement this interface:

@RestController
public class MyController implements MyApi {
    @Override
    public Publisher<ResponseEntity<MyResult>> myEndpoint(final MyDto myDto) {
        return Mono.just(myDto)
                .map(myService::myProcessor)
                .map(myResult -> ResponseEntity.ok(myResult));
    }
}
shi-rudo commented 2 years ago

I just wanted to give quick feedback that the bug still exists, a year later. When activing reactive

configOptions.set(mapOf(
       "reactive" to "true",
    ))

Jackson cant parse requests like

override fun doSomething(
        doSomethingRequest: Mono<DoSomethingRequest>?,
        exchange: ServerWebExchange?
    ): Mono<ResponseEntity<Flux<Something>>> {
       TODO()
    }
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of 
`reactor.core.publisher.Mono` (no Creators, like default constructor, exist): abstract types
 either need to be mapped to concrete types, have custom deserializer, or contain additional
 type informationat [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]

Is a fix realistic regarding version 7.0?

martin-mfg commented 1 year ago

The Jackson deserialization of Mono parameters works fine for me. I am using OpenAPI generator 6.4.0. I suspect the problem some people here are facing is misconfiguration of dependencies. Most importantly, please make sure to remove the spring-boot-starter-web dependency when turning on reactive. If this doesn't help, it would be great if you could share a minimal reproducible example of the problem.

ssternal commented 1 year ago

At least for me this issue was fixed in the mean time. It works fine with currently generated code using the latest official version (6.6.0) and latest Spring Webflux. However, I'm using a pure reactive stack currently (excluding traditional servlet stack). To be honest I don't know what caused these issues back in the days, but the idea of @martin-mfg towards a misconfiguration seems most promising to me for now.

jaredr-branscum commented 1 year ago

Does someone here have a published working CRUD example using this generated with reactive enabled? The suggested workaround above is not clear to me in how its overriding the generated Api method correctly and dealing with a reactive request parameter.