softwaremill / tapir

Rapid development of self-documenting APIs
https://tapir.softwaremill.com
Apache License 2.0
1.36k stars 418 forks source link

[BUG] Endpoints examples ignored at AsyncAPI docs generation. #933

Closed strobe closed 3 years ago

strobe commented 3 years ago

Tapir version: *** 0.17.1

Scala version: *** 2.12.12

Describe the bug

Currently, it's possible to specify custom example/s for WebSockets endpoints but that will be ignored during AsyncApi documentation generation.

For instance:

sealed trait WsMessage
case object SomeMessage extends WsMessage

sealed trait WsResponse
case object SomeResponse extends WsResponse
case object SomeResponse2 extends WsResponse

endpoint.get
  .in("ws")
  .info(info)
  .out(webSocketBody[WsMessage, CodecFormat.Json, WsResponse, CodecFormat.Json](AkkaStreams)
    .examples(
      List(
        Example(Flow.fromFunction((in: WsMessage) => SomeResponse()), name = Some("first"), summary = Some("r1")),
        Example(Flow.fromFunction((in: WsMessage) => SomeResponse2()), name = Some("second"), summary = Some("r2")
    )
  )
)

after AsyncApi generation via 'AsyncAPIInterpreter.toAsyncAPI' both examples will be ignored, seems a single example that appears in the result generated directly from WsResponse objects/classes.

What's required to do in order to implement that? Where implementation for this should be added?

adamw commented 3 years ago

@strobe can you look at the PR https://github.com/softwaremill/tapir/pull/973? It should fix the problem - examples where indeed ignored. To support them, I've added . requestsExample and .responsesExample as these need to be specified separately (there's no way to inspect a Flow for examples).

However, when I paste the generated YAML into the async api playground (https://playground.asyncapi.io/), I get verification errors. I think this must be a problem with the playground, though, as as far as I can see the generated yaml matches the spec.

strobe commented 3 years ago

@adamw thanks, I will try to test that in the next 2 days and will write feedback here.

I get verification errors.

seems I got those with previous versions too (with v0.17.1)

adamw commented 3 years ago

This is now released in 0.17.8 - let me know how it works :)

strobe commented 3 years ago

I tested it - works for me.

@adamw Regarding those "async api playground" errors I found that at least one problem is related to wrong indentation on 'examples' and 'contentType'.

as result of docs generation I getting something like this:

asyncapi: 2.0.0
info:
  title: Account Service
  version: 1.0.0
  description: This service is in charge of processing user signups
channels:
  user/signedup:
    subscribe:
      message:
        $ref: '#/components/messages/UserSignedUp'
components:
  messages:
    UserSignedUp:
      payload:
        type: object
        properties:
          displayName:
            type: string
            description: Name of the user
          email:
            type: string
            format: email
            description: Email of the use
      contentType: application/json   
      examples:    
        - displayName: "name1"
          email: "email1@example.com"
        - displayName: "name2"
          email: "email2@example.com"
        - value: ''
          command: LogMessage

but it should be:

asyncapi: 2.0.0
info:
  title: Account Service
  version: 1.0.0
  description: This service is in charge of processing user signups
channels:
  user/signedup:
    subscribe:
      message:
        $ref: '#/components/messages/UserSignedUp'
components:
  messages:
    UserSignedUp:
      payload:
        type: object
        properties:
          displayName:
            type: string
            description: Name of the user
          email:
            type: string
            format: email
            description: Email of the use
        contentType: application/json   
        examples:    
          - displayName: "name1"
            email: "email1@example.com"
          - displayName: "name2"
            email: "email2@example.com"
          - value: ''
            command: LogMessage
strobe commented 3 years ago

actually not sure, seems the spec doesn't has much info about this. Only one example from spec that suitable looks like: 'Model with Example'

type: object
properties:
  id:
    type: integer
    format: int64
  name:
    type: string
required:
- name
example:
  name: Puma
  id: 1

'example' here at same level as 'properties'

adamw commented 3 years ago

According to: https://www.asyncapi.com/docs/specifications/2.0.0#messageObject, payload and examples are on the same level. This is reflected in the model: https://github.com/softwaremill/tapir/blob/master/apispec/asyncapi-model/src/main/scala/sttp/tapir/asyncapi/AsyncAPI.scala#L99-L114

However, schemas can also have examples (https://www.asyncapi.com/docs/specifications/2.0.0#schemaObject, https://github.com/softwaremill/tapir/blob/master/apispec/apispec-model/src/main/scala/sttp/tapir/apispec/Schema.scala#L19). These could be two different examples.

Currently tapir doesn't support schema-level examples, though we probably will in the future, when we add an example to tapir's sttp.tapir.Schema object.

The reason for this separation is probably that a single schema can be used in different contexts (e.g. as part of different messages), so you might want to both provide example for the individual building blocks, as well as for the whole message.

strobe commented 3 years ago

thanks for the clarification, seems it's asyncAPI a parser issue.

strobe commented 3 years ago

@adamw there some new info about this issue, please check this https://github.com/asyncapi/asyncapi/issues/501#issuecomment-781345179 and this example: https://github.com/asyncapi/tck/blob/master/tests/asyncapi-2.0/Message%20Object/valid.yaml

Documentation on the AsyncApi website little outdated and the new version is here https://github.com/asyncapi/asyncapi/blob/master/versions/2.0.0/asyncapi.md#messageObject

So seems the correct way to specify examples on message object is:

components:
  messages:
    UserSignedUp:
      payload:
        type: object
        properties:
          displayName:
            type: string
            description: Name of the user
          email:
            type: string
            format: email
            description: Email of the use
      examples:  
        - payload:
          - displayName: "name1"
            email: "email1@example.com"
          - displayName: "name2"
            email: "email2@example.com"            
      contentType: application/json   

and tricky part: examples that validate against the headers or payload (schema level examples inside payload also supported)

adamw commented 3 years ago

Ah, so we need to add payload as the key under examples, right?

strobe commented 3 years ago

examples there is an array with at least two possible keys "payload" & "header", spec said: array of key/value pairs where keys MUST be either headers and/or payload so it seems to have only "payload" it's fine but it should be preceded by '-' (maybe it's an array because they wish to allow there multiply 'headers' or 'payloads' items if the payload has something like 'oneOf').

adamw commented 3 years ago

@strobe should be fixed in next 0.18.x :)