go generateを使って、JSONファイルからソースコードを自動生成する

はじめに

Go 1.4 にて導入されたgo generateコマンドを使って、JSONファイルからソースコードを自動生成してみます。

今回は、JSONファイルに定義されたWebアプリケーションのルーティング情報をもとに、ルーターのプログラムが記載された単純なソースコードを生成してみます。

今回の手順で作成したソースコードは、こちらのリポジトリに格納しています。

今回、ルーターにはgorilla/muxを使用しています。

JSONファイルからソースコードを生成するコマンドの作成

まずは、以下のようなルーティング情報が記載されているJSONファイルを読み込み、ルーターのプログラムが記載されたソースコードを生成するコマンドを作成します。

[
  {"method": "GET", "path": "/", "handler": "topHandler"},
  {"method": "GET", "path": "/users", "handler": "usersIndexHandler"},
  ...
]

cmd/gen-router/main.go

package main

import (  
    "bytes"
    "encoding/json"
    "flag"
    "io/ioutil"
    "text/template"
)

const strTmpl = `// generated by gen-router; DO NOT EDIT  
package main

import "github.com/gorilla/mux"

func newRouter() *mux.Router {  
    r := mux.NewRouter(){{range .}}
    r.HandleFunc("{{.Path}}", {{.Handler}}).Methods("{{.Method}}"){{end}}
    return r
}
`

type route struct {  
    Method  string
    Path    string
    Handler string
}

func main() {  
    inPath := flag.String("i", "", "input file path")
    outPath := flag.String("o", "", "output file path")

    flag.Parse()

    in, err := ioutil.ReadFile(*inPath)
    if err != nil {
        panic(err)
    }

    var routes []route
    if err := json.Unmarshal(in, &routes); err != nil {
        panic(err)
    }

    tmpl, err := template.New("router").Parse(strTmpl)
    if err != nil {
        panic(err)
    }

    bf := new(bytes.Buffer)

    if err := tmpl.Execute(bf, routes); err != nil {
        panic(err)
    }

    if err := ioutil.WriteFile(*outPath, bf.Bytes(), 0644); err != nil {
        panic(err)
    }
}

上記プログラムの作成後、以下コマンドを実行し、ローカルマシンへgen-routerコマンドをインストールします。

$ go install ./cmd/gen-router

JSONファイルの作成

ルーティング情報を記載したJSONファイルを、以下の通り作成します。

routes.json

[
  {"method": "GET", "path": "/", "handler": "topHandler"},
  {"method": "GET", "path": "/users", "handler": "usersIndexHandler"},
  {"method": "GET", "path": "/users/{id}", "handler": "usersShowHandler"},
  {"method": "POST", "path": "/users", "handler": "usersCreateHandler"}
]

メインプログラムの作成

メインプログラムを以下の通り作成します。このプログラムは、go generateコマンドで生成されるルーターを使用します。また、このソースコード上に、go generateコマンド実行時の処理を定義した、//go:generateで始まるコメントを記載します。

package main

import (  
    "fmt"
    "net/http"
)

func topHandler(w http.ResponseWriter, r *http.Request) {  
    fmt.Fprintln(w, "topHandler")
}

func usersIndexHandler(w http.ResponseWriter, r *http.Request) {  
    fmt.Fprintln(w, "usersIndexHandler")
}

func usersShowHandler(w http.ResponseWriter, r *http.Request) {  
    fmt.Fprintln(w, "usersShowHandler")
}

func usersCreateHandler(w http.ResponseWriter, r *http.Request) {  
    fmt.Fprintln(w, "usersCreateHandler")
}

//go:generate gen-router -i routes.json -o router.go
func main() {  
    http.Handle("/", newRouter())

    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

go generate コマンドの実行

以下の通り、go generateコマンドを実行します。

$ go generate

以下の内容が記載されたrouter.goファイルが生成されたことを確認します。

// generated by gen-router; DO NOT EDIT
package main

import "github.com/gorilla/mux"

func newRouter() *mux.Router {  
    r := mux.NewRouter()
    r.HandleFunc("/", topHandler).Methods("GET")
    r.HandleFunc("/users", usersIndexHandler).Methods("GET")
    r.HandleFunc("/users/{id}", usersShowHandler).Methods("GET")
    r.HandleFunc("/users", usersCreateHandler).Methods("POST")
    return r
}

動作確認

プログラムを実行し、ルーティング処理がJSONファイルの定義通りに実施されることを確認します。

$ go run main.go router.go
$ curl -XGET localhost:8080/
topHandler  
$ curl -XGET localhost:8080/users
usersIndexHandler  
$ curl -XGET localhost:8080/users/1
usersShowHandler  
$ curl -XPOST localhost:8080/users
usersCreateHandler  

おわりに

今回のサンプルは非常に単純なものでしたが、go generateコマンドを有効活用することで、ソフトウェア開発作業を効率化させられることと思います。なお、JSONファイルからGo言語のソースコードを自動生成しているプロジェクトとして、stripe/aws-goがあります。

参考文献