Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aligning error mapping with spec generation for new HTTP error structure #5135

Closed
lnash94 opened this issue May 21, 2023 · 3 comments
Closed

Comments

@lnash94
Copy link
Member

lnash94 commented May 21, 2023

Description:
$Subject
Reference Issue : #4101

Describe your problem(s)

Describe your solution(s)

Related Issues (optional):

Suggested Labels (optional):

Suggested Assignees (optional):

@lnash94 lnash94 transferred this issue from ballerina-platform/openapi-tools Nov 1, 2023
@TharmiganK
Copy link
Contributor

TharmiganK commented Jan 29, 2024

The ballerina/http module has introduced a new error structure to represent status code errors. This has been introduced with 2.7.0 and has moved to a separate module httpscerr in 2.9.0.

Sample Ballerina service:

import ballerina/http;
import ballerina/time;
import ballerina/http.httpscerr;

type Album readonly & record {|
    string title;
    string artist;
|};

table<Album> key(title) albums = table [
    {title: "Blue Train", artist: "John Coltrane"},
    {title: "Jeru", artist: "Gerry Mulligan"}
];

type ErrorBody record {
    string timeStamp;
    string message;
};

type ErrorHeaders record {|
    string req\-id;
    string error\-code;
|};

type ErrorDetail record {
    *httpscerr:ErrorDetail;
    ErrorBody body;
    ErrorHeaders headers;
};

type Error error<ErrorDetail>;

type AlbumNotFound httpscerr:NotFoundError & Error;

type DefaultError httpscerr:DefaultStatusCodeError & Error;

service / on new http:Listener(9090) {

    resource function get albums/[string title]() returns Album|AlbumNotFound {
        if albums.hasKey(title) {
            return albums.get(title);
        }
        return error AlbumNotFound("Album not found", body = {
            timeStamp: time:utcToString(time:utcNow()),
            message: "Album not found for title: " + title
        }, headers = {
            req\-id: "req001",
            error\-code: "err001"
        });
    }

    resource function get albums(string artist) returns Album[]|DefaultError {
        Album[] selectedAlbums = from Album album in albums
            where album.artist == artist
            select album;
        if selectedAlbums.length() == 0 {
            return error DefaultError("No albums found for artist: " + artist, body = {
                timeStamp: time:utcToString(time:utcNow()),
                message: "No albums found for artist: " + artist
            }, headers = {
                req\-id: "req001",
                error\-code: "err002"
            },
            statusCode = 404);
        }
        return selectedAlbums;
    }
}

Expected OpenAPI specification:

openapi: 3.0.1
info:
  title: Main Openapi Yaml
  version: 0.1.0
servers:
- url: "{server}:{port}/"
  variables:
    server:
      default: http://localhost
    port:
      default: "9090"
paths:
  /albums/{title}:
    get:
      operationId: getAlbumsTitle
      parameters:
      - name: title
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Ok
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Album'
        "404":
          description: NotFound
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        "400":
          description: BadRequest
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorPayload'
  /albums:
    get:
      operationId: getAlbums
      parameters:
      - name: artist
        in: query
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Ok
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Album'
        "400":
          description: BadRequest
          headers:
            req-id:
              schema:
                type: string
            error-code:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorPayload'
        "default":
          description: Default error response
          headers:
            req-id:
              schema:
                type: string
            error-code:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
components:
  schemas:
    Album:
      required:
      - artist
      - title
      type: object
      properties:
        title:
          type: string
        artist:
          type: string
      additionalProperties: false
    ErrorBody:
      required:
      - timestamp
      - message
      type: object
      properties:
        timestamp:
          type: string
        message:
          type: string
    ErrorPayload:
      required:
      - message
      - method
      - path
      - reason
      - status
      - timestamp
      type: object
      properties:
        timestamp:
          type: string
        status:
          type: integer
          format: int64
        reason:
          type: string
        message:
          type: string
        path:
          type: string
        method:
          type: string

@TharmiganK
Copy link
Contributor

TharmiganK commented Jan 29, 2024

Proposal: OAS mapping for HTTP status code errors

Summary

This proposal is to add the relevant OAS response mapping for HTTP status code error return types.

Goals

  • Align the OpenAPI specification with the current implementation of the HTTP status code errors.
  • Add the default status code response mapping for the relaxed error handling with httpscerr:DefaultStatusCodeError.
  • Generate the response content and headers based on the ErrorDetail field of the status code error.

Motivation

When writing HTTP services it is common to deal with errors and return the appropriate HTTP status codes. The Ballerina HTTP library provides a set of HTTP status code errors that can be returned from the service. So at runtime, the HTTP status code errors are converted to the relevant HTTP status code responses.

