Swagger is a language for defining an API, with an ecosystem of tools to generate both code and documentation. I’m experimenting with it to see how much I can use it to automate creating API docs for Zulip. It’s full power is in defining your API functionality in one file, and then generating both documentation and skeleton code for multiple programming languages. You still have to implement the actual behavior behind it, but it generates the structure to do that more easily.
As with most code generators, I’m not sure how useful this is going to be for an already existing system not designed with the same structure. But it’s useful enough as a doc tool if you are willing to create the spec by hand. Then you get some nice pretty pages with, in theory, the ability for 3rd party developers to test API samples right from the documentation.
My first pass is to set up some static docs, and then a basic generated API and docs with functional demos.
The simplest way to have docs is Swagger UI, a viewing front-end. I thought, from reading blog posts and tutorials from other users, that I had to modify Swagger UI itself to create docs from my own spec. That is a mess of installing a bunch of other things that I never got working. After some advice from the IRC channel, I learned I didn’t actually need to do that.
You can literally take a single directory out of the downloadable source from GitHub, add your custom YAML or JSON file defining your API, and stick it on a webserver. (For more, go to the Swagger docs and look for “Swagger UI Documentation”.) You don’t get nice interactive examples this way, but you do get a well-structured description of your API.
The default uses the Swagger pet store example, so in index.html you will need to change the line
url = "http://petstore.swagger.io/v2/swagger.json";
to be where you have your definition file. I put mine in the same directory, it can be either YAML or JSON. That’s it.
If you don’t want functioning examples (which won’t work in this simplistic demo) also change which methods are allowed for the “Try it out!” button. Remove all of them to disable the button altogether, or only some if you need to disallow testing certain types of requests.
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
You can also specify which language you want Swagger UI to use for its static content (look in the lang directory), although I found from testing it appears you can only pick one. I haven’t figured out how to actually provide localized content in the user’s own language.
<!-- Some basic translations -->
<script src='lang/translator.js' type='text/javascript'></script>
<script src='lang/it.js' type='text/javascript'></script>
So that’s it for the super-simple option.
To generate a working API from your spec is a little more complicated, but not too much. In addition to creating your YAML or JSON file, you need to generate server code for it and install it on your own server. I had to install several additional packages, too.
For this, you need to start with Swagger Editor. You can install it locally, but I just used the web version.
I started with one of the samples available in the web version, a simplified pet store.
File-> Open Example -> petstore_simple.yaml
Edit the sample file as desired, the important things to note are the hostname and port of your server, and the base path where your API endpoint URLs will start. Here’s mine:
swagger: '2.0'
info:
version: '1.0.0'
title: Swagger test store
description: A sample API that uses the swagger-2.0 specification
termsOfService: http://feorlen.org
contact:
name: Feorlen
email: foo@example.com
url: http://swagger.io
license:
name: MIT
url: http://opensource.org/licenses/MIT
host: 10.2.3.4:8080
basePath: /api
schemes:
- http
consumes:
- application/json
produces:
- application/json
paths:
/items:
get:
description: Returns all items from the system that the user has access to
operationId: findItems
produces:
- application/json
- application/xml
- text/xml
- text/html
parameters:
- name: tags
in: query
description: tags to filter by
required: false
type: array
items:
type: string
collectionFormat: csv
- name: limit
in: query
description: maximum number of results to return
required: false
type: integer
format: int32
responses:
'200':
description: item response
schema:
type: array
items:
$ref: '#/definitions/item'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
post:
description: Creates a new item in the store. Duplicates are allowed
operationId: addItem
produces:
- application/json
parameters:
- name: item
in: body
description: Item to add to the store
required: true
schema:
$ref: '#/definitions/newItem'
responses:
'200':
description: item response
schema:
$ref: '#/definitions/item'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
/items/{id}:
get:
description: Returns a user based on a single ID, if the user does not have access to the item
operationId: findItemById
produces:
- application/json
- application/xml
- text/xml
- text/html
parameters:
- name: id
in: path
description: ID of item to fetch
required: true
type: integer
format: int64
responses:
'200':
description: item response
schema:
$ref: '#/definitions/item'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
delete:
description: deletes a single item based on the ID supplied
operationId: deleteItem
parameters:
- name: id
in: path
description: ID of item to delete
required: true
type: integer
format: int64
responses:
'204':
description: item deleted
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
definitions:
item:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
newItem:
type: object
required:
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
errorModel:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
I edited this elsewhere and then pasted it back into the online editor. The right pane will tell you if you messed up anything required. Now generate server code based on this API definition. I’m using Python Flask, but there are many to choose from.
Generate Server -> Python Flask
Download the resulting zip file and put its contents on your server somewhere. Since I’m using port 8080, I can do this in my own home directory without running as root. (I’m making my changes on the server directly, but edit locally and then upload if you like.)
First, customize the generated code:
In app.py I added my correct host, some extra logging (“INFO” for less debugging), and changed the title. I haven’t figured out where this title is actually used, maybe I’ll find it eventually. It now looks like this:
#!/usr/bin/env python3
import connexion
import logging
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
app = connexion.App(__name__, specification_dir='./swagger/')
app.add_api('swagger.yaml', arguments={'title': 'Sample Swagger server'})
app.run(host='10.2.3.4', port=8080)
I also had to edit the generated implementations in controllers/default_controller.py to remove type hints because my version of Python doesn’t support them.
def add_item(item):
return 'do some magic!'
Those are the basic changes. The code doesn’t do anything besides return a static message, but is otherwise functional. I haven’t figured out how to change the language for Swagger UI, but maybe that is possible.
Now install any necessary packages. I already have Python 3 and Flask, but I need Connexion to handle HTTP. To get that, I first have to install a version of pip that can handle packages for Python 3.
sudo apt-get install python3-pip
sudo pip3 install -U connexion
Next add execute permissions to the server script
chmod 744 app.py
and run the server
./app.py
And then the ui is available on port 8080 (note my base path is used here.)
http://10.2.3.4:8080/api/ui/
I can interact with the api using this page, or coping the curl examples and executing them in a shell. Click on the example values to post the sample payload into the appropriate field.
curl -X GET --header 'Accept: text/html' 'http://10.2.3.4:8080/api/items'
do some magic!
curl -X POST --header 'Content-Type: applicheader 'Accept: application/json' -d '{ "id": 5, "name": "potato" }' 'http://10.2.3.4:8080/api/items'
"do some magic!"
The next step of this example is to actually implement the endpoint functionality. For my purposes, my next task is to figure out how to make the web UI work with an existing API that was not designed to use it.