micronaut-projects / micronaut-openapi

Generates OpenAPI / Swagger Documentation for Micronaut projects
https://micronaut-projects.github.io/micronaut-openapi/latest/guide/index.html
Apache License 2.0
80 stars 94 forks source link

Unusable declarative HTTP client generated from OpenAPI for file download #1703

Closed theobisproject closed 2 months ago

theobisproject commented 3 months ago

Expected Behavior

The generated client is able to download files from the API. The API is expected to deliver files >10GB.

Actual Behaviour

Calling the client fails with the following exception

Error decoding HTTP response body: No bean introspection available for type [class java.io.InputStream]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected

I already found micronaut-projects/micronaut-core#10256 but I found no way to influence the openapi-generator to generate method signature with byte[]. It would still not be usable since the size of a byte[] is limited. The documentation for downloading a big file with StreamingHttpClient also does not help me with the generated client.

I also tried the reproduction steps with reactive = true for which the client sadly fails in the same way.

Steps To Reproduce

Take the following OpenAPI spec

---
openapi: 3.0.3
info:
  title: Test resource
  version: 8.0-SNAPSHOT
paths:
  /{id}:
    get:
      summary: Fetch data
      operationId: fetchData
      parameters:
        - name: id
          in: path
          required: true
          schema:
            format: int64
            minimum: 0
            type: integer
            nullable: false
      responses:
        "200":
          description: Job found
          headers:
            Content-Disposition:
              description: File name for the download
              style: simple
          content:
            application/zip:
              schema:
                format: binary
                type: string

Generate a client with micronaut-openapi

micronaut {
    openapi {
        client(file("src/openapi/openapi.yml")) {
            apiPackageName = "com.example.openapi.api"
            modelPackageName = "com.example.openapi.model"
            clientId = "test"
            useOptional = true
            useReactive = false
        }
    }
}

Resulting class

@Generated("io.micronaut.openapi.generator.JavaMicronautClientCodegen")
@Client("test")
public interface DefaultApi {

    /**
     * {@summary Fetch data}
     *
     * @param id (required)
     * @return InputStream
     */
    @Get("/{id}")
    InputStream fetchData(
        @PathVariable("id") @NotNull @Min(0L) Long id
    );
}

Environment Information

Operating System: Ubuntu 22.04 Java Version: 21.0.2

Example Application

No response

Version

4.5.1

altro3 commented 2 months ago

@graemerocher How can I fix it? Micronaut controller / client method can't return InputStream? Which class I need to return?

altro3 commented 2 months ago

I chaged return type:

InputStream -> HttpResponse<FileCustomizableResponseType>

But I'm not sure if this is the right solution.

@theobisproject Could you check your code with my fix in https://github.com/micronaut-projects/micronaut-openapi/pull/1704 ?

altro3 commented 2 months ago

@graemerocher Is this solution right?

theobisproject commented 2 months ago

@altro3 Sadly the changes also do not work. Trying to download a 4.5GB file leads to the following exception.

io.micronaut.http.client.exceptions.ContentLengthExceededException: Client 'file-download': The received length exceeds the maximum allowed content length [10485760]
    at io.micronaut.http.client.netty.DefaultHttpClient$BaseHttpResponseHandler.exceptionCaught(DefaultHttpClient.java:2082)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Assembly trace from producer [reactor.core.publisher.FluxCreate] :
    reactor.core.publisher.Flux.create(Flux.java:650)
    io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$27(DefaultHttpClient.java:1122)
Error has been observed at the following site(s):
    *_________Flux.create ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$27(DefaultHttpClient.java:1122)
    *____Mono.flatMapMany ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1109)
    |_          Flux.next ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.<init>(ReactorExecutionFlowImpl.java:50)
    |_           Mono.map ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.map(ReactorExecutionFlowImpl.java:71)
    |_ Mono.onErrorResume ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.onErrorResume(ReactorExecutionFlowImpl.java:77)
    |_           Mono.map ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.map(ReactorExecutionFlowImpl.java:71)
    |_          Flux.from ⇢ at io.micronaut.configuration.metrics.binder.web.WebMetricsPublisher.<init>(WebMetricsPublisher.java:124)
    *__________Mono.error ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.toMono(ReactorExecutionFlowImpl.java:145)
    *___________Mono.from ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.<init>(ReactorExecutionFlowImpl.java:50)
    |_           Mono.map ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.map(ReactorExecutionFlowImpl.java:71)
    |_ Mono.onErrorResume ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.onErrorResume(ReactorExecutionFlowImpl.java:77)
    |_       Mono.flatMap ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.flatMap(ReactorExecutionFlowImpl.java:59)
    |_          Flux.from ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1144)
    |_       Flux.timeout ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1153)
    |_ Flux.onErrorResume ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1154)
    *__________Mono.error ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.toMono(ReactorExecutionFlowImpl.java:145)
    *__________Flux.error ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$28(DefaultHttpClient.java:1158)
    *______Flux.switchMap ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchange(DefaultHttpClient.java:834)
    |_      Flux.doOnNext ⇢ at io.micronaut.http.client.netty.DefaultHttpClient$1.exchange(DefaultHttpClient.java:561)
