OpenAPI Exporter

Introduction

OpenAPI is used as a programming language-agnostic interface description for REST APIs. Given ALFA’s service defines an interface, the ALFA OpenAPI Exporter was developed as a PoC to validate feature coverage from ALFA to OpenAPI.

Initial conclusions are that ALFA is more than capable of supporting all of OpenAPI definitions. There are however concerns how some complex modelling features in ALFA will be expressed in OpenAPI.

Please note this exporter is highly experimental at this stage and will require addtional work to ensure better compatility and feature coverage of OpenAPI.

Usage Steps

  • Similar to generating Java and CSharp, Python is generated using the following commandline.
alfa -c -e openapi -o generated/openapi src

OpenAPI Model expressed in ALFA

The generated OpenAPI model is in fact, expressed in ALFA. The Exporter converts the ALFA model to a structure alfa.openapi.model.Spec, and generates YAML - the result is a OpenAPI definition.

namespace alfa.openapi.model

record Spec {
    openapi : string
    info : Info
    servers : list< Server >?
    components : Components?
    paths : map< string, PathItem >
    security : list< SecurityRequirement >?
    tags : list< Tag >?
    externalDocs : ExternalDocumentation?
}

record Info {
    title : string
    description : string?
    termsOfService : string?
    contact : Contact?
    license : License?
    version : string
}

record Tag {
    name : string
    description : string?
    externalDocs : ExternalDocumentation?
}

record SecurityRequirement {
    scheme : SecurityScheme
    scopes : list< string >
}

enum SecurityScheme {
    apiKey http oauth2 openIdConnect
}

record Components {
    schemas : map< string, SchemaDef >
}

record ExternalDocumentation {
    description : string?
    url : uri
}

record PathItem {
    ref : string?
    summary : string?
    description : string?
    get : Operation?
    put : Operation?
    post : Operation?
    delete : Operation?
    options : Operation?
    head : Operation?
    patch : Operation?
    trace : Operation?
    servers : list< Server >?
    parameters : list< union< parameter:Parameter, reference:Reference > >?
}

enum ParameterType {
    path query headers cookies
}

record Parameter {
    name : string
    in : ParameterType
    description : string?
    required : boolean
    deprecated : boolean?
    allowEmptyValue : boolean?
    schema : SchemaObject
}

trait SchemaObject {
}

record SchemaDef includes SchemaObject {
    type : string
    description : string?

    // Primitive array type
    items : SchemaObject?

    enum : list< string >?

    minimum : int?
    maximum : int?

    properties : map< string, SchemaObject >?

    additionalProperties : map< string, string >?
}

record Reference includes SchemaObject {
    `$ref` : string
}

record Operation {
    tags : list< string >?
    summary : string?
    description : string?
    externalDocs : ExternalDocumentation?
    operationId : string?
    parameters : list< Parameter >?
    requestBody : union< requestBody: RequestBody, reference:Reference >?
    responses : map< Code: string, Response >
    callbacks : map< string, union< callback: Callback, reference:Reference > >?
    deprecated : boolean?
    security : map< SecurityRequirement : string, list< string > >?
    servers : list< Server >?
}

record Callback {
    paths : map< string, string >
}

record Response {
    description : string
    headers : map< string, union< header:Header, reference:Reference > >?
    content : map< string, MediaType >?
    links : map< string, union< link:Link, reference:Reference > >?
}

record MediaType {
    schema : SchemaObject
}

record Header {
    description : string?
    // REVIEW
}

record RequestBody {
    description : string?
    content : map< string, MediaType >
    required : boolean?
}

record Link {
    operationRef : string?
    operationId : string
    parameters : map< string, string?> // REVIEW
    requestBody : string? // REVIEW
    description : string?
    server : Server
}

record Server {
    url : uri
    description : string?
    variables : map< string, ServerVariableObject >?
}

record ServerVariableObject {
    enum : list< string >?
    default : string
    description : string?
}

record License {
    name : string
    url : uri?
}

record Contact {
   name : string
   email : string
   url : uri
}

Example

Given the following data model below, the OpenAPI Exporter will generate an OpenAPI YAML definition.

The OpenAPI Exporter makes use of ALFA annotation support for the author to include meta information only required by the OpenAPI generator.

Note that the service definition can also be used for all other intended usages of a service, and not limited to OpenAPI usage.

@alfa.openapi.Info(
    title="Swagger Petstore",
    description="This is a sample Petstore server",
    version="1.0.0" )
namespace Petstore

enum PetCategory {
   Cat Dog Reptile
}
record Pet {
    name : string
    category : PetCategory
}
enum PetStatus {
    Available Pending Sold
}
@alfa.openapi.Path(name="/pet")
service PetService
{
    # Add a new pet to the store
    @alfa.openapi.Post(tags=["pet"])
    addPet( p : Pet ) : void

    # Update an existing pet
    @alfa.openapi.Put(tags=["pet"])
    updatePet( p : Pet ) : void

    # Finds pets by status
    @alfa.openapi.Get(path="findByStatus", tags=["pet"])
    findByStatus( statuses : list< PetStatus > ) : list< Pet >

    # Finds pets by tags
    @alfa.openapi.Get(path="findByStatus", tags=["pet"])
    findByTags( statuses : list< string > ) : list< Pet >

    # Find pet by id
    @alfa.openapi.Get(path="/{petId}", tags=["pet"])
    getPetById( petId : long ) : Pet
}

Generated OpenAPI YAML definition:

openapi: "3.0.0"
info:
  title: "Swagger Petstore"
  version: "1.0.0"
paths:
  /petfindByStatus:
    get:
      tags:
      - "pet"
      summary: "Finds pets by tags"
      operationId: "findByTags"
      parameters:
      - name: "statuses"
        in: "query"
        required: true
        schema:
          type: "array"
          items:
            type: "string"
      responses:
        200:
          description: "Result of findByTags"
          content:
            application/json:
              schema:
                type: "array"
                items:
                  $ref: "Petstore.Pet"
  /pet/{petId}:
    get:
      tags:
      - "pet"
      summary: "Find pet by id"
      operationId: "getPetById"
      parameters:
      - name: "petId"
        in: "path"
        required: true
        schema:
          type: "integer"
      responses:
        200:
          description: "Result of getPetById"
          content:
            application/json:
              schema:
                $ref: "Petstore.Pet"
  /pet:
    put:
      tags:
      - "pet"
      summary: "Update an existing pet"
      operationId: "updatePet"
      parameters:
      - name: "p"
        in: "query"
        required: true
        schema:
          $ref: "Petstore.Pet"
      responses:
        204:
          description: "Result of updatePet"
components:
  schemas:
    Petstore.PetCategory:
      type: "string"
      enum:
      - "Cat"
      - "Dog"
      - "Reptile"
    Petstore.Pet:
      type: "object"
      properties:
        name:
          type: "string"
        category:
          $ref: "Petstore.PetCategory"
    Petstore.PetStatus:
      type: "string"
      enum:
      - "Available"
      - "Pending"
      - "Sold"