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:

1swagger: '2.0'
2info:
3  version: 1.0.0
4    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:

 1paths:
 2  /v1/nodes:
 3    post:
 4      description: Create a node
 5      produces:
 6        - application/json
 7      responses:
 8        202:
 9          description: A request ID.
10        400:
11          description: |
12            When the request is malformated or when mandatory arguments are missing            
13        500:
14          desctiption: Unhandled error
15        502:
16          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:

1consumes:
2  - 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:

 1parameters:
 2  - name: kind
 3    in: body
 4    description: "The OS type"
 5    required: true
 6  - name: size 
 7    in: body
 8    description: "The size of the (virtual) Machine"
 9    required: true
10  - name: disksize
11    in: body
12    description: "The initial disk capacity allocated"
13    required: true
14  - name: leasedays
15    in: body
16    description: "The lease (in days)"
17    required: true
18  - name: environment_type
19    in: body
20    description: "The target environment"
21  - name: description
22    in: body
23    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:

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:

 1parameters:
 2  - name: kind
 3    in: body
 4    description: "The OS type"
 5    required: true
 6    schema:
 7      type: string
 8  - name: size 
 9    in: body
10    description: "The size of the (virtual) Machine"
11    required: true
12    schema:
13      type: string
14    ...

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

Swagger ErrorOperation 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:

1parameters:
2  - name: nodeRequest 
3    in: body
4    description: a node request
5    required: true
6    schema:
7        $ref: '#/definitions/NodeRequest'

And the proper NodeRequest definition in the definition area:

 1definitions:
 2  NodeRequest:
 3    description: A Node Request object
 4    properties:
 5      kind:
 6        type: string
 7        description: The OS type
 8      size:
 9        type: string
10        description: The size of the (virtual) machine
11      disksize:
12        type: integer
13        format: int32
14        description: The initial disk capacity size (in GB)
15      leasedays:
16        type: integer
17        format: int32
18        description: The lease
19      environment_type:
20        type: string
21        description: the target environment
22      description:
23        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:

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

Let’s checkout our project:

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

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:

1router.
2       Methods("GET").
3       PathPrefix("/apidocs").
4       Name("Apidocs").
5       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à!

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