Original Stack Trace:
        at io.micronaut.http.client.netty.DefaultHttpClient$BaseHttpResponseHandler.exceptionCaught(DefaultHttpClient.java:2082)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.exceptionCaught(DefaultHttpClient.java:2318)
        at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:346)
        at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:325)
        at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:317)
        at io.netty.channel.ChannelInboundHandlerAdapter.exceptionCaught(ChannelInboundHandlerAdapter.java:143)
        at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:346)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:447)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:333)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:455)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
        at io.micronaut.http.client.netty.ResettableReadTimeoutHandler$NextInterceptor.channelRead(ResettableReadTimeoutHandler.java:92)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:289)
        at io.micronaut.http.client.netty.ResettableReadTimeoutHandler.channelRead(ResettableReadTimeoutHandler.java:64)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:104)
        at reactor.core.publisher.Flux.blockFirst(Flux.java:2766)
        at io.micronaut.http.client.netty.DefaultHttpClient$1.exchange(DefaultHttpClient.java:571)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.lambda$handleSynchronous$1(HttpClientIntroductionAdvice.java:233)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleBlockingCall(HttpClientIntroductionAdvice.java:620)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleSynchronous(HttpClientIntroductionAdvice.java:232)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.intercept(HttpClientIntroductionAdvice.java:195)
        at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:138)
        at io.micronaut.validation.ValidatingInterceptor.validateReturnMicronautValidator(ValidatingInterceptor.java:153)
        at io.micronaut.validation.ValidatingInterceptor.intercept(ValidatingInterceptor.java:139)
        at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:138)

Trying a smaller file which fits into the content length leads to the same exception as with the inputstream.

io.micronaut.http.client.exceptions.HttpClientResponseException: Client 'file-download': Error decoding HTTP response body: No bean introspection available for type [interface io.micronaut.http.server.types.files.FileCustomizableResponseType]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected

Also it feels a bit weird to add a dependency to micronaut-http-server when I'm generating a client.

altro3 commented 2 months ago

Ok, let's try to return StreamedFile

altro3 commented 2 months ago

@theobisproject try i t again, pls

theobisproject commented 2 months ago

Also leads to the bean introspection error

Error decoding HTTP response body: No bean introspection available for type [class io.micronaut.http.server.types.files.StreamedFile]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected
altro3 commented 2 months ago

@theobisproject thanks

@sdelamo @graemerocher @yawkat @dstepanov Need help with this case. I don’t understand what needs to be generated for the http client to work correctly

altro3 commented 2 months ago

@yawkat Could you help?

dstepanov commented 2 months ago

That class is from the server package, not sure if we have something for client.

altro3 commented 2 months ago

@dstepanov and what can we do with it? How to write correct client if you want to get file as InputStream?

graemerocher commented 2 months ago

don't think it is supported today to read a file as an input stream so this feature shouldn't even exist in the generator. Likely this is fixable though now with the byte body abstraction that @yawkat implemented

The first thing to do would be to implement the feature in core and add tests before generating code for a feature that doesn't exist.

yawkat commented 2 months ago

It's not possible still because ByteBody is implemented only for the server atm

altro3 commented 2 months ago

@graemerocher @dstepanov @yawkat I have only one question: It turns out that I can’t describe a method in an HTTP client that will return me the body of a binary file (in the form of a stream or a byte array or any other object)? I tried different ways to describe the client method, but I still couldn't get the file.

