Use go-swagger

This article Go (その2) Advent Calendar 2016 ` _ is the seventh day of the article.

Swagger is popular recently as an automatic code generation tool from the API (objections are accepted).

In this field, goa but is famous, goa is a method that only generated from the DSL of Go. It can not be used if swagger definition already exists.

In the case of Ki There swagger definition, this time will be described in go-swagger _ is convenient to use when.

In addition, the contents of https://github.com/shirou/swagger_sample has published at.

What is go-swagger?

Go-swagger is a tool that generates go server and client from swagger definition.

Installation may be installed with go get, but if there is no go environment

There are prepared.

How to use go-swagger - Server

As an example, let's assume that we define it with swagger.yml like the following.

produces:
  - application/json
paths:
  '/search':
    get:
      summary: get user information
      consumes:
        - application/x-www-form-urlencoded
      tags:
        - user
      parameters:
        - name: user_id
          type: integer
          in: formData
          description: user id
      responses:
        200:
          schema:
            $ref: '#/definitions/User'
        default:
          schema:
            $ref: '#/definitions/Error'
definitions:
  User:
    type: object
    required:
      - user_id
    properties:
      user_id:
        type: integer
        description: user id
      name:
        type: string
        description: name
  Error:
    type: object
    properties:
      message:
        type: string

To generate server side code execute the following command.

$ swagger generate server -f swagger.yml -A swaggertest

with this,

  • Cmd
  • Models
  • Restapi

This creates the directory named "source code" required. Specifically, it is like this.

restapi/
|-- configure_swaggertest.go
|-- doc.go
|-- embedded_spec.go
|-- operations
|    |-- swaggertest_api.go
|    `-- user
|         |-- get_search.go
|         |-- get_search_parameters.go
|         |-- get_search_responses.go
|         `-- get_search_urlbuilder.go
`-- server.go

-A swaggertest is the application name. If specified, it will be used in various places.

under the Operations user it has been taken from the tag. If multiple tags are set, the same contents will be generated in multiple directories, so please be careful.

After that, I will not touch files other than configure_swaggertest.go which will be explained later.

Implementing Handler

Let's create another file under the package without touching the generated file. restapi/operations/user/search.go create a file called, to implement the code, such as the following.

package user

import (
    middleware "github.com/go-openapi/runtime/middleware"
    "github.com/go-openapi/swag"

    "github.com/shirou/swagger_sample/models"
)

func Search(params GetSearchParams) middleware.Responder {
    payload := &models.User{
            UserID: params.UserID,
            Name:    fmt.Sprintf("name_%d", swag.Int64Value(params.UserID)),
    }

    return NewGetSearchOK().WithPayload(payload)
}

Params is a parameter that comes across. It is validated in advance according to swagger definition, so you can use with confidence.

NewGetSearchOK is in the swagger definition 200 to create a structure in which to return to the case of. Define and pass User as payload.

swag package or conversion and a pointer and the real, useful library that provides a convenient functions such as or search for a path . As a language restriction of go, we need to make it a pointer to distinguish between no value and default value, so we need to use the conversion with swag.

Register Handler

restapi/configure_swaggertest.go to adds the Handler was now implemented. The first is middleware.NotImplemented so is written, and replace it with the Handler was earlier implementation.

api.UserGetSearchHandler = user.GetSearchHandlerFunc(user.Search)

Since configure.go is generated once it is not changed automatically afterwards, care must be taken when updating swagger.yml and adding a handler. I'm renaming it once and then copying it from the generated file again.

Since configure.go also contains the setup code, it is a good idea to add preconfiguration or add middleware as needed.

Execution

We are ready now. cmd/swaggertest-server Let's build in the following.

$ cd cmd/swaggertest-server && go build -o ../../server

$ ./server -port=8000

Since the default is to start with a random Port, port has been specified in the argument.

After that you will be able to access normally.

$ curl "http://localhost:8080/search?user_id=10"
{"name":"name_10","user_id":10}

It's simple.

Raw Request

in the params is http.Request because there is, if you want to obtain a Request itself can be obtained from here. I think whether to obtain context from here.

How to use Swagger-Go : client Hen

Go-swagger can generate clients with the following commands, not just servers.

$ swagger generate client -f swagger.yml -A swaggertest

After that, if you write the following code, you can execute it as client.

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/go-openapi/swag"

    apiclient "github.com/shirou/swagger_sample/client"
    "github.com/shirou/swagger_sample/client/user"
)

func main() {

    // make the request to get all items
    p := &user.GetSearchParams{
            UserID: swag.Int64(10),
    }

    resp, err := apiclient.Default.User.GetSearch(p.WithTimeout(10 * time.Second))
    if err != nil {
            log.Fatal(err)
    }
    fmt.Println(resp.Payload.Name)
}

In this example, you only hit with fixed parameters, but you can easily implement the CLI tool by setting the arguments and so on properly.

Model

In swagger you can define a model and include it in a reply. Definitions below.

Of example you are using this time User model is models/user.go will be generated as follows in.

// User user
// swagger:model User
type User struct {
     // name
     Name string `json:"name,omitempty"`

     // user id
     // Required: true
     UserID *int64 `json:"user_id"`
}

Since the models / user.go that things would have been crushed to write and re-generation, models I think that making such user_hoge.go below may you at continue to add lot of the function.

test

To test the server implementation pass the specified parameters to Handler. However, since it is necessary to put a http.Request, it is at that time httptest.NewRecorder() so that you record the return value using. After that, because each Handler is a mere function, and it is only necessary to perform its function, httptest.Server you do not need to launch.

func TestSearch(t *testing.T) {
    req, err := http.NewRequest("GET", "", nil)
    if err != nil {
            t.Fatal(err)
    }

    params := GetSearchParams{
            HTTPRequest: req,
            UserID:      swag.Int64(10),
    }
    r := Search(params)
    w := httptest.NewRecorder()
    r.WriteResponse(w, runtime.JSONProducer())
    if w.Code != 200 {
            t.Error("status code")
    }
    var a models.User
    err = json.Unmarshal(w.Body.Bytes(), &a)
    if err != nil {
            t.Error("unmarshal")
    }
    if swag.Int64Value(a.UserID) != 10 {
            t.Error("wrong user id")
    }
}

Summary

I showed an example of generating server and client code from swagger definition file using go-swagger.

swagger is swagger ui is fairly easy to use, such as coming out of the command of the curl, the problem that the definition file is also not difficult if you get used to it (file split can not There is.

When developing with development of swagger and swagger ui while checking protocol definitions, such as when developing separately between client and server, inconsistency of understanding is less likely to occur.

It is good to generate from Goa, but if you are generating from the swagger definition file you should consider go-swagger as well.