OpenAPI (formerly known as Swagger) is the more popular protocol for defining HTTP APIs. However, the OpenAPI definition file is in a machine-friendly format that is not easy to write and read. Here is a method to generate OpenAPI definition files based on Go code using go-swagger. This method uses only Go code to define the API, and does not force the Server or Client to use Go as well.

Currently go-swagger can only generate definitions in OpenAPI 2.0 format. This is also the format that is widely used now. go-swagger will support OpenAPI 3.0 in the future.

This article assumes familiarity with Go syntax and only explains the extensions to go-swagger in detail.

Suppose you want to create a set of APIs for a pet store that supports defining pets. This article shows how to use Go to define this API.

Although go-swagger does not require the use of a Go project, it is recommended to create a separate Go project for the API to facilitate the use of completions and code highlighting.

The first step is to define the basic information about the API, such as the name of the API, the protocol, the supported serialization formats, the base path, and some security information. This information is defined in the petstore.go file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Package petstore The API of the pet store.
//
// The pet store is a demo service to show how to use go-swagger generate
// OpenAPI (Swagger) Spec.
//
// Version: 1.0.0
// BasePath: /v1/petstore
// Schemes: https
// Consumes:
// - application/json
// Produces:
// - application/json
//
// swagger:meta
package petstore
//go:generate go run github.com/go-swagger/go-swagger/cmd/swagger@latest generate spec -o openapi.yaml
//go:generate go run github.com/go-swagger/go-swagger/cmd/swagger@latest validate openapi.yaml

The package description, according to Go’s specification, is to start with Package petstore. The remainder of that line, which is The API of the pet store. will be used as the title part of OpenAPI, and the rest of the non-go-swagger defined comments will be used as the description of OpenAPI.

Note: If the package description does not contain a description section, go-swagger will use the first line of what would otherwise be the title section as the description and leave the title blank. The OpenAPI file defined this way does not conform to the specification. So please write the two descriptions in full.

Starting at the Version: line is the definition of go-swagger. This section defines the rest of the base content of the generated OpenAPI file. The specific options available can be found in swagger:meta. The last line swagger:meta indicates that this comment is used in go-swagger to generate the base information.

The next two lines go:generate are used to generate the OpenAPI file. To ensure that no executable is missing during execution, go run github.com/go-swagger/go-swagger/cmd/swagger@latest is used to call go-swagger. If you can guarantee that the execution environment has go-swagger installed, the two lines go run ... can be simplified directly to swagger. This simplification will improve the speed of execution when generating.

To install go-swagger, you can refer to official website. If you have a Go environment, you can install it directly via go install github.com/go-swagger/go-swagger/cmd/swagger@latest. Note that you need to add $GOPATH/bin to $PATH for easy execution.

The first line generate spec -o openapi.yaml will generate the OpenAPI file based on the contents of the current directory. The second line validate openapi.yaml will verify that the OpenAPI file is compliant.

With this file, you can generate the OpenAPI file by executing go generate . to generate the OpenAPI file. The execution will generate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ cat openapi.yaml 
basePath: /v1/petstore
consumes:
- application/json
info:
  description: |-
    The pet store is a demo service to show how to use go-swagger generate
    OpenAPI (Swagger) Spec.
  title: The API of the pet store.
  version: 1.0.0
paths: {}
produces:
- application/json
schemes:
- https
swagger: "2.0"

We haven’t defined any APIs yet, so the paths field is empty. We will add this field later step by step.

After that, define the API related to the Pet instance in the pet.go file.

First define the Pet structure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// NewPet provides data to create a pet.
//
// swagger:model
type NewPet struct {
        // The name of the pet.
        //
        // min length: 1
        // max length: 64
        // example: Yuki
        Name string `json:"name"`
        // The contact email for the pet.
        //
        // min length: 1
        // max length: 64
        // example: person@domain.com
        // swagger:strfmt email
        Contact string `json:"contact"`
}
// Pet provides data of a pet.
//
// swagger:model
type Pet struct {
        // The pet id.
        //
        // example: 1
        ID uint64 `json:"id"`
        // The name of the pet.
        //
        // example: Yuki
        Name string `json:"name"`
        // The contact email for the pet.
        //
        // example: person@domain.com
        // swagger: strfmt email
        Contact string `json:"contact"`
}

where NewPet is used to create and modify Pet data, and Pet is used to display Pet data. The two structures have some nuances, such as NewPet will not have ID information, and Pet does not need to define input restrictions. In simple scenarios, the structures can be combined into one structure, or NewPet can be embedded anonymously in Pet to simplify the structure.

swagger:model is processed by go-swagger as a predefined structure and the definitions are placed in definitions. This structure will be reused across multiple API methods.

go-swagger will generate the corresponding types based on the type of the Go structure. The annotation of each field can specify more detailed value constraints, such as min length: 1 and max length: 64 in the example, which agree that the length of the corresponding field string should be between [ 1, 64]. Further semantic constraints on the string values can also be made using swagger:strfmt.

In order to return another structure when handling errors, it is also necessary to define the error structure.

1
2
3
4
type Error struct {
        ID     string `json:"error_id"`
        Detail string `json:"error_detail"`
}

Simple structures can be used without writing all the information, even without marking swagger:model. go-swagger will automatically find the corresponding structure definition based on the definition of the method afterwards.

