Goの標準ライブラリだけでルーティングするコードを自動生成するツールを作った

Goは標準ライブラリが豊富で、Webアプリが簡単に書けていいですよね。

ひと通りの機能は多少冗長でも標準ライブラリだけで実装できます。

しかし、パスパラメータが複数のときのルーティングは標準ライブラリだけだと面倒です。

Webフレームワークを導入すればいいという声が聞こえてきそうですが、世の中標準ライブラリだけで実装する需要が少なからずあるみたいなので、ルーターのコードを生成するツールを作って見ました。

# ルーティングのお話

本題に入る前にフレームワークと標準ライブラリのルーティング方法を比較していきましょう。

## 単純なパスのとき

パスパラメータを扱わない、単純なパスをルーティングするときは次のようにできます。

```go:標準ライブラリ
http.HandleFunc("/users", UsersHandler) // HTTPメソッドの判定など、関数内で工夫が必要
```

ちなみに軽めのWebフレームワーク・ライブラリで有名なgin、echo、gorilla/muxなどはこんな感じです。

```go:gin
r := gin.Default()
r.GET("/users", GetHandler)
r.POST("/users", PostHandler)
```

```go:echo
e := echo.New()
e.GET("/users", GetHandler)
e.POST("/users", PostHandler)
```

```go:gorilla/mux
r := mux.NewRouter()
r.HandleFunc("/users", UsersHandler) 
```

この程度の差なら標準ライブラリだけで問題ない印象です。

## パスパラメータが1つのとき

ではパスパラメータを扱うときはどうでしょう。`/users/:user_id`をルーティングしてみます。

フレームワークを使うとこんな感じで書けます。

```go:gin
r := gin.Default()
r.GET("/users/:user_id", GetUserHandler) 

func GetUserHandler(c *gin.Context) {
	userId := c.Param("user_id") // 引数で渡される*gin.contextからパスパラメータを取得可能
	/*...*/
}
```

```go:echo
e := echo.New()
e.GET("/users/:user_id", GetUserHandler)

func GetUserHandler(c echo.Context) {
	userId := c.Param("user_id") // ginとほぼ同じ
	/*...*/
}
```

```go:gorilla/mux
r := mux.NewRouter()
r.HandleFunc("/users/{user_id}", UserHandler) 

func UserHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r) // ライブラリの関数を使うと取得可能
	userId := vars["user_id"]
	/*...*/
}
```

標準ライブラリではどうかというと、

```go:標準ライブラリ
http.HandleFunc("/users/", UserHandler) // /users/:user_idをhandle

func UserHandler(w http.ResponseWriter, r *http.Request) {
	userId := strings.TrimPrefix(r.URL.Path, "/users/") // URLを切り取ってなんとかする
}
```

ここまでは、ああそうなのねって感じです。

文字列操作がスマートじゃないですが、特に問題はないでしょう。

## パスパラメータが複数で階層になっているとき

次に`/users/:user_id/posts/:post_id/comments/:comment_id`を扱ってみましょう。

フレームワークを使うとこんな感じで書けます。

```go:gin
r := gin.Default()
r.GET("/users/:user_id/posts/:post_id/comments/:comment_id", GetCommentHandler) 

func GetCommentHandler(c *gin.Context) {
	userId := c.Param("user_id")
	postId := c.Param("post_id")
	commentId := c.Param("comment_id")
	/*...*/
}
```

```go:echo
e := echo.New()
e.GET("/users/:user_id", GetCommentHandler)

func GetCommentHandler(c echo.Context) {
	userId := c.Param("user_id")
	postId := c.Param("post_id")
	commentId := c.Param("comment_id")
	/*...*/
}
```

```go:gorilla/mux
r := mux.NewRouter()
r.HandleFunc("/users/{user_id}/posts/{post_id}/comments/{comment_id}", CommentHandler) 

func CommentHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	userId := vars["user_id"]
	postId := vars["post_id"]
	commentId := vars["comment_id"]
    /*...*/
}
```

こんな感じでパスパラメータが増えてもシンプルに取得することができます。

標準ライブラリもみてみましょう。

```go:標準ライブラリ

http.HandleFunc("/users/", UserHandler)

func UserHandler(w http.ResponseWriter, r *http.Request) {
	path := strings.TrimPrefix(r.URL.Path, "/users/")
	i := strings.Index(path, "/")
	userId, path := path[:i], path[i:]
	PostHandler(w, r, path, userId)
}

func PostHandler(w http.ResponseWriter, r *http.Request, path string, userId string) {
	path = strings.TrimPrefix(path, "/posts/")
	i := strings.Index(path, "/")
	postId, path := path[:i], path[i:]
	CommentHandler(w, r, path, userId, postId)
}

func CommentHandler(w http.ResponseWriter, r *http.Request, path string, userId string, postId string) {
	commentId := strings.TrimPrefix(path, "/comments/")
	/*...*/
}
```

**~~こんなんやってられるか！！！！~~**

- HandleFuncでパスの扱いを明示できない
- ハンドラの中身がどんどん汚れていく
- 分岐が増えると関数の階層地獄になる
- パスパラメータの話に隠れているが、HTTPMethodの分岐も考えなければならない

などなど、問題点が結構あります。

このように、標準ライブラリのルーティングはいけてない点が多すぎて快適には使えません。

## 標準ライブラリを使った実装の改善

実は先程の**ハンドラの中身がどんどん汚れていく**という点は改善が可能です。

ここまでの`http.HandleFunc`を使った例は、

```go:標準ライブラリのサーバー
func main() {
	http.HandleFunc("/users/", UserHandler)
	http.ListenAndServe(":8080", nil)
}
```

のように`http.ListenAndServe`の第2引数に`nil`を渡す前提になっています。この第2引数は`http.Handler`インターフェイスで、次のように定義されています。

```go:http/server.go
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
```

この`ServeHTTP`関数がリクエストがサーバーにリクエストが来たときのエントリポイントになっていて、登録されたパスのパターンにしたがってルーティング処理をしています。

上の例では`nil`を渡しているのでhttpパッケージ内でグローバル宣言されている`DefaultServeMux`が代わりに使用されます。

ちなみに`http.HandleFunc`の実装は次のようになっていて、`DefaultServeMux`にHandlerを登録していることがわかります。

```go:http/server.go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
```

少し複雑な話が続きましたが、要は`DefaultServeMux`と`http.HandleFunc`を使わないようにすればいいわけです。`http.Handler`を満たす構造体を自分で実装して`http.ListenAndServe`に渡せば、ルーティング処理をServeHTTP関数で行えることになり、Handler内が汚れないということです。

