With the V2 REST API we started using a design driven process.
The API is defined using OpenAPI v2 in algod.oas2.json.
- Document your changes by editing algod.oas2.json
- Regenerate the endpoints by running make generate.
- Update the implementation in server/v2/handlers.go. It is sometimes useful to consult generated/routes.go to make sure the handler properly implements ServerInterface.
We found that oapi-codegen produced the cleanest code, and had an easy to work with codebase. There is an algorand fork of this project which contains a couple modifications that were needed to properly support our needs.
Specifically, uint64 types aren't strictly supported by OpenAPI. So we added a type-mapping feature to oapi-codegen.
We chose to maintain V2 and V3 versions of the spec because OpenAPI v3 doesn't seem to be widely supported. Some tools worked better with V3 and others with V2, so having both available has been useful. To reduce developer burdon, the v2 specfile is automatically converted v3 using converter.swagger.io.
swagger.jsondefines the API schema. However, server code inapi/v1/...currently serves as the ground truth, as the schema is generated from server code.- to generate
swagger.json, runmake build. You may need to havego-swaggerinstalled. You can get it by runningmake deps.
- to generate
api/clientis a package for internal (or external) libraries to interact with the REST API. In particular, it should minimize dependencies.- we currently use a non-swagger generated client. Why? The swagger generated client pulls in too many dependencies (go-openapi, for instance) and unnecessary functionality. Testing the swagger spec must be done another way. It seems that unwrapped json raw types are sent on the wire (so not wrapped by responses), so we don't need to decode them into responses.
api/v1/...contains an implementation for the server. The swagger schema is auto-generated (cd api/; swagger generate spec -o ./swagger.json) from server implementation code.api/v1/handlersandapi/v1/modelsshould never be directly imported by external clients.- or, run
go generatein theapifolder.
- or, run
go-swaggerdoes not generatex-nullableproperties on model fields. We want them so that we can generate models without pointers. (This is more compatible with the current model we use. We may want to use pointers instead, eventually)- make sure you populate the
defaultproperty in order to generate a model without a pointer field
- make sure you populate the
go-swaggerdoes not support OpenAPI 3.0. It only supports OpenAPI 2.0. There does not seem to be another tool that allows us to generate a swagger spec from code. It may be worth writing our own, eventually.go-swaggerdoes not support embedded structs.- in fact,
go-swaggeris generally very strange. The source -> spec generation looks fairly immature. Here are some (undocumented) tips:- every
swagger:responsetype must contain a single field (e.g.BodyorPayload) that is the actual data type you want to return. So theresponsetype is a wrapper, which makes sense, except the clients thatgo-swaggergenerate automatically unwrap the underlying value. So this is very weird, and undocumented. swagger:routeis a less powerful version of theswagger:operationannotation. However,swagger:operationis much more finicky and not mature. When defining the annotation, make sure it is precise yaml, and start the yaml section with---. This means keeping track of tabs and whitespaces. This seems to be the easiest way to define parameters without having to make explicit structs (which we may want to do eventually anyways).
- every
- in fact,
- don't deal with
go-swaggercodegen docs. Refer directly tohttps://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#pathItemObject/ go-swaggerdoes not supportregexin path parameter path templating.- complex parameter schemas are only supported in parameters
in:body - responses are distinct from definition objects (e.g. the former has a
descriptionfield, and headers). We always want to return a response in an operation. Returning a model seems to work, but does not seem advised. go-swaggerassumesx-isnullable: trueand generates pointer files. If we ever want to use a swagger generated client internally this may be a problem. Note thatgo-swaggerdoesn't support a correspondingx-isnullableannotation. We can get around that by using thedefaultannotation and then find-and-replacing anx-isnullableinto the actual spec:
//go:generate sed -i "" -e "s/object\",/object\", \"x-nullable\": false,/" ./swagger.json
- go-swagger does not seem to support simple string responses. They always get wrapped. (oh well) e.g. [go-swagger/go-swagger#1635]
- I've hardcoded a keylength into the spec for now, until I figure out how to tie that programatically back into the server code (perhaps with a find-and-replace).