4
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

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

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

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

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

ルーティングのお話

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

単純なパスのとき

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

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

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

gin
r := gin.Default()
r.GET("/users", GetHandler)
r.POST("/users", PostHandler)
echo
e := echo.New()
e.GET("/users", GetHandler)
e.POST("/users", PostHandler)
gorilla/mux
r := mux.NewRouter()
r.HandleFunc("/users", UsersHandler) 

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

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

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

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

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

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

func GetUserHandler(c echo.Context) {
    userId := c.Param("user_id") // ginとほぼ同じ
    /*...*/
}
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"]
    /*...*/
}

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

標準ライブラリ
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を扱ってみましょう。

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

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")
    /*...*/
}
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")
    /*...*/
}
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"]
    /*...*/
}

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

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

標準ライブラリ

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を使った例は、

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

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

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

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

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

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

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

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

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

標準ライブラリのサーバー(改善版)

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 してください。

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

使い方

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

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

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

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

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を実行する

$ stdrouter
stdrouter: Router file generated to router_gen.go

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

ディレクトリ構成(生成後)
.
├── handler
│   └── handler.go  
├── main.go         
├── router.go
└── router_gen.go  // 生成されたファイル

router_gen.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
}

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

$ go build -o server
$ ./server

使い方は以上です。

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

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

TODO

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

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

stdrouter作成の流れ

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

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

作り方の流れとしては

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

のような感じでした。

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

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

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が見れます。

ビルドと実行

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

実行結果 router.goのAST(長いので折りたたんでます)
     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  }

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

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

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

  1. [/api/users, GET, GetUsers]を追加 tree_1.jpeg
  2. [/api/users/:user_id, GET, GetUser]を追加 tree_2.jpeg
  3. [/api/users/:user_id/posts, GET, GetPosts]を追加 tree_3.jpeg

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

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

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

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

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

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
4
Help us understand the problem. What are the problem?