import ballerina/http;
import ballerina/time;
import ballerina/http.httpscerr;

type Album readonly & record {|
    string title;
    string artist;
|};

table<Album> key(title) albums = table [
    {title: "Blue Train", artist: "John Coltrane"},
    {title: "Jeru", artist: "Gerry Mulligan"}
];

type ErrorBody record {
    string timeStamp;
    string message;
};

type ErrorHeaders record {|
    string req\-id;
    string error\-code;
|};

type ErrorDetail record {
    *httpscerr:ErrorDetail;
    ErrorBody body;
    ErrorHeaders headers;
};

type Error error<ErrorDetail>;

type AlbumNotFound httpscerr:NotFoundError & Error;

service / on new http:Listener(9090) {

    resource function get albums/[string title]() returns Album|AlbumNotFound {
        if albums.hasKey(title) {
            return albums.get(title);
        }
        return error AlbumNotFound("Album not found", body = {
            timeStamp: time:utcToString(time:utcNow()),
            message: "Album not found for title: " + title
        }, headers = {
            req\-id: "req001",
            error\-code: "err001"
        });
    }
}

In addition to this strict mode, where the user should create different subtypes of the HTTP status code errors, the Ballerina HTTP library provides a relaxed mode where the users can return a subtype of httpscerr:DefaultStatusCodeError with the relevant status code as an error detail. This is useful when all the error responses have a similar structure and the user does not want to create different subtypes of the HTTP status code errors.

...

type DefaultError httpscerr:DefaultStatusCodeError & Error;

service / on new http:Listener(9090) {

    ...

    resource function get albums(string artist) returns Album[]|DefaultError {
        Album[] selectedAlbums = from Album album in albums
            where album.artist == artist
            select album;
        if selectedAlbums.length() == 0 {
            return error DefaultError("No albums found for artist: " + artist, body = {
                timeStamp: time:utcToString(time:utcNow()),
                message: "No albums found for artist: " + artist
            }, headers = {
                req\-id: "req001",
                error\-code: "err002"
            },
            statusCode = 404);
        }
        return selectedAlbums;
    }
}

The current OpenAPI tool generates the following response mapping for all the error types.

"500":
  description: InternalServerError
  content:
    application/json: {}

Since the HTTP status code errors returned from the resource are converted to relevant HTTP status code responses at runtime, the generated OpenAPI specification should be updated with the relevant response mapping.

Description

As mentioned above, the purpose of this proposal is to reflect the current implementation of the HTTP status code errors in the OpenAPI specification generated by the tool.

Strict mode with subtypes of HTTP status code errors

The relevant response mapping can be generated from the following details:

Detail Field Description
The HTTP status code error type - This defines the HTTP status code of the response.
The ErrorDetail field of the HTTP status code error body This defines the response content.
The ErrorDetail field of the HTTP status code error headers This defines the response headers. The headers are only added when this field type is a record.

Example:

Ballerina Code OpenAPI
...

type AlbumNotFound 
     httpscerr:NotFoundError & Error;

service / on new http:Listener(9090) {

    resource function get 
          albums/[string title]()
     returns Album|AlbumNotFound {
        ...
    }
}
responses:
  "200":
    description: Ok
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/Album"
  # The response mapping for the AlbumNotFound error
  "404":
    description: NotFound
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorBody"
  "400": # This is for the databinding errors
    description: BadRequest
    headers:
      req\-id:
        schema:
          type: string
      error\-code:
        schema:
          type: string
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorPayload"

Relaxed mode with httpscerr:DefaultStatusCodeError

The subtypes of httpscerr:DefaultStatusCodeError are mapped to the default status code response mapping in the OpenAPI specification. The content and headers of the response are generated from the ErrorDetail field of the HTTP status code error.

Example:

BallerinaCode OpenAPI
...

type DefaultError 
   httpscerr:DefaultStatusCodeError & Error;

service / on new http:Listener(9090) {

    ...

    resource function 
          get albums(string artist)
    returns Album[]|DefaultError {
        ...
    }
}
responses:
  "200":
    description: Ok
    content:
      application/json:
        schema:
          type: array
          items:
            $ref: "#/components/schemas/Album"
  "400": # This is for the databinding errors
    description: BadRequest
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorPayload"
  # The response mapping for the DefaultError
  "default":
    description: Default error response
    headers:
      req\-id:
        schema:
          type: string
      error\-code:
        schema:
          type: string
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorBody"

@TharmiganK
Copy link
Contributor

Implemented via ballerina-platform/openapi-tools#1604

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Status: Done
Development

No branches or pull requests

2 participants