次のコードが`http.Handler`を自分で実装した例になっています。

```go:標準ライブラリのサーバー（改善版）

func main() {
	r := NewRouter()
	http.ListenAndServe(":8080", r)
}

type Router struct{}

func NewRouter() http.Handler {
	r := &Router{}
	return r
}

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// パスパラメータの分離の例
	endpoint, param := SeparatePath(...)

	// ルーティング処理の例
	switch endpoint {
	case "/users":
		switch r.Method {
		case http.MethodGet:
			UserHandler(w, r, param)
		default:
			MethodNotAllowedHandler(w, r)
		}
	/* ... */
	default:
		NotFoundHandler(w, r)
	}
}

func UserHandler(w http.ResponseWriter, r *http.Request, userId string) {
	/* ... */
}
```


# 本題（ルーターのコード生成）

前置きが非常に長くなってしまいましたが、ここからコード生成の話になります。

前節でルーティング処理の部分とHandlerを分離できることがわかりましたが、それでもswitch文を大量に書くことになり、面倒くさいです。面倒くさいですよね？

できればWebフレームワークみたいに簡単にルーティングしたいです。

ということで、シンプルなルーティング定義からコードを生成する**stdrouter**というツールを作りました。

## インストール

go get してください。

```shell
$ go get -u github.com/tetsuzawa/stdrouter/cmd/stdrouter
```

## 使い方

例としてこんな感じのディレクトリ構成で進めます。

```shell:ディレクトリ構成
.
├── handler
│   └── handler.go  // UserHandlerなど
├── main.go         // ListenAndServeがあるとこ
└── router.go       // ルーティング定義
```

### 1. router.goにルーティング定義を書く

Goのbuild tagsでビルドファイルを切り替えるので、先頭の`//+build stdrouter`を忘れないでください


```go:router.go
//+build stdrouter

package main

import (
	"net/http"

	"github.com/tetsuzawa/stdrouter"
	"github.com/tetsuzawa/stdrouter/_example/handler"
)

func NewRouter() http.Handler {
	r := stdrouter.NewRouter()
	r.HandleFunc("/", http.MethodGet, handler.GetRoot)
	r.HandleFunc("/api", http.MethodGet, handler.GetAPIRoot)
	r.HandleFunc("/api/users", http.MethodGet, handler.GetUsers)
	r.HandleFunc("/api/products", http.MethodGet, handler.GetProducts)
	r.HandleFunc("/api/products", http.MethodPost, handler.CreateProducts)
	r.HandleFunc("/api/users/create", http.MethodPost, handler.CreateUser)
	r.HandleFunc("/api/users/:user_id", http.MethodGet, handler.GetUser)
	r.HandleFunc("/api/users/:user_id", http.MethodPatch, handler.UpdateUser)
	r.HandleFunc("/api/users/:user_id", http.MethodDelete, handler.DeleteUser)
	r.HandleFunc("/api/users/:user_id/posts", http.MethodGet, handler.GetPosts)
	r.HandleFunc("/api/users/:user_id/posts/:post_id", http.MethodGet, handler.GetPost)
	r.HandleNotFound(handler.NotFoundHandler)
	r.HandleMethodNotAllowed(handler.MethodNotAllowedHandler)
	/*
		...
	*/
	return r
}
```

### 2. router.goと同じディレクトリで`stdrouter`を実行する

```shell
$ stdrouter
stdrouter: Router file generated to router_gen.go
```

実行が完了するとrouter_gen.goが生成されます。

```shell:ディレクトリ構成（生成後）
.
├── handler
│   └── handler.go  
├── main.go         
├── router.go
└── router_gen.go  // 生成されたファイル
```
<details><summary>router_gen.goの中身（長いので折りたたんでます）</summary><div>

```go:router_gen.go
// Code generated by Standard Library Router Generator; DO NOT EDIT"

//go:generate stdrouter
//+build !stdrouter

package main

import (
	"github.com/tetsuzawa/stdrouter/_example/handler"
	"net/http"
	"path"
	"strings"
)

type Router struct{}

func NewRouter() http.Handler {
	r := &Router{}
	return r
}

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	handleBase(w, r, r.URL.Path)
}

func handleBase(w http.ResponseWriter, r *http.Request, p string) {
	endpoint, p := SeparatePath(p, 3)
	switch endpoint {
	case "/":
		switch r.Method {
		case http.MethodGet:
			handler.GetRoot(w, r)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	case "/api":
		switch r.Method {
		case http.MethodGet:
			handler.GetAPIRoot(w, r)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	case "/api/users":
		switch r.Method {
		case http.MethodGet:
			handler.GetUsers(w, r)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	case "/api/products":
		switch r.Method {
		case http.MethodGet:
			handler.GetProducts(w, r)
		case http.MethodPost:
			handler.CreateProducts(w, r)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	case "/api/users/create":
		switch r.Method {
		case http.MethodPost:
			handler.CreateUser(w, r)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	default:
		endpoint, param := SeparatePath(endpoint, 2)
		if endpoint == "/api/users" {
			handleUserId(w, r, p, param[1:])
		} else {
			handler.NotFoundHandler(w, r)
		}

	}

}

func handleUserId(w http.ResponseWriter, r *http.Request, p string, userId string) {
	endpoint, p := SeparatePath(p, 2)
	switch endpoint {
	case "/":
		switch r.Method {
		case http.MethodPatch:
			handler.UpdateUser(w, r, userId)
		case http.MethodDelete:
			handler.DeleteUser(w, r, userId)
		case http.MethodGet:
			handler.GetUser(w, r, userId)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	case "/posts":
		switch r.Method {
		case http.MethodGet:
			handler.GetPosts(w, r, userId)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	default:
		endpoint, param := SeparatePath(endpoint, 1)
		if endpoint == "/posts" {
			handlePostId(w, r, p, userId, param[1:])
		} else {
			handler.NotFoundHandler(w, r)
		}

	}

}

func handlePostId(w http.ResponseWriter, r *http.Request, p string, userId string, postId string) {
	endpoint, p := SeparatePath(p, 0)
	switch endpoint {
	case "/":
		switch r.Method {
		case http.MethodGet:
			handler.GetPost(w, r, userId, postId)
		default:
			handler.MethodNotAllowedHandler(w, r)
		}

	default:
		handler.NotFoundHandler(w, r)
	}

}

func SeparatePath(p string, n int) (head, tail string) {
	p = path.Clean("/" + p)
	ps := strings.Split(p[1:], "/")
	if len(ps) < n {
		return p, ""
	}
	head = path.Clean("/" + strings.Join(ps[:n], "/"))
	tail = path.Clean("/" + strings.Join(ps[n:], "/"))
	return head, tail
}
```

