In a previous post I have explained how to develop a very simple API server.

Without the associated documentation, the API will be useless. Let’s see how we can use swagger-ui in this project to generate a beautiful documentation.

Note I’m blogging and experimenting, of course, in the “real” life, it’s a lot better to code the API interface before implementing the middleware.

About Swagger

Swagger is a framework. On top of the swagger project is composed of several tools.

The entry point is to write the API interface using the Swagger Formal Specification. I will the use the swagger-ui to display the documentation. The swagger-ui can be modified and recompiled, but I won’t do it (as I don’t want to play with nodejs). Instead I will rely on the “dist” part which can be used “as-is”

Defining the API interface with Swagger

Header and specification version:

Swagger comes with an editor which can be used online.

I will use swagger spec 2.0, as I don’t see any good reason not to do so. Moreover, I will describe the API using the YAML format instead of the JSON format to be human-friendly.

Indeed, in my YAML skeleton the header of my specs will then look like this:

swagger: '2.0'
info:
  version: 1.0.0
    title: 'Very Simple IAAS'

The node creation: a POST method

Let’s document the Node creation (as it is the method that we have implemented before).

The node creation is a POST method, that produces a JSON in output with the request ID of the node created.

The responses code may be:

  • 202 : if the request has been taken in account
  • 400 : when the request is not formatted correctly
  • 500 : if any unhanldled exception occurred
  • 502 : if the backend is not accessible (either the RPC server or the backend)

So far, the YAML spec will look like:

paths:
  /v1/nodes:
    post:
      description: Create a node
      produces:
        - application/json
      responses:
        202:
          description: A request ID.
        400:
          description: |
            When the request is malformated or when mandatory arguments are missing            
        500:
          desctiption: Unhandled error
        502:
          description: Execution backend not available

So far so good, let’s continue with the input payload. The payload will be formatted in JSON, so I add this directive to the model:

consumes:
  - application/json

I’ve decided in my previous post that 6 parameters were needed:

  • the kind of os
  • the size of the machine
  • the initial disk size allocated
  • the lease (in days)
  • the environment
  • the description

All the parameters will compose a payload and therefore will be present in the body of the request. The YAML representation of the parameters array is:

parameters:
  - name: kind
    in: body
    description: "The OS type"
    required: true
  - name: size 
    in: body
    description: "The size of the (virtual) Machine"
    required: true
  - name: disksize
    in: body
    description: "The initial disk capacity allocated"
    required: true
  - name: leasedays
    in: body
    description: "The lease (in days)"
    required: true
  - name: environment_type
    in: body
    description: "The target environment"
  - name: description
    in: body
    description: "The target environment"

Sounds ok, but when I test this implementation in the swagger editor for validation, I get this error:

Swagger Error
Data does not match any schemas from 'oneOf'

STFWing and RTFMing…

in the Specifications, I have found this line:

If in is "body":

Field Name Type Description
schema Schema Object Required. The schema defining the type used for the body parameter.

Therefore, I should set a schema object for every parameter in order to define its type. In this example, I don’t want to go too deeply into the swagger specification, so I won’t define any type.

So I have tested the following:

parameters:
  - name: kind
    in: body
    description: "The OS type"
    required: true
    schema:
      type: string
  - name: size 
    in: body
    description: "The size of the (virtual) Machine"
    required: true
    schema:
      type: string
    ...

And again, I had a validation error from the editor:

Swagger Error

Operation cannot have multiple body parameters

RTFMing…

Body - The payload that’s appended to the HTTP request. Since there can only be one payload, there can only be one body parameter. The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.

What I must do, is to create a custom type nodeRequest with the input fields as properties and reference it in the body.

Here is the complete structure:

parameters:
  - name: nodeRequest 
    in: body
    description: a node request
    required: true
    schema:
        $ref: '#/definitions/NodeRequest'

And the proper NodeRequest definition in the definition area:

definitions:
  NodeRequest:
    description: A Node Request object
    properties:
      kind:
        type: string
        description: The OS type
      size:
        type: string
        description: The size of the (virtual) machine
      disksize:
        type: integer
        format: int32
        description: The initial disk capacity size (in GB)
      leasedays:
        type: integer
        format: int32
        description: The lease
      environment_type:
        type: string
        description: the target environment
      description:
        type: string

OK ! The swagger file is valid… Now let’s glue it together with swagger-ui and serve it from the GO API server I have developed before

Integrating swagger-ui

As written in the README in the github of the project, swagger-ui can be used “as-is” using the files in the dist folder. Let’s get the files from github:

/tmp #  git clone https://github.com/swagger-api/swagger-ui.git
Cloning into 'swagger-ui'...
remote: Counting objects: 7292, done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 7292 (delta 8), reused 0 (delta 0), pack-reused 7256
Receiving objects: 100% (7292/7292), 19.20 MiB | 1021.00 KiB/s, done.
Resolving deltas: 100% (3628/3628), done.
Checking connectivity... done.

Let’s checkout our project:

/tmp # git clone https://github.com/owulveryck/example-iaas.git 
...

and move the dist folder into the project:

mv /tmp/swagger-ui/dist /tmp/example-iaas

Adding a route to the GO server to serve the static files

I cannot simply add a route in the routes.go file for this very simple reason: The loop used in the router.go is using the Path method, and to serve the content of the directory, I need to use the PathPrefix method (see The Gorilla Documentation for more information).

To serve the content, I add this entry to the muxrouter in the router.go file:

router.
       Methods("GET").
       PathPrefix("/apidocs").
       Name("Apidocs").
       Handler(http.StripPrefix("/apidocs", http.FileServer(http.Dir("./dist"))))

Then I start the server and point my browser to http://localhost:8080/apidocs…

Wait, nothing is displayed…

The final test

As I serve the files from the ./dist directory, what I need to do is to move my swagger.yaml spec file into the dist subfolder and tell swagger to read it.

Et voilà!

Result

Final word

As you can see, there is a “Try out” button, which triggers a curl command… Very helpful to enter a test driven development mode.

On top of that swagger is really helpful and may be a great tool to synthesize the need of a client in term of an interface. Once the API is fully implemented, any client binding may also be generated with the swagger framework.

No not hesitate to clone the source code from github and test the swagger.yaml file in the editor to see how the bindings are generated

You can find all the codes in the github repository here in the branch simple-iaas-api-documentation-with-swagger

The final YAML file can be found here