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.
Leave a Reply