Once the structure for processing is defined, the specific operation methods can be defined. To cover the widest range of cases, here is an example of how to update a Pet method.

1
2
3
4
5
6
7
// swagger:route POST /pets/{pet_id} pet UpdatePet
//
// Update data of a pet.
//
// responses:
//   200: PetReply
//   default: ErrorReply

swagger:route is used to define a specific API method in the format swagger:route <method> <url> <tag> ... <operation id> . There can be zero or any number of tags, giving the method a number of tags. No matter how many tags there are, the last parameter operation id is the name of the method. This method name needs to be unique in an API definition file.

Specifically in the example, swagger:router POST /pets/{pet_id} pet CreatePet means the following.

  • Follow the common API principle of defining the update method on the POST /pets/{pet_id} endpoint.
    • The path has an input parameter pet_id.
  • Add the pet tag to this method.
  • The method name is CreatePet.

This is followed by the method description information.

responses is the return information that defines the go-swagger, and can be structured differently depending on the HTTP Code. The example returns a PetReply structure when the operation is normal, i.e. Code 200, and an ErrorReply structure in other cases.

Let’s put aside the output structure and look at how to define the input structure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// swagger:parameters UpdatePet RetrievePet DeletePet
type PetIDArg struct {
        // in: path
        ID uint64 `json:"pet_id"`
}
// swagger:parameters CreatePet UpdatePet
type NewPetArg struct {
        // in: body
        NewPet *NewPet
}

swagger:parameters is used to define an input structure in the format swagger:parameters <operation id> <operation id> ... . where operation id is the name of the method that uses this input structure. Note that it is possible to have multiple input structures used on a single method, such as the example where both structures reference UpdatePet. Because the input to a method is not only the HTTP Post message, but also the information inside the URL and possibly even the Header. In order to reuse these structures, go-swagger can define different parts of the input into different structures and reference them to the same method.

In the example, PetIDArg defines the path parameter pet_id by in: path, where pet_id is defined by json: "pet_id". Another structure NewPetArg defines the Post message structure NewPet via in: body, where NewPet is one of the structures described above.

This is followed by the output structure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// PetReply returns a pet.
//
// swagger:response
type PetReply struct {
        // in: body
        Pet *Pet
}
// ErrorReply replys an error of API calling.
//
// swagger:response
type ErrorReply struct {
        // in: body
        Error Error
}

swagger:response is used to define an output structure. Its name is the same as responses defined by swagger:route. in: body indicates the corresponding field for the returned HTTP message.

This completes the definition of Pet’s update method. It also explains most of the contents of go-swagger. You can directly execute go generate . or swagger generate spec to see the output. More details on using go-swagger to generate spec files can be found on the official website.

As an example, the following pet.go file gives all the definitions for the Pet operation. Together with the previously mentioned petstore.go file, it can be used directly to generate the OpenAPI definitions for petstore.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package petstore
// NewPet provides data to create a pet.
//
// swagger:model
type NewPet struct {
    // The name of the pet.
    //
    // min length: 1
    // max length: 64
    // example: Yuki
    Name string `json:"name"`
    // The contact email for the pet.
    //
    // min length: 1
    // max length: 64
    // example: person@domain.com
    // swagger:strfmt email
    Contact string `json:"contact"`
}
// Pet provides data of a pet.
//
// swagger:model
type Pet struct {
    // The pet id.
    //
    // example: 1
    ID uint64 `json:"id"`
    // The name of the pet.
    //
    // example: Yuki
    Name string `json:"name"`
    // The contact email for the pet.
    //
    // example: person@domain.com
    // swagger: strfmt email
    Contact string `json:"contact"`
}
// swagger:parameters UpdatePet RetrievePet DeletePet
type PetIDArg struct {
    // in: path
    ID uint64 `json:"pet_id"`
}
// swagger:parameters CreatePet UpdatePet
type NewPetArg struct {
    // in: body
    NewPet *NewPet
}
// PetReply returns a pet.
//
// swagger:response
type PetReply struct {
    // in: body
    Pet *Pet
}
// PetsReply returns a list of pets.
//
// swagger:response
type PetsReply struct {
    // in: body
    Pets []*Pet
}
type Error struct {
    ID     string `json:"error_id"`
    Detail string `json:"error_detail"`
}
// ErrorReply replys an error of API calling.
//
// swagger:response
type ErrorReply struct {
    // in: body
    Error Error
}
type ErrorInput struct {
    Error
    Fields map[string]Error `json:"error_fields"`
}
// ErrorInputReply replys an error with details of input data.
//
// swagger:response
type ErrorInputReply struct {
    // in: body
    ErrorInput ErrorInput
}
// swagger:route GET /pets pet ListPet
//
// Get the list of pets.
//
// responses:
//   200: PetsReply
//   default: ErrorReply
// swagger:route POST /pets pet CreatePet
//
// Create a new pet.
//
// responses:
//   200: PetReply
//   default: ErrorReply
// swagger:route POST /pets/{pet_id} pet UpdatePet
//
// Update data of a pet.
//
// responses:
//   200: PetReply
//   default: ErrorReply
// swagger:route GET /pets/{pet_id} pet RetrievePet
//
// Get data of a pet.
//
// responses:
//   200: PetReply
//   default: ErrorReply
// swagger:route DELETE /pets/{pet_id} pet DeletePet
//
// Delete a pet.
//
// responses:
//   200: PetReply
//   default: ErrorReply