ballerina-platform / ballerina-library

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

[Proposal] OpenAPI Example Annotation #6730

Closed TharmiganK closed 3 months ago

TharmiganK commented 4 months ago

Summary

This proposal aims to introduce example annotations in the OpenAPI tool. This feature will enable rendering example schemas in the generated OpenAPI specification.

Goals

Non-Goals

Motivation

The OpenAPI specification allows adding examples to parameters, properties and objects. These examples can be read by the tools and the libraries that process the OpenAPI specification. For example, the Ballerina OpenAPI tool can use the examples to generate the mock client or server.

Note: Once this proposed annotations are introduced we need to adjust the mock client generation and we have a way to represent the examples in the OpenAPI spec to the generated service/client.

Description

Proposed design

The examples can be specified in the following two ways in the OpenAPI specification:

  1. The example schema, which represents a single example

    components:
      schemas:
        User:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
          example:
            id: 1
            name: Jessica Smith
  2. The examples schema, which represents multiple examples with distinct key values

    components:
      schemas:
        User:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
          examples:
              Jessica:   # Example 1
                value:
                  id: 10
                  name: Jessica Smith
              Ron:       # Example 2
                value:
                  id: 11
                  name: Ron Stewart

The above scenarios will be addressed via two different annotations in the Ballerina OpenAPI tool:

  1. The @openapi:Example annotation

    @openapi:Example {
      value: {
        id: 10,
        name: "Jessica Smith"
      }
    }
    type User record {
      int id;
      string name
    }
  2. The @openapi:Examples annotation

    @openapi:Examples {
      Jessica: { // Example 1
        value: {
           id: 10,
           name: "Jessica Smith"
        }
      },
      Ron: { // Example 2
        value: {
           id: 11,
           name: "Ron Stewart"
        }
      } 
    }
    type User record {
      int id;
      string name
    }

The example and examples schemas can be specified in objects, individual properties and operation parameters. This is supported by enabling the ballerina annotation on the Ballerina types, record fields and resource function parameter.

Note:

  • The example annotations are not allowed on the request parameter when it has a union type which cannot represented by a single media-type. In that case, it is not possible to infer the corresponding media-type of the example.
  • In addition to that, the example annotations are not supported in the response return type since it can have multiple status code response with multiple media-types.

The types can be imported from external ballerina packages and the examples from these annotations should be accessed in the compiler plugin. For that requirement, the annotations are designed as constant annotations.

The proposed annotation definitions look like this:

type ExampleValue record {|
    anydata value;
|};

type ExampleValues record {|
    ExampleValue...;
|};

const annotation ExampleValue Example on type, parameter, record field;

const annotation ExampleValues Examples on paramete;

The examples schemas will be added as follows:

Annotation attachment point Behaviour
type Examples will be added to the type schema
parameter (request payload) Examples will be added to the media-type field of the request body
parameter (other) Examples will be added to the parameter schema
record field Examples will be added to the corresponding property schema

Compiler validation

Examples

  1. Parameter example:

    Ballerina representation:

    resource function get path(
      @openapi:Example{value: "approved"} 
      "approved"|"pending"|"closed"|"new" status) {}

    Generated Schema:

    parameters:
     - in: query
       name: status
       schema:
         type: string
         enum: [approved, pending, closed, new]
         example: approved
  2. Request body example with inline object schema:

    Ballerina representation:

    resource function post users(
      @openapi:Example {
         value: {
            id: 10,
            name: Jessica Smith
         }
      }
      record{int id; string name;} payload) returns http:Ok {}

    Generated Schema:

    paths:
      /users:
        post:
          summary: Adds a new user
          requestBody:
            content:
              application/json:
                schema:      # Request body contents
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
                  example:   # Sample object
                    id: 10
                    name: Jessica Smith
          responses:
            '200':
              description: OK   
  3. Request body example with reference:

    Ballerina representation:

    resource function post users(
      @openapi:Example {
         value: {
            id: 10,
            name: Jessica Smith
         }
      } User payload) returns http:Ok {}

    Generated Schema:

    paths:
      /users:
        post:
          summary: Adds a new user
          requestBody:
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/User'
                example:
                  id: 10
                  name: Jessica Smith
          responses:
            '200':
              description: OK   
  4. Property level example:

    Ballerina representation:

    type User record {
       @openapi:Example { value: 1 }
       int id;
       @openapi:Example { value: "Jessica Smith" }
       string name;
    }

    Generated Schema:

    components:
      schemas:
        User:    # Schema name
          type: object
          properties:
            id:
              type: integer
              format: int64
              example: 1          # Property example
            name:
              type: string
              example: Jessica Smith  # Property example
  5. Object level example:

    Ballerina representation:

    @openapi:Example {
       value: {
          id: 1,
          name: "Jessica Smith"
       }
    }
    type User record {
       int id;
       string name;
    }

    Generated Schema:

    components:
      schemas:
        User:       # Schema name
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
          example:   # Object-level example
            id: 1
            name: Jessica Smith
  6. Array example:

    Ballerina representation:

    @openapi:Example {
       value: [
          {
             id: 10,
             name: "Jessica Smith"
          },
          {
             id:20,
             name: "Ron Stewart"
          }
       ]
    }
    type ArrayOfUsers User[];

    Generated Schema:

    components:
      schemas:
        ArrayOfUsers:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              name:
                type: string
          example:
            - id: 10
              name: Jessica Smith
            - id: 20
              name: Ron Stewart

References

Future Works

TharmiganK commented 3 months ago

Implemented via https://github.com/ballerina-platform/openapi-tools/pull/1740