ballerina-platform / ballerina-library

The Ballerina Library
https://ballerina.io/learn/api-docs/ballerina/
Apache License 2.0
136 stars 64 forks source link

Support server sent events in the openapi #6785

Open dilanSachi opened 3 months ago

dilanSachi commented 3 months ago

Description: In this task, we need to support,

dilanSachi commented 3 months ago

There is no standard way of depicting SSEs in openapi-spec [1]. However, there are some approaches that we can look on (Suggested by gemini).

Use produces or content to indicate text/event-stream as a potential media type for the response. However, this doesn't explicitly convey the SSE nature of the response.

Create custom extensions to the OpenAPI specification to add specific properties for SSE. This requires agreement on the extensions within your organization or community. (This does not work for us)

Clearly document the SSE nature of the endpoint in the operation's summary and description. While not ideal, it provides some information to API consumers. (This is not useful for the tools)

Include example responses in the examples or content property to demonstrate the SSE format. This can help clarify the expected output. (This is not useful for the tools)

[1] https://github.com/OAI/OpenAPI-Specification/issues/396

dilanSachi commented 3 months ago

For the first suggestion above, we can do something like this [1].

/events:
  get:
    responses:
      200:
        content:
          text/event-stream:
            schema:
              type: array # eventually also stream as new type, to mean unboundness, or a new parameter of array
              format: chunked # or whatever other keyword to mean chunks separated by empty lines
                              # another use could be jsonlines, one line per JSON object, with no [ , ]
              items:
                type: object
                format: text # assuming this means "key: value", one per line, for each key 
                             # or we may introduce yaml !! 🥇
                required:
                  - id # not strictly required by SSE, but it's an example
                  - event # not strictly required by SSE, but it's an example
                  - data
                properties: # also possible to model with oneOf+discriminator
                  id:
                    type: integer
                    description: my event id
                  event:
                    type: string
                    description: my event type
                  data:
                    type: object # object in case we have a complex structure, otherwise string, integer, ..., as usual
                    format: json # JSON is the default, but we may have different format as proposed above for the array
                    required:
                      - a
                      - b
                    properties: # as usual
                      a: # ...
                      b: # ...
                      c: # ...

[1] https://github.com/OAI/OpenAPI-Specification/issues/396#issuecomment-492287488

dilanSachi commented 3 months ago

We will first support the client generation side. When the input yaml is as below,

paths:
  /events:
    get:
      responses:
        200:
          description: desc
          content:
            text/event-stream:
              schema:
                type: string

The generated client will be,

    resource isolated function get events(map<string|string[]> headers = {}) returns stream<http:SseEvent, error?>|error {
        string resourcePath = string `/events`;
        return self.clientEp->get(resourcePath, headers);
    }

or

    remote isolated function getEvent(map<string|string[]> headers = {}) returns stream<http:SseEvent, error?>|error {
        string resourcePath = string `/events`;
        return self.clientEp->get(resourcePath, headers);
    }

In the case of status code binding,

resource isolated function get events(map<string|string[]> headers = {}, typedesc<OkString> targetType = <>) returns targetType|error;

public type OkString record {|
    *http:Ok;
    stream<http:SseEvent, error?> body;
    map<string|string[]> headers;
|};
TharmiganK commented 3 months ago

With the current SSE implementation, we are not supporting status code response binding yet. So we can ignore that for now. So we cannot generate status code binding client for SSE. We could ignore that specific resource and give a warning for now.

There can be additional content-types which translates to union return type. But I am not sure we are handling that union part in the current SSE implementation. Adding @MohamedSabthar

paths:
  /events:
    get:
      responses:
        200:
          description: desc
          content:
            text/event-stream:
              ...
            application/json:
              ...
resource isolated function get events(map<string|string[]> headers = {}) returns stream<http:SseEvent, error?>|json|error {
    string resourcePath = string `/events`;
    return self.clientEp->get(resourcePath, headers);
}
dilanSachi commented 3 months ago

Had an offline discussion with @MohamedSabthar. As per him, it is not currently supported. In that case, I think we will have to support only single type returns.

MohamedSabthar commented 3 months ago
resource isolated function get events(map<string|string[]> headers = {}) returns stream<http:SseEvent, error?>|json|error {
    string resourcePath = string `/events`;
    return self.clientEp->get(resourcePath, headers);
}

+1, we are not handling this. In the case of a union of stream and anydata types being present, we return an error.