For example, I have this endpoint:

    @Get("/{id}")
    public FileCustomizableResponseType fetchData(
            @PathVariable("id") @NotNull @Min(0L) Long id
    ) {

        try {
            return new StreamedFile(new FileInputStream("test.zip"), MediaType.ZIP_TYPE);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

How I need to describe HTTP client to get result of this method? I tried different vriants:

    @Get("/{id}")
    byte[] fetchData(
        @PathVariable("id") @NotNull @Min(0L) Long id
    );
    @Get("/{id}")
    Object fetchData(
        @PathVariable("id") @NotNull @Min(0L) Long id
    );

But every time I see that error:

Internal Server Error
io.micronaut.http.client.exceptions.HttpClientResponseException: Internal Server Error
    at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.makeErrorFromRequestBody(DefaultHttpClient.java:2256)
    Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Assembly trace from producer [reactor.core.publisher.FluxCreate] :
    reactor.core.publisher.Flux.create(Flux.java:650)
    io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$27(DefaultHttpClient.java:1112)
Error has been observed at the following site(s):
    *_________Flux.create ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$27(DefaultHttpClient.java:1112)
    *____Mono.flatMapMany ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1099)
    |_          Flux.next ⇢ at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.<init>(ReactorExecutionFlowImpl.java:50)
    |_          Flux.from ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1134)
    |_       Flux.timeout ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1144)
    |_ Flux.onErrorResume ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchangeImpl(DefaultHttpClient.java:1145)
    *__________Flux.error ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$30(DefaultHttpClient.java:1149)
    *______Flux.switchMap ⇢ at io.micronaut.http.client.netty.DefaultHttpClient.exchange(DefaultHttpClient.java:824)
Original Stack Trace:
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.makeErrorFromRequestBody(DefaultHttpClient.java:2256)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.forwardResponseToPromise(DefaultHttpClient.java:2210)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.channelReadInstrumented(DefaultHttpClient.java:2180)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.channelReadInstrumented(DefaultHttpClient.java:2148)
        at io.micronaut.http.client.netty.SimpleChannelInboundHandlerInstrumented.channelRead0(SimpleChannelInboundHandlerInstrumented.java:46)
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:289)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:104)
        at reactor.core.publisher.Flux.blockFirst(Flux.java:2766)
        at io.micronaut.http.client.netty.DefaultHttpClient$1.exchange(DefaultHttpClient.java:566)
        at io.micronaut.http.client.netty.DefaultHttpClient$1.retrieve(DefaultHttpClient.java:574)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.lambda$handleSynchronous$3(HttpClientIntroductionAdvice.java:241)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleBlockingCall(HttpClientIntroductionAdvice.java:619)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleSynchronous(HttpClientIntroductionAdvice.java:240)
        at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.intercept(HttpClientIntroductionAdvice.java:194)
        at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:143)
        at io.micronaut.validation.ValidatingInterceptor.validateReturnMicronautValidator(ValidatingInterceptor.java:153)
        at io.micronaut.validation.ValidatingInterceptor.intercept(ValidatingInterceptor.java:139)
        at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:143)
        at com.example.openapi.api.DefaultApi2$Intercepted.fetchData(Unknown Source)
        at com.micronaut.bug.controller.MyEntityControllerTest.myTest(MyEntityControllerTest.java:20)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at io.micronaut.test.extensions.junit5.MicronautJunit5Extension$2.proceed(MicronautJunit5Extension.java:142)
        at io.micronaut.test.extensions.AbstractMicronautExtension.interceptEach(AbstractMicronautExtension.java:162)
        at io.micronaut.test.extensions.AbstractMicronautExtension.interceptTest(AbstractMicronautExtension.java:119)
        at io.micronaut.test.extensions.junit5.MicronautJunit5Extension.interceptTestMethod(MicronautJunit5Extension.java:129)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

I can't believe there is no way I can describe the client's method for getting the file in the response at the moment. Is this really true?

yawkat commented 2 months ago

That error looks more like the client representing a 500, there should be an associated server error.

But yes the streaming support of the client is very limited atm, I think the best way is a publisher.

altro3 commented 2 months ago

Ok, for fast fix, I think, we can replace InputSream to ByteBuffer<?> - I checked it locally - all works fine. About Publisher: if user want, he can enable reactive mode and return type in client will be Mono<ByteBuffer<?>>.

About streaming support: it is different task for core, I think

altro3 commented 2 months ago

@theobisproject check it, please, now all must be ok

theobisproject commented 2 months ago

@altro3 For files smaller than the allowed content length this works great.

For larger files I still see the error shown here. Unsure if the openapi generator can do anything there or if this is somthing fore the core project. From my point of view there should be a chunking in place for requests larger than the allowed content length.

altro3 commented 2 months ago

@theobisproject you can up max content length in client configuration. Set max-content-length property for it. But maximal value is Integer.MAX_VALUE ~ 2_147_483_647 bytes ~ 2 Gb

altro3 commented 2 months ago

@theobisproject this is about files much than 2Gb: https://github.com/micronaut-projects/micronaut-core/issues/975

theobisproject commented 2 months ago

Yes this is the same problem. As described the API will provide files larger than 10GB. I will see what I can do :smile: