kokuwaio/micronaut-openapi-codegen

Different semantic of produces and consumes on client and controller side

sebplorenz opened this issue · 5 comments

As stated in the documentation at https://docs.micronaut.io/latest/guide/index.html#clientAnnotation @produces (as well as @consumes) has different semantics on client and server side. From the docs:

Be aware that some annotations, such as Produces and Consumes, have different semantics between server and client side usage. For example, @produces on a controller method (server side) indicates how the method’s return value is formatted, while @produces on a client indicates how the method’s parameters are formatted when sent to the server. While this may seem a little confusing, it is logical considering the different semantics between a server producing/consuming vs a client: a server consumes an argument and returns a response to the client, whereas a client consumes an argument and sends output to a server.

Therefore, the mapping of:

responses:
  "200":
    description: Ok
      content:
        application/ld+json:
          schema:
            type: object

would be expected to result in:

public interface Api {
	@io.micronaut.http.annotation.Get("/api")
	@io.micronaut.http.annotation.Produces({ "application/ld+json" })
	Object getPrivateSelfDescription();
}

public interface ApiClient {
	@io.micronaut.http.annotation.Get("/api")
	@io.micronaut.http.annotation.Consumes({ "application/ld+json" })
	Object getPrivateSelfDescription();
}

But it is currently mapped to:

public interface Api {
	@io.micronaut.http.annotation.Get("/api")
	@io.micronaut.http.annotation.Produces({ "application/ld+json" })
	Object getPrivateSelfDescription();
}

public interface ApiClient {
	@io.micronaut.http.annotation.Get("/api")
	@io.micronaut.http.annotation.Produces({ "application/ld+json" })
	Object getPrivateSelfDescription();
}

The current mapping plus the default functionality of the Micronaut HTTP client leads to a client sending the get request with an automatically created Accept: application/json header. If the server is validating the Accept Header, it answers with 406, because it can only produce ld+json.

The mapper must respect the different meanings of @produces (and @consumes) on client and server side.

I cannot reproduce that problem. When I use it exactly the same way(or maybe I overlooked a difference), the client produces the expected header (and Consumes annotation).

This spec:

 responses:
        '200':
          description: OK
          content:
            application/ld+json:
              schema:
                $ref: '#/components/schemas/EntityList'

produces the client code:

@io.micronaut.http.annotation.Post("/entities/")
@io.micronaut.http.annotation.Consumes("application/ld+json")
@io.micronaut.http.annotation.Produces({ "application/ld+json" })
io.micronaut.http.HttpResponse<Object> createEntity(@io.micronaut.http.annotation.BodyEntityVO entityVO);

The request looks as following:

DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP POST to http://localhost:1026/ngsi-ld/v1/entities/
TRACE i.m.h.client.netty.DefaultHttpClient - Content-Type: application/ld+json
TRACE i.m.h.client.netty.DefaultHttpClient - Accept: application/ld+json
TRACE i.m.h.client.netty.DefaultHttpClient - host: localhost:1026
TRACE i.m.h.client.netty.DefaultHttpClient - connection: close
TRACE i.m.h.client.netty.DefaultHttpClient - content-length: 499
TRACE i.m.h.client.netty.DefaultHttpClient - Request Body
TRACE i.m.h.client.netty.DefaultHttpClient - ----

Maybe you can spot the difference, the code is here: https://github.com/wistefan/contract-service

Btw. are we working on the same stuff?:) Your problems look very familiar.

Hi Stefan,
have a look at this code snippet generated from your example:

public interface EntitiesApi {
  ...
  @io.micronaut.http.annotation.Get("/entities/")
  @io.micronaut.http.annotation.Produces({ "application/ld+json" })
  io.micronaut.http.HttpResponse<EntityListVO> queryEntities(
  ...

If the EntitiesAPI interface is implemented by a Controller, then the annotations are correct.
But the HTTP Client should look like this:

public interface EntitiesApiClient extends EntitiesApi {
  ...
  @io.micronaut.http.annotation.Get("/entities/")
  @io.micronaut.http.annotation.Consumes({ "application/ld+json" })
  io.micronaut.http.HttpResponse<EntityListVO> queryEntities(

It should use @consumes. Which it does not if I generate the code from your example.

Can you check the Accept Header that your EntitiesApiClient is sending to the Server when doing a queryEntities? I assume it will not contain application/ld+json.

Your right. This looks like a problem with the GET requests. It worked, because I tried it with a POST.

Will be fixed with version 3.0.0

Fixed with 3.x