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
79 stars 93 forks source link

Non-annotated argument is included in schema even when another argument is annotated with `@Body` #1651

Closed rorueda closed 1 month ago

rorueda commented 1 month ago

Feature description

I expect a method argument annotated with @Body to take precedence over other non-annotated arguments.

I'm creating this as a feature request because it's not that important and the workaround is trivial.

void "test @Body method argument has precedence over other arguments"() {
        given:
        buildBeanDefinition('test.MyBean', '''
package test;

import java.util.Optional;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.bind.binders.TypedRequestArgumentBinder;
import jakarta.inject.Singleton;

@Introspected
record SimpleBody(String value) {}

@Introspected
record FilterProvidedArgument(String id) {}

@Singleton
class TestArgumentBinder implements TypedRequestArgumentBinder<FilterProvidedArgument> {
  @Override
  public Argument<FilterProvidedArgument> argumentType() {
    return Argument.of(FilterProvidedArgument.class);
  }

  @Override
  public BindingResult<FilterProvidedArgument> bind(ArgumentConversionContext<FilterProvidedArgument> context, HttpRequest<?> source) {
    return () -> Optional.of(new FilterProvidedArgument("my-id"));
  }
}

@Controller("/test")
class TestController {
    @Post
    HttpResponse<SimpleBody> save(@Body SimpleBody body, FilterProvidedArgument filterProvidedArgument) {
        return HttpResponse.ok(body);
    }
}

@Singleton
class MyBean {}
''')
        when: "The OpenAPI is retrieved"
        OpenAPI openAPI = Utils.testReference

        then: "the state is correct"
        openAPI != null

        when:
        Operation operation = openAPI.paths.get("/test").post

        then:
        operation

        and:
        operation.requestBody
        operation.requestBody.content
        operation.requestBody.content.size() == 1
        operation.requestBody.content."application/json"
        operation.requestBody.content."application/json".schema
        operation.requestBody.content."application/json".schema.$ref == "#/components/schemas/SimpleBody"
    }
altro3 commented 1 month ago

Unfortunately, it is not possible to determine 100% whether a parameter is part of the request body or added using other tools. You can test this by changing your endpoint to this:

    @Post
    HttpResponse<SimpleBody> save(@Body SimpleBody body, Integer test, String id, FilterProvidedArgument filterProvidedArgument) {
...

And try to send request:

{
    "value": "PERS",
    "test": 12,
    "id": "sdsdsdsd"
}

You will see that all parameters of the request body are substituted into the required method parameters: изображение

Therefore, the only way to skip such parameters is to add the @Parameter(hidden = true) annotation

    @Post
    HttpResponse<SimpleBody> save(@Body SimpleBody body, @Parameter(hidden = true) FilterProvidedArgument filterProvidedArgument) {
        return HttpResponse.ok(body);
    }
rorueda commented 1 month ago

Thanks, @altro3.

I've opened this only because I wasn't expecting this to be the behavior of micronaut-core. I guess this behavior won't ever be changed, so I'm closing it.