</div></details>

### 3. 通常通り`go build`してサーバーを動かす

```shell
$ go build -o server
$ ./server
```

使い方は以上です。

ちなみに一度コード生成したあとは、`go generate`でもコード生成できるようになります。

これでルーティングのコードを書く作業から開放されるので、メインのロジックを書くことに集中できます！

## TODO

- gorutineとcontextに対応
- importの並び替え
- SeparatePathの速い実装を検討
- デフォルトのエラーハンドラの用意
- goパッケージで利用できる関数がないか探す
- ベンチマークをとる
- リファクタ

など、やりたいことが結構あります。PRお待ちしてます！
リポジトリ: [github.com/tetsuzawa/stdrouter](https://github.com/tetsuzawa/stdrouter)


# stdrouter作成の流れ

ここからはstdrouterの作り方の話になります。興味のある方は参考にしていただけると幸いです。

最近Goの界隈で静的解析の波がきてますよね。静的解析で何が作れるのか調べてたときにコード生成ができることを知り、ネタになるとおもったので作りました。

作り方の流れとしては

1. ルーティングの定義ファイルと完成形のコードを手動で書いて、途中のロジックを考える
2. ルーティングの定義ファイルをAST（抽象構文木）に分解して解析する
3. 泥臭くfor文とif文でコードを組み立てるロジックを書く
4. リファクタする

のような感じでした。

1の定義ファイルと生成結果はrouter.goとrouter_gen.goのことです。

2のコードの解析などは初めてで完全に手探り状態だったので、まず次のコードでASTを表示してみました。

```go:main.go
package main

import (
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"os"
)

func main() {
	filename := os.Args[1]

	// ファイルごとのトークンの位置を記録するFileSetを作成する
	fset := token.NewFileSet()

	// ファイル単位で構文解析を行う
	f, err := parser.ParseFile(fset, filename, nil, 0)
	if err != nil {
		log.Fatal("Error:", err)
	}

	ast.Print(fset, f)
}
```

これをビルドして実行するとrouter.goのASTが見れます。

```shell:ビルドと実行

$ go build -o ast
$ ./ast router.go
...
```

<details><summary>実行結果 router.goのAST（長いので折りたたんでます）</summary><div>

```text
     0  *ast.File {
     1  .  Package: router.go:6:1
     2  .  Name: *ast.Ident {
     3  .  .  NamePos: router.go:6:9
     4  .  .  Name: "main"
     5  .  }
     6  .  Decls: []ast.Decl (len = 2) {
     7  .  .  0: *ast.GenDecl {
     8  .  .  .  TokPos: router.go:8:1
     9  .  .  .  Tok: import
    10  .  .  .  Lparen: router.go:8:8
    11  .  .  .  Specs: []ast.Spec (len = 3) {
    12  .  .  .  .  0: *ast.ImportSpec {
    13  .  .  .  .  .  Path: *ast.BasicLit {
    14  .  .  .  .  .  .  ValuePos: router.go:9:2
    15  .  .  .  .  .  .  Kind: STRING
    16  .  .  .  .  .  .  Value: "\"net/http\""
    17  .  .  .  .  .  }
    18  .  .  .  .  .  EndPos: -
    19  .  .  .  .  }
    20  .  .  .  .  1: *ast.ImportSpec {
    21  .  .  .  .  .  Path: *ast.BasicLit {
    22  .  .  .  .  .  .  ValuePos: router.go:11:2
    23  .  .  .  .  .  .  Kind: STRING
    24  .  .  .  .  .  .  Value: "\"github.com/tetsuzawa/stdrouter\""
    25  .  .  .  .  .  }
    26  .  .  .  .  .  EndPos: -
    27  .  .  .  .  }
    28  .  .  .  .  2: *ast.ImportSpec {
    29  .  .  .  .  .  Path: *ast.BasicLit {
    30  .  .  .  .  .  .  ValuePos: router.go:12:2
    31  .  .  .  .  .  .  Kind: STRING
    32  .  .  .  .  .  .  Value: "\"github.com/tetsuzawa/stdrouter/_example/handler\""
    33  .  .  .  .  .  }
    34  .  .  .  .  .  EndPos: -
    35  .  .  .  .  }
    36  .  .  .  }
    37  .  .  .  Rparen: router.go:13:1
    38  .  .  }
    39  .  .  1: *ast.FuncDecl {
    40  .  .  .  Name: *ast.Ident {
    41  .  .  .  .  NamePos: router.go:16:6
    42  .  .  .  .  Name: "NewRouter"
    43  .  .  .  .  Obj: *ast.Object {
    44  .  .  .  .  .  Kind: func
    45  .  .  .  .  .  Name: "NewRouter"
    46  .  .  .  .  .  Decl: *(obj @ 39)
    47  .  .  .  .  }
    48  .  .  .  }
    49  .  .  .  Type: *ast.FuncType {
    50  .  .  .  .  Func: router.go:16:1
    51  .  .  .  .  Params: *ast.FieldList {
    52  .  .  .  .  .  Opening: router.go:16:15
    53  .  .  .  .  .  Closing: router.go:16:16
    54  .  .  .  .  }
    55  .  .  .  .  Results: *ast.FieldList {
    56  .  .  .  .  .  Opening: -
    57  .  .  .  .  .  List: []*ast.Field (len = 1) {
    58  .  .  .  .  .  .  0: *ast.Field {
    59  .  .  .  .  .  .  .  Type: *ast.SelectorExpr {
    60  .  .  .  .  .  .  .  .  X: *ast.Ident {
    61  .  .  .  .  .  .  .  .  .  NamePos: router.go:16:18
    62  .  .  .  .  .  .  .  .  .  Name: "http"
    63  .  .  .  .  .  .  .  .  }
    64  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
    65  .  .  .  .  .  .  .  .  .  NamePos: router.go:16:23
    66  .  .  .  .  .  .  .  .  .  Name: "Handler"
    67  .  .  .  .  .  .  .  .  }
    68  .  .  .  .  .  .  .  }
    69  .  .  .  .  .  .  }
    70  .  .  .  .  .  }
    71  .  .  .  .  .  Closing: -
    72  .  .  .  .  }
    73  .  .  .  }
    74  .  .  .  Body: *ast.BlockStmt {
    75  .  .  .  .  Lbrace: router.go:16:31
    76  .  .  .  .  List: []ast.Stmt (len = 15) {
    77  .  .  .  .  .  0: *ast.AssignStmt {
    78  .  .  .  .  .  .  Lhs: []ast.Expr (len = 1) {
    79  .  .  .  .  .  .  .  0: *ast.Ident {
    80  .  .  .  .  .  .  .  .  NamePos: router.go:17:2
    81  .  .  .  .  .  .  .  .  Name: "r"
    82  .  .  .  .  .  .  .  .  Obj: *ast.Object {
    83  .  .  .  .  .  .  .  .  .  Kind: var
    84  .  .  .  .  .  .  .  .  .  Name: "r"
    85  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 77)
    86  .  .  .  .  .  .  .  .  }
    87  .  .  .  .  .  .  .  }
    88  .  .  .  .  .  .  }
    89  .  .  .  .  .  .  TokPos: router.go:17:4
    90  .  .  .  .  .  .  Tok: :=
    91  .  .  .  .  .  .  Rhs: []ast.Expr (len = 1) {
    92  .  .  .  .  .  .  .  0: *ast.CallExpr {
    93  .  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
    94  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
    95  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:17:7
    96  .  .  .  .  .  .  .  .  .  .  Name: "stdrouter"
    97  .  .  .  .  .  .  .  .  .  }
    98  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
    99  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:17:17
   100  .  .  .  .  .  .  .  .  .  .  Name: "NewRouter"
   101  .  .  .  .  .  .  .  .  .  }
   102  .  .  .  .  .  .  .  .  }
   103  .  .  .  .  .  .  .  .  Lparen: router.go:17:26
   104  .  .  .  .  .  .  .  .  Ellipsis: -
   105  .  .  .  .  .  .  .  .  Rparen: router.go:17:27
   106  .  .  .  .  .  .  .  }
   107  .  .  .  .  .  .  }
   108  .  .  .  .  .  }
   109  .  .  .  .  .  1: *ast.ExprStmt {
   110  .  .  .  .  .  .  X: *ast.CallExpr {
   111  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   112  .  .  .  .  .  .  .  .  X: *ast.Ident {
   113  .  .  .  .  .  .  .  .  .  NamePos: router.go:18:2
   114  .  .  .  .  .  .  .  .  .  Name: "r"
   115  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   116  .  .  .  .  .  .  .  .  }
   117  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   118  .  .  .  .  .  .  .  .  .  NamePos: router.go:18:4
   119  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   120  .  .  .  .  .  .  .  .  }
   121  .  .  .  .  .  .  .  }
   122  .  .  .  .  .  .  .  Lparen: router.go:18:14
   123  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   124  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   125  .  .  .  .  .  .  .  .  .  ValuePos: router.go:18:15
   126  .  .  .  .  .  .  .  .  .  Kind: STRING
   127  .  .  .  .  .  .  .  .  .  Value: "\"/\""
   128  .  .  .  .  .  .  .  .  }
   129  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   130  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   131  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:18:20
   132  .  .  .  .  .  .  .  .  .  .  Name: "http"
   133  .  .  .  .  .  .  .  .  .  }
   134  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   135  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:18:25
   136  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   137  .  .  .  .  .  .  .  .  .  }
   138  .  .  .  .  .  .  .  .  }
   139  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   140  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   141  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:18:36
   142  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   143  .  .  .  .  .  .  .  .  .  }
   144  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   145  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:18:44
   146  .  .  .  .  .  .  .  .  .  .  Name: "GetRoot"
   147  .  .  .  .  .  .  .  .  .  }
   148  .  .  .  .  .  .  .  .  }
   149  .  .  .  .  .  .  .  }
   150  .  .  .  .  .  .  .  Ellipsis: -
   151  .  .  .  .  .  .  .  Rparen: router.go:18:51
   152  .  .  .  .  .  .  }
   153  .  .  .  .  .  }
   154  .  .  .  .  .  2: *ast.ExprStmt {
   155  .  .  .  .  .  .  X: *ast.CallExpr {
   156  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   157  .  .  .  .  .  .  .  .  X: *ast.Ident {
   158  .  .  .  .  .  .  .  .  .  NamePos: router.go:19:2
   159  .  .  .  .  .  .  .  .  .  Name: "r"
   160  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   161  .  .  .  .  .  .  .  .  }
   162  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   163  .  .  .  .  .  .  .  .  .  NamePos: router.go:19:4
   164  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   165  .  .  .  .  .  .  .  .  }
   166  .  .  .  .  .  .  .  }
   167  .  .  .  .  .  .  .  Lparen: router.go:19:14
   168  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   169  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   170  .  .  .  .  .  .  .  .  .  ValuePos: router.go:19:15
   171  .  .  .  .  .  .  .  .  .  Kind: STRING
   172  .  .  .  .  .  .  .  .  .  Value: "\"/api\""
   173  .  .  .  .  .  .  .  .  }
   174  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   175  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   176  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:19:23
   177  .  .  .  .  .  .  .  .  .  .  Name: "http"
   178  .  .  .  .  .  .  .  .  .  }
   179  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   180  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:19:28
   181  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   182  .  .  .  .  .  .  .  .  .  }
   183  .  .  .  .  .  .  .  .  }
   184  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   185  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   186  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:19:39
   187  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   188  .  .  .  .  .  .  .  .  .  }
   189  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   190  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:19:47
   191  .  .  .  .  .  .  .  .  .  .  Name: "GetAPIRoot"
   192  .  .  .  .  .  .  .  .  .  }
   193  .  .  .  .  .  .  .  .  }
   194  .  .  .  .  .  .  .  }
   195  .  .  .  .  .  .  .  Ellipsis: -
   196  .  .  .  .  .  .  .  Rparen: router.go:19:57
   197  .  .  .  .  .  .  }
   198  .  .  .  .  .  }
   199  .  .  .  .  .  3: *ast.ExprStmt {
   200  .  .  .  .  .  .  X: *ast.CallExpr {
   201  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   202  .  .  .  .  .  .  .  .  X: *ast.Ident {
   203  .  .  .  .  .  .  .  .  .  NamePos: router.go:20:2
   204  .  .  .  .  .  .  .  .  .  Name: "r"
   205  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   206  .  .  .  .  .  .  .  .  }
   207  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   208  .  .  .  .  .  .  .  .  .  NamePos: router.go:20:4
   209  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   210  .  .  .  .  .  .  .  .  }
   211  .  .  .  .  .  .  .  }
   212  .  .  .  .  .  .  .  Lparen: router.go:20:14
   213  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   214  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   215  .  .  .  .  .  .  .  .  .  ValuePos: router.go:20:15
   216  .  .  .  .  .  .  .  .  .  Kind: STRING
   217  .  .  .  .  .  .  .  .  .  Value: "\"/api/users\""
   218  .  .  .  .  .  .  .  .  }
   219  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   220  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   221  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:20:29
   222  .  .  .  .  .  .  .  .  .  .  Name: "http"
   223  .  .  .  .  .  .  .  .  .  }
   224  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   225  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:20:34
   226  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   227  .  .  .  .  .  .  .  .  .  }
   228  .  .  .  .  .  .  .  .  }
   229  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   230  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   231  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:20:45
   232  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   233  .  .  .  .  .  .  .  .  .  }
   234  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   235  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:20:53
   236  .  .  .  .  .  .  .  .  .  .  Name: "GetUsers"
   237  .  .  .  .  .  .  .  .  .  }
   238  .  .  .  .  .  .  .  .  }
   239  .  .  .  .  .  .  .  }
   240  .  .  .  .  .  .  .  Ellipsis: -
   241  .  .  .  .  .  .  .  Rparen: router.go:20:61
   242  .  .  .  .  .  .  }
   243  .  .  .  .  .  }
   244  .  .  .  .  .  4: *ast.ExprStmt {
   245  .  .  .  .  .  .  X: *ast.CallExpr {
   246  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   247  .  .  .  .  .  .  .  .  X: *ast.Ident {
   248  .  .  .  .  .  .  .  .  .  NamePos: router.go:21:2
   249  .  .  .  .  .  .  .  .  .  Name: "r"
   250  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   251  .  .  .  .  .  .  .  .  }
   252  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   253  .  .  .  .  .  .  .  .  .  NamePos: router.go:21:4
   254  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   255  .  .  .  .  .  .  .  .  }
   256  .  .  .  .  .  .  .  }
   257  .  .  .  .  .  .  .  Lparen: router.go:21:14
   258  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   259  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   260  .  .  .  .  .  .  .  .  .  ValuePos: router.go:21:15
   261  .  .  .  .  .  .  .  .  .  Kind: STRING
   262  .  .  .  .  .  .  .  .  .  Value: "\"/api/products\""
   263  .  .  .  .  .  .  .  .  }
   264  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   265  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   266  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:21:32
   267  .  .  .  .  .  .  .  .  .  .  Name: "http"
   268  .  .  .  .  .  .  .  .  .  }
   269  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   270  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:21:37
   271  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   272  .  .  .  .  .  .  .  .  .  }
   273  .  .  .  .  .  .  .  .  }
   274  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   275  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   276  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:21:48
   277  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   278  .  .  .  .  .  .  .  .  .  }
   279  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   280  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:21:56
   281  .  .  .  .  .  .  .  .  .  .  Name: "GetProducts"
   282  .  .  .  .  .  .  .  .  .  }
   283  .  .  .  .  .  .  .  .  }
   284  .  .  .  .  .  .  .  }
   285  .  .  .  .  .  .  .  Ellipsis: -
   286  .  .  .  .  .  .  .  Rparen: router.go:21:67
   287  .  .  .  .  .  .  }
   288  .  .  .  .  .  }
   289  .  .  .  .  .  5: *ast.ExprStmt {
   290  .  .  .  .  .  .  X: *ast.CallExpr {
   291  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   292  .  .  .  .  .  .  .  .  X: *ast.Ident {
   293  .  .  .  .  .  .  .  .  .  NamePos: router.go:22:2
   294  .  .  .  .  .  .  .  .  .  Name: "r"
   295  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   296  .  .  .  .  .  .  .  .  }
   297  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   298  .  .  .  .  .  .  .  .  .  NamePos: router.go:22:4
   299  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   300  .  .  .  .  .  .  .  .  }
   301  .  .  .  .  .  .  .  }
   302  .  .  .  .  .  .  .  Lparen: router.go:22:14
   303  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   304  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   305  .  .  .  .  .  .  .  .  .  ValuePos: router.go:22:15
   306  .  .  .  .  .  .  .  .  .  Kind: STRING
   307  .  .  .  .  .  .  .  .  .  Value: "\"/api/products\""
   308  .  .  .  .  .  .  .  .  }
   309  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   310  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   311  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:22:32
   312  .  .  .  .  .  .  .  .  .  .  Name: "http"
   313  .  .  .  .  .  .  .  .  .  }
   314  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   315  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:22:37
   316  .  .  .  .  .  .  .  .  .  .  Name: "MethodPost"
   317  .  .  .  .  .  .  .  .  .  }
   318  .  .  .  .  .  .  .  .  }
   319  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   320  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   321  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:22:49
   322  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   323  .  .  .  .  .  .  .  .  .  }
   324  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   325  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:22:57
   326  .  .  .  .  .  .  .  .  .  .  Name: "CreateProducts"
   327  .  .  .  .  .  .  .  .  .  }
   328  .  .  .  .  .  .  .  .  }
   329  .  .  .  .  .  .  .  }
   330  .  .  .  .  .  .  .  Ellipsis: -
   331  .  .  .  .  .  .  .  Rparen: router.go:22:71
   332  .  .  .  .  .  .  }
   333  .  .  .  .  .  }
   334  .  .  .  .  .  6: *ast.ExprStmt {
   335  .  .  .  .  .  .  X: *ast.CallExpr {
   336  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   337  .  .  .  .  .  .  .  .  X: *ast.Ident {
   338  .  .  .  .  .  .  .  .  .  NamePos: router.go:23:2
   339  .  .  .  .  .  .  .  .  .  Name: "r"
   340  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   341  .  .  .  .  .  .  .  .  }
   342  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   343  .  .  .  .  .  .  .  .  .  NamePos: router.go:23:4
   344  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   345  .  .  .  .  .  .  .  .  }
   346  .  .  .  .  .  .  .  }
   347  .  .  .  .  .  .  .  Lparen: router.go:23:14
   348  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   349  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   350  .  .  .  .  .  .  .  .  .  ValuePos: router.go:23:15
   351  .  .  .  .  .  .  .  .  .  Kind: STRING
   352  .  .  .  .  .  .  .  .  .  Value: "\"/api/users/create\""
   353  .  .  .  .  .  .  .  .  }
   354  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   355  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   356  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:23:36
   357  .  .  .  .  .  .  .  .  .  .  Name: "http"
   358  .  .  .  .  .  .  .  .  .  }
   359  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   360  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:23:41
   361  .  .  .  .  .  .  .  .  .  .  Name: "MethodPost"
   362  .  .  .  .  .  .  .  .  .  }
   363  .  .  .  .  .  .  .  .  }
   364  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   365  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   366  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:23:53
   367  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   368  .  .  .  .  .  .  .  .  .  }
   369  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   370  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:23:61
   371  .  .  .  .  .  .  .  .  .  .  Name: "CreateUser"
   372  .  .  .  .  .  .  .  .  .  }
   373  .  .  .  .  .  .  .  .  }
   374  .  .  .  .  .  .  .  }
   375  .  .  .  .  .  .  .  Ellipsis: -
   376  .  .  .  .  .  .  .  Rparen: router.go:23:71
   377  .  .  .  .  .  .  }
   378  .  .  .  .  .  }
   379  .  .  .  .  .  7: *ast.ExprStmt {
   380  .  .  .  .  .  .  X: *ast.CallExpr {
   381  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   382  .  .  .  .  .  .  .  .  X: *ast.Ident {
   383  .  .  .  .  .  .  .  .  .  NamePos: router.go:24:2
   384  .  .  .  .  .  .  .  .  .  Name: "r"
   385  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   386  .  .  .  .  .  .  .  .  }
   387  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   388  .  .  .  .  .  .  .  .  .  NamePos: router.go:24:4
   389  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   390  .  .  .  .  .  .  .  .  }
   391  .  .  .  .  .  .  .  }
   392  .  .  .  .  .  .  .  Lparen: router.go:24:14
   393  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   394  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   395  .  .  .  .  .  .  .  .  .  ValuePos: router.go:24:15
   396  .  .  .  .  .  .  .  .  .  Kind: STRING
   397  .  .  .  .  .  .  .  .  .  Value: "\"/api/users/:user_id\""
   398  .  .  .  .  .  .  .  .  }
   399  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   400  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   401  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:24:38
   402  .  .  .  .  .  .  .  .  .  .  Name: "http"
   403  .  .  .  .  .  .  .  .  .  }
   404  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   405  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:24:43
   406  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   407  .  .  .  .  .  .  .  .  .  }
   408  .  .  .  .  .  .  .  .  }
   409  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   410  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   411  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:24:54
   412  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   413  .  .  .  .  .  .  .  .  .  }
   414  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   415  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:24:62
   416  .  .  .  .  .  .  .  .  .  .  Name: "GetUser"
   417  .  .  .  .  .  .  .  .  .  }
   418  .  .  .  .  .  .  .  .  }
   419  .  .  .  .  .  .  .  }
   420  .  .  .  .  .  .  .  Ellipsis: -
   421  .  .  .  .  .  .  .  Rparen: router.go:24:69
   422  .  .  .  .  .  .  }
   423  .  .  .  .  .  }
   424  .  .  .  .  .  8: *ast.ExprStmt {
   425  .  .  .  .  .  .  X: *ast.CallExpr {
   426  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   427  .  .  .  .  .  .  .  .  X: *ast.Ident {
   428  .  .  .  .  .  .  .  .  .  NamePos: router.go:25:2
   429  .  .  .  .  .  .  .  .  .  Name: "r"
   430  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   431  .  .  .  .  .  .  .  .  }
   432  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   433  .  .  .  .  .  .  .  .  .  NamePos: router.go:25:4
   434  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   435  .  .  .  .  .  .  .  .  }
   436  .  .  .  .  .  .  .  }
   437  .  .  .  .  .  .  .  Lparen: router.go:25:14
   438  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   439  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   440  .  .  .  .  .  .  .  .  .  ValuePos: router.go:25:15
   441  .  .  .  .  .  .  .  .  .  Kind: STRING
   442  .  .  .  .  .  .  .  .  .  Value: "\"/api/users/:user_id\""
   443  .  .  .  .  .  .  .  .  }
   444  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   445  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   446  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:25:38
   447  .  .  .  .  .  .  .  .  .  .  Name: "http"
   448  .  .  .  .  .  .  .  .  .  }
   449  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   450  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:25:43
   451  .  .  .  .  .  .  .  .  .  .  Name: "MethodPatch"
   452  .  .  .  .  .  .  .  .  .  }
   453  .  .  .  .  .  .  .  .  }
   454  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   455  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   456  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:25:56
   457  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   458  .  .  .  .  .  .  .  .  .  }
   459  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   460  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:25:64
   461  .  .  .  .  .  .  .  .  .  .  Name: "UpdateUser"
   462  .  .  .  .  .  .  .  .  .  }
   463  .  .  .  .  .  .  .  .  }
   464  .  .  .  .  .  .  .  }
   465  .  .  .  .  .  .  .  Ellipsis: -
   466  .  .  .  .  .  .  .  Rparen: router.go:25:74
   467  .  .  .  .  .  .  }
   468  .  .  .  .  .  }
   469  .  .  .  .  .  9: *ast.ExprStmt {
   470  .  .  .  .  .  .  X: *ast.CallExpr {
   471  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   472  .  .  .  .  .  .  .  .  X: *ast.Ident {
   473  .  .  .  .  .  .  .  .  .  NamePos: router.go:26:2
   474  .  .  .  .  .  .  .  .  .  Name: "r"
   475  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   476  .  .  .  .  .  .  .  .  }
   477  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   478  .  .  .  .  .  .  .  .  .  NamePos: router.go:26:4
   479  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   480  .  .  .  .  .  .  .  .  }
   481  .  .  .  .  .  .  .  }
   482  .  .  .  .  .  .  .  Lparen: router.go:26:14
   483  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   484  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   485  .  .  .  .  .  .  .  .  .  ValuePos: router.go:26:15
   486  .  .  .  .  .  .  .  .  .  Kind: STRING
   487  .  .  .  .  .  .  .  .  .  Value: "\"/api/users/:user_id\""
   488  .  .  .  .  .  .  .  .  }
   489  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   490  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   491  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:26:38
   492  .  .  .  .  .  .  .  .  .  .  Name: "http"
   493  .  .  .  .  .  .  .  .  .  }
   494  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   495  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:26:43
   496  .  .  .  .  .  .  .  .  .  .  Name: "MethodDelete"
   497  .  .  .  .  .  .  .  .  .  }
   498  .  .  .  .  .  .  .  .  }
   499  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   500  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   501  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:26:57
   502  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   503  .  .  .  .  .  .  .  .  .  }
   504  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   505  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:26:65
   506  .  .  .  .  .  .  .  .  .  .  Name: "DeleteUser"
   507  .  .  .  .  .  .  .  .  .  }
   508  .  .  .  .  .  .  .  .  }
   509  .  .  .  .  .  .  .  }
   510  .  .  .  .  .  .  .  Ellipsis: -
   511  .  .  .  .  .  .  .  Rparen: router.go:26:75
   512  .  .  .  .  .  .  }
   513  .  .  .  .  .  }
   514  .  .  .  .  .  10: *ast.ExprStmt {
   515  .  .  .  .  .  .  X: *ast.CallExpr {
   516  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   517  .  .  .  .  .  .  .  .  X: *ast.Ident {
   518  .  .  .  .  .  .  .  .  .  NamePos: router.go:27:2
   519  .  .  .  .  .  .  .  .  .  Name: "r"
   520  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   521  .  .  .  .  .  .  .  .  }
   522  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   523  .  .  .  .  .  .  .  .  .  NamePos: router.go:27:4
   524  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   525  .  .  .  .  .  .  .  .  }
   526  .  .  .  .  .  .  .  }
   527  .  .  .  .  .  .  .  Lparen: router.go:27:14
   528  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   529  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   530  .  .  .  .  .  .  .  .  .  ValuePos: router.go:27:15
   531  .  .  .  .  .  .  .  .  .  Kind: STRING
   532  .  .  .  .  .  .  .  .  .  Value: "\"/api/users/:user_id/posts\""
   533  .  .  .  .  .  .  .  .  }
   534  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   535  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   536  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:27:44
   537  .  .  .  .  .  .  .  .  .  .  Name: "http"
   538  .  .  .  .  .  .  .  .  .  }
   539  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   540  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:27:49
   541  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   542  .  .  .  .  .  .  .  .  .  }
   543  .  .  .  .  .  .  .  .  }
   544  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   545  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   546  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:27:60
   547  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   548  .  .  .  .  .  .  .  .  .  }
   549  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   550  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:27:68
   551  .  .  .  .  .  .  .  .  .  .  Name: "GetPosts"
   552  .  .  .  .  .  .  .  .  .  }
   553  .  .  .  .  .  .  .  .  }
   554  .  .  .  .  .  .  .  }
   555  .  .  .  .  .  .  .  Ellipsis: -
   556  .  .  .  .  .  .  .  Rparen: router.go:27:76
   557  .  .  .  .  .  .  }
   558  .  .  .  .  .  }
   559  .  .  .  .  .  11: *ast.ExprStmt {
   560  .  .  .  .  .  .  X: *ast.CallExpr {
   561  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   562  .  .  .  .  .  .  .  .  X: *ast.Ident {
   563  .  .  .  .  .  .  .  .  .  NamePos: router.go:28:2
   564  .  .  .  .  .  .  .  .  .  Name: "r"
   565  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   566  .  .  .  .  .  .  .  .  }
   567  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   568  .  .  .  .  .  .  .  .  .  NamePos: router.go:28:4
   569  .  .  .  .  .  .  .  .  .  Name: "HandleFunc"
   570  .  .  .  .  .  .  .  .  }
   571  .  .  .  .  .  .  .  }
   572  .  .  .  .  .  .  .  Lparen: router.go:28:14
   573  .  .  .  .  .  .  .  Args: []ast.Expr (len = 3) {
   574  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
   575  .  .  .  .  .  .  .  .  .  ValuePos: router.go:28:15
   576  .  .  .  .  .  .  .  .  .  Kind: STRING
   577  .  .  .  .  .  .  .  .  .  Value: "\"/api/users/:user_id/posts/:post_id\""
   578  .  .  .  .  .  .  .  .  }
   579  .  .  .  .  .  .  .  .  1: *ast.SelectorExpr {
   580  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   581  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:28:53
   582  .  .  .  .  .  .  .  .  .  .  Name: "http"
   583  .  .  .  .  .  .  .  .  .  }
   584  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   585  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:28:58
   586  .  .  .  .  .  .  .  .  .  .  Name: "MethodGet"
   587  .  .  .  .  .  .  .  .  .  }
   588  .  .  .  .  .  .  .  .  }
   589  .  .  .  .  .  .  .  .  2: *ast.SelectorExpr {
   590  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   591  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:28:69
   592  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   593  .  .  .  .  .  .  .  .  .  }
   594  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   595  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:28:77
   596  .  .  .  .  .  .  .  .  .  .  Name: "GetPost"
   597  .  .  .  .  .  .  .  .  .  }
   598  .  .  .  .  .  .  .  .  }
   599  .  .  .  .  .  .  .  }
   600  .  .  .  .  .  .  .  Ellipsis: -
   601  .  .  .  .  .  .  .  Rparen: router.go:28:84
   602  .  .  .  .  .  .  }
   603  .  .  .  .  .  }
   604  .  .  .  .  .  12: *ast.ExprStmt {
   605  .  .  .  .  .  .  X: *ast.CallExpr {
   606  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   607  .  .  .  .  .  .  .  .  X: *ast.Ident {
   608  .  .  .  .  .  .  .  .  .  NamePos: router.go:29:2
   609  .  .  .  .  .  .  .  .  .  Name: "r"
   610  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   611  .  .  .  .  .  .  .  .  }
   612  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   613  .  .  .  .  .  .  .  .  .  NamePos: router.go:29:4
   614  .  .  .  .  .  .  .  .  .  Name: "HandleMethodNotAllowed"
   615  .  .  .  .  .  .  .  .  }
   616  .  .  .  .  .  .  .  }
   617  .  .  .  .  .  .  .  Lparen: router.go:29:26
   618  .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) {
   619  .  .  .  .  .  .  .  .  0: *ast.SelectorExpr {
   620  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   621  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:29:27
   622  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   623  .  .  .  .  .  .  .  .  .  }
   624  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   625  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:29:35
   626  .  .  .  .  .  .  .  .  .  .  Name: "MethodNotAllowedHandler"
   627  .  .  .  .  .  .  .  .  .  }
   628  .  .  .  .  .  .  .  .  }
   629  .  .  .  .  .  .  .  }
   630  .  .  .  .  .  .  .  Ellipsis: -
   631  .  .  .  .  .  .  .  Rparen: router.go:29:58
   632  .  .  .  .  .  .  }
   633  .  .  .  .  .  }
   634  .  .  .  .  .  13: *ast.ExprStmt {
   635  .  .  .  .  .  .  X: *ast.CallExpr {
   636  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
   637  .  .  .  .  .  .  .  .  X: *ast.Ident {
   638  .  .  .  .  .  .  .  .  .  NamePos: router.go:30:2
   639  .  .  .  .  .  .  .  .  .  Name: "r"
   640  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   641  .  .  .  .  .  .  .  .  }
   642  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   643  .  .  .  .  .  .  .  .  .  NamePos: router.go:30:4
   644  .  .  .  .  .  .  .  .  .  Name: "HandleNotFound"
   645  .  .  .  .  .  .  .  .  }
   646  .  .  .  .  .  .  .  }
   647  .  .  .  .  .  .  .  Lparen: router.go:30:18
   648  .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) {
   649  .  .  .  .  .  .  .  .  0: *ast.SelectorExpr {
   650  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
   651  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:30:19
   652  .  .  .  .  .  .  .  .  .  .  Name: "handler"
   653  .  .  .  .  .  .  .  .  .  }
   654  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
   655  .  .  .  .  .  .  .  .  .  .  NamePos: router.go:30:27
   656  .  .  .  .  .  .  .  .  .  .  Name: "NotFoundHandler"
   657  .  .  .  .  .  .  .  .  .  }
   658  .  .  .  .  .  .  .  .  }
   659  .  .  .  .  .  .  .  }
   660  .  .  .  .  .  .  .  Ellipsis: -
   661  .  .  .  .  .  .  .  Rparen: router.go:30:42
   662  .  .  .  .  .  .  }
   663  .  .  .  .  .  }
   664  .  .  .  .  .  14: *ast.ReturnStmt {
   665  .  .  .  .  .  .  Return: router.go:34:2
   666  .  .  .  .  .  .  Results: []ast.Expr (len = 1) {
   667  .  .  .  .  .  .  .  0: *ast.Ident {
   668  .  .  .  .  .  .  .  .  NamePos: router.go:34:9
   669  .  .  .  .  .  .  .  .  Name: "r"
   670  .  .  .  .  .  .  .  .  Obj: *(obj @ 82)
   671  .  .  .  .  .  .  .  }
   672  .  .  .  .  .  .  }
   673  .  .  .  .  .  }
   674  .  .  .  .  }
   675  .  .  .  .  Rbrace: router.go:35:1
   676  .  .  .  }
   677  .  .  }
   678  .  }
   679  .  Scope: *ast.Scope {
   680  .  .  Objects: map[string]*ast.Object (len = 1) {
   681  .  .  .  "NewRouter": *(obj @ 43)
   682  .  .  }
   683  .  }
   684  .  Imports: []*ast.ImportSpec (len = 3) {
   685  .  .  0: *(obj @ 12)
   686  .  .  1: *(obj @ 20)
   687  .  .  2: *(obj @ 28)
   688  .  }
   689  .  Unresolved: []*ast.Ident (len = 26) {
   690  .  .  0: *(obj @ 60)
   691  .  .  1: *(obj @ 94)
   692  .  .  2: *(obj @ 130)
   693  .  .  3: *(obj @ 140)
   694  .  .  4: *(obj @ 175)
   695  .  .  5: *(obj @ 185)
   696  .  .  6: *(obj @ 220)
   697  .  .  7: *(obj @ 230)
   698  .  .  8: *(obj @ 265)
   699  .  .  9: *(obj @ 275)
   700  .  .  10: *(obj @ 310)
   701  .  .  11: *(obj @ 320)
   702  .  .  12: *(obj @ 355)
   703  .  .  13: *(obj @ 365)
   704  .  .  14: *(obj @ 400)
   705  .  .  15: *(obj @ 410)
   706  .  .  16: *(obj @ 445)
   707  .  .  17: *(obj @ 455)
   708  .  .  18: *(obj @ 490)
   709  .  .  19: *(obj @ 500)
   710  .  .  20: *(obj @ 535)
   711  .  .  21: *(obj @ 545)
   712  .  .  22: *(obj @ 580)
   713  .  .  23: *(obj @ 590)
   714  .  .  24: *(obj @ 620)
   715  .  .  25: *(obj @ 650)
   716  .  }
   717  }
```

</div></details>


適切なルーティングのswitch文を生成するには、3のコード生成をする前にパスの木構造を把握する必要があります。


`go/ast`パッケージには`ast.Inspect`という関数が用意されていて、深さ優先でASTを探索することができます。

stdrouterではこの関数を使って順々に型や関数名を確認しながらパスの木構造を構築してます。

1. [/api/users, GET, GetUsers]を追加
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359782/55056eec-0c3f-68bb-74ac-1678cb367be2.jpeg" alt="tree_1.jpeg" width=700">
2. [/api/users/:user_id, GET, GetUser]を追加
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359782/620b6360-9311-a021-3025-7d217e89c678.jpeg" alt="tree_2.jpeg" width=700">
3. [/api/users/:user_id/posts, GET, GetPosts]を追加
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359782/9e1992a8-1dc8-e706-5499-b49918458c01.jpeg" alt="tree_3.jpeg" width=700">

パスの木構造が構築できたら、あとは泥臭くコードを生成していくだけです。

コードの生成には`text/template`をつかっています。

他には`fmt.Spintf`を使う方法やASTを直接操作して生成する方法などがあるようです。

作り終わってみると、そこまで難しいことをしているわけではないと感じました。

処理系の周りにふれるので勉強になりますし、goパッケージを使ってなにか作ってみるといいかも知れません。

# 参考

- [goパッケージで簡単に静的解析して世界を広げよう #golang](https://qiita.com/tenntenn/items/868704380455c5090d4b)  
  静的解析関連でやりたいことに対する道筋が示されてて助かりました。
- [github/yudppp/json_snake_case](https://github.com/yudppp/json_snake_case)  
  ASTの解析とコード生成の点を参考にしました。
- [github.com/google/wire](https://github.com/google/wire)  
  ディレクトリ構成やgo generateの使い方を参考にしました。
- [Goの標準パッケージだけでRESTfulなHandlerを作る](https://daisuzu.hatenablog.com/entry/2018/08/26/225739)  
  パスパラメータの扱いなどを参考にしました。