Simple IaaS API documentation with swagger
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