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

[Proposal] OpenAPI Example Annotation #6730

Closed
TharmiganK opened this issue Jul 11, 2024 · 1 comment
Closed

[Proposal] OpenAPI Example Annotation #6730

TharmiganK opened this issue Jul 11, 2024 · 1 comment

Comments

@TharmiganK
Copy link
Contributor

TharmiganK commented Jul 11, 2024

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

  • Provide a way to add example(s) for the Ballerina types and the resource function parameters
  • Provide validation for the examples with respect to the attached type
  • Generate example schemas in the OpenAPI specification with the values provided in the annotation

Non-Goals

  • Provide a way to add a file path or external link(See External Examples in Adding Examples) for a example value
  • Provide a way to automatically generate example and add it in the example annotation

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

  • Validate whether the provided example value matches the type
  • Validate the example annotation usage in the request parameter
  • Restrict the usage of both annotations: @openapi:Example/@openapi:Examples on the same parameter

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

  • Provide a way to add a file path or external link for a example value:

    This can be done by adding a union type for the annotation type. This has to be checked with the Ballerina lang team:

    type ExamplePath record {|
        string path;
    |};
    
    type ExampleReference record {|
        string ref;
    |};
    
    const annotation ExampleValue|ExamplePath|ExampleReference Example on type, parameter, record field;
  • Provide a way to automatically generate example and add it in the example annotation:

    When the type is complex, it is not practical to add an example manually. So it will be better to generate a sample example using a code action or generate one with Ballerina AI tool.
    In addition to that, Ballerina tooling supports creating records from JSON examples. We could use that to automatically inject the example annotation when a record is created from JSON.

@TharmiganK
Copy link
Contributor Author

Implemented via ballerina-platform/openapi-tools#1740

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

3 participants