- 投稿日:2019-02-18T21:55:15+09:00
Goの文字列操作でよく使う関数
Goの文字列操作でよく使う関数を記載しています。
文字列の結合
strings.Join([]string{"I'm","love in it"}, " ") // "I'm love in it"文字列のカウント
strings.Count("Mac Mac Mac Mos", "Mac") // 3部分文字列の検索
strings.Contains("Macdonald", "Mac") // true // 指定した文字列のどれかが含まれるか strings.ContainsAny("Macdonald", "Md") // true // Prefixで検索 strings.HasPrefix("Macdonald", "Mac") // true // Suffixで検索 strings.HasSuffix("Macdonald", "donald") // true文字列の置換
strings.Replace("I love Mac", "Mac", "Mos", 1) // "I love Mos" strings.Replace("MacMacMac", "Mac", "Mos", 2) // "MosMosMac" strings.Replace("MacMacMac", "Mac", "Mos", -1) // "MosMosMos"文字列の分割
strings.Split("Mos,Mac,BergerKing,Lotteria,KFC", ",") // []string{"Mos", "Mac", "BergerKing", "Lotteria", "KFC"}大文字/小文字変換
strings.ToLower("MACDONALD") // "macdonald" strings.ToUpper("macdonald") // "MACDONALD"空白トリム
strings.TrimSpace(" Macdonald . ") // "Macdonald ."
- 投稿日:2019-02-18T18:03:11+09:00
interface{}型に特定のstructぶっこんでから置き換えってできるの?
できるはずなんだけど... 不安になったので、実験してみた。
結果:できた。
package main import ( "fmt" ) type a struct { ID string Something interface{} } type b struct { ID string } type c struct { ID string Param string } func main() { r := a{ ID: "test", Something: b{ ID: "hey gay!!", }, } other := c{ ID: "hey girl!!", Param: "you are awesome", } r.Something = other fmt.Printf("%+v\n", r) }
- 投稿日:2019-02-18T11:34:38+09:00
xerrors - 関連情報
日本語情報
xerrors
- Goの新しいerrors パッケージ xerrors(Go 1.13からは標準のerrorsパッケージに入る予定)
- xerrors - エラー設計の注意点
- xerrorsパッケージがWrapメソッドではなく : %w でラップする理由
- xerrors パッケージ - 独自に定義したエラー型はIsメソッドとAsメソッドでデフォルトの振る舞いを変更可能
- xerrors パッケージで途中に独自定義したエラー型をラップする方法
- ラップするライブラリ Github - sonatard/werror
- Go で静的解析して Go 1.13 から標準の xerrors とうまく付き合っていく
error一般
2019/1/25 最新提案
今までのドラフトや議論を元に現在の最新の提案がまとめられている。
xerrorsパッケージとして実装も用意されている。以前との大きな違いは、Asメソッドがcontractを利用した実装ではなくなったこと。Go 1.13からerrorsパッケージに組み込まれる予定である。
2018/8/27 初期提案
- エラー検査 設計のドラフト Error Inspection — Draft Design
- エラー出力 設計のドラフト Error Printing — Draft Design
- エラーの値 問題の概要 Error Values — Problem Overview
- ユーザからのフィードバック Go 2 Error Values Feedback
- 投稿日:2019-02-18T11:31:11+09:00
xerrors - エラー設計の注意点
トレースの情報を上位のレイヤーに渡すだけならパッケージで定義したエラーの型は公開しない
トレースの情報だけをラップして渡していきたい場合には、Formatを実装していればパッケージ内で独自に定義したエラータイプは非公開にして問題ありません。非公開の場合は、Isでマッチなどはすることはありません。しかし
Unwrap
を実装しないと、より下位にラップされた公開されているエラーに到達できなくなってしまうので注意が必要です。package pkg1 import ( "fmt" "golang.org/x/xerrors" ) // 非公開の型 type pkg1Error struct { msg string err error frame xerrors.Frame } func (e *pkg1Error) Error() string { return e.msg } // Unwrapメソッドは実装する func (e *pkg1Error) Unwrap() error { return e.err } func (e *pkg1Error) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) } func (e *pkg1Error) FormatError(p xerrors.Printer) (next error) { p.Print(e.Error()) e.frame.Format(p) return e.err } func UserSearch(uID string) (string, error) { pkgErr := &pkg1Error{ msg: "hello", err: nil, frame: xerrors.Caller(1), } return "", xerrors.Errorf("error user %v not found: %w", uID, pkgErr) }func main() { _, err := pkg1.UserSearch("12345") fmt.Printf("%+v\n", err) }正しく外部パッケージで定義されたエラーのトレース情報が出力されています。
error user 12345 not found: xerrors/pkg1.UserSearch /Users/sonatard/tmp/xerrors/pkg1/user.go:37 - hello: main.main /Users/sonatard/tmp/xerrors/main.go:9逆にIsやAsを利用したい場合には、型を公開しなければなりません。
自分で定義した型を他のパッケージに公開したいが、IsやAsでマッチさせたくない場合はUnwrap型を実装しない
Unwrap
を実装しないことで公開されているエラーの型をIsやAsでマッチさせなくすることができます。
またFormatを実装することで、Unwrap
を実装しなくてもエラーのトレースは出力することはできます。これは標準ライブラリで返ってきた型をラップしたいが、外部パッケージに公開はしたくないときに使います。
package pkg1 import ( "fmt" "io" "golang.org/x/xerrors" ) type Pkg1Error struct { msg string err error frame xerrors.Frame } func (e *Pkg1Error) Error() string { return e.msg } func (e *Pkg1Error) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) } func (e *Pkg1Error) FormatError(p xerrors.Printer) (next error) { p.Print(e.Error()) e.frame.Format(p) return e.err } func UserSearch(uID string) (string, error) { pkgErr := &Pkg1Error{ msg: "hello", err: xerrors.Errorf("error: %w", io.ErrUnexpectedEOF), // 標準ライブラリからエラーが返ってきた場合を想定 frame: xerrors.Caller(1), } return "", xerrors.Errorf("error user %v not found: %w", uID, pkgErr) }func (e *Pkg1Error) Unwrap() error { return e.err }func main() { _, err := pkg1.UserSearch("12345") fmt.Printf("%v\n", xerrors.Is(err, io.ErrUnexpectedEOF)) fmt.Printf("%+v\n", err) }
Unwrapの実装をしている場合の結果
Unwrap
を実装しているためIs
がtrue
となる。true error user 12345 not found: xerrors/pkg1.UserSearch /Users/sonatard/tmp/xerrors/pkg1/user.go:35 - hello: main.main /Users/sonatard/tmp/xerrors/main.go:31 - error: xerrors/pkg1.UserSearch /Users/sonatard/tmp/xerrors/pkg1/user.go:31 - unexpected EOF
Unwrap
を実装していない場合の結果
Unwrap
を実装していないためIs
がfalse
となる。
Unwrap
を実装していなくとも同じようにエラーのトレースは表示される。false error user 12345 not found: xerrors/pkg1.UserSearch /Users/sonatard/tmp/xerrors/pkg1/user.go:35 - hello: main.main /Users/sonatard/tmp/xerrors/main.go:31 - error: xerrors/pkg1.UserSearch /Users/sonatard/tmp/xerrors/pkg1/user.go:31 - unexpected EOF関連情報
- 投稿日:2019-02-18T07:52:13+09:00
【Go言語】うほうほ!!gorillaを分かりやすくまとめてみた
はじめに
出オチです。
gorillaとはgolang向けの有名なWEBツールキットです。
複数のパッケージで構成されていますが、今回はよく使用されるgorilla/mux(ルーティング処理機能)とgorilla/context(リクエストスコープで変数を保持しておく機能)を対象にします。
日本語の記事があまり多くなかったので、まとめてみました。インストール
$ go get -u github.com/gorilla/mux $ go get -u github.com/gorilla/contextgorilla/mux
NewRouter
muxでは
*Router
型(NewRouter関数の返り値)のメソッドを色々操作することがメインになります。
そのため、まずはNewRouter関数で*Router
型の変数を作ることが前提として必要になります。r := mux.NewRouter()*Router型メソッド
扱えるメソッドは以下です。
- HandleFunc
- MatcherFunc
- Use
- PathPrefix
- Methods
- Schemes
- Headers
- Queries
この記事では、HandleFuncとUseを具体例で使用していきます。
以下のように複数組み合わせて使用することもできます。r.HandleFunc("/products", ProductsHandler). Host("www.example.com"). Methods("GET"). Schemes("http")Vars
クエリパラメータの値を取得する際にVars関数を利用します。
Vars関数はmap[string]string
型を返すため、クエリパラメータをkey(string)/value(string)形式で保持します。vars := mux.Vars(r)具体例
クエリパラメータ無し版
一番シンプルなパターン。
HandleFuncメソッドの第一引数にパス、第二引数にハンドラーを指定することで、ルーティングされます。package main import ( "fmt" "log" "net/http" "time" "github.com/gorilla/mux" ) func sampleHandler1(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello sample") } func main() { // ルーティング設定 r := mux.NewRouter() r.HandleFunc("/sample1", sampleHandler1) // サーバ設定 srv := &http.Server{ Handler: r, Addr: "127.0.0.1:8000", WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } // 起動 log.Fatal(srv.ListenAndServe()) }実行結果$ curl http://localhost:8000/sample1 hello sampleクエリパラメータ有り版
クエリパラメータを扱う場合は、
{name}
あるいは{name:}
の形式でURLパスに変数を埋め込みます。package main import ( "fmt" "log" "net/http" "time" "github.com/gorilla/mux" ) func sampleHandler2(w http.ResponseWriter, r *http.Request) { // クエリパラメータの取得 vars := mux.Vars(r) fmt.Fprintf(w, vars["name"]) } func main() { r := mux.NewRouter() // クエリパラメータ指定 r.HandleFunc("/sample2/{name}", sampleHandler2) srv := &http.Server{ Handler: r, Addr: "127.0.0.1:8000", WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } log.Fatal(srv.ListenAndServe()) }実行結果$ curl http://localhost:8000/sample2/gold-kou gold-kouミドルウェア
golangではHTTPリクエストを処理する際に必要な共通処理(ロギング、認証、panicリカバリなど)をそれぞれミドルウェアとして定義し、それらを連鎖させます。
gorillaではその定義したミドルウェアをUse関数で簡単に適用できます。func sampleMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here log.Println(r.RequestURI) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) }) }r := mux.NewRouter() r.HandleFunc("/", handler) // ミドルウェアの適用 r.Use(sampleMiddleware)gorilla/context
リクエストスコープで変数を保持します。
Set関数で値を格納して、Get関数で値を取得します。具体例
変数aをリクエストスコープで扱う例です。
package main import ( "fmt" "log" "net/http" "time" "github.com/gorilla/context" "github.com/gorilla/mux" ) // contextで扱う変数 var a string func setA(w http.ResponseWriter, r *http.Request) { // 変数に値をセット context.Set(r, a, "foo") } func sampleContextHandler(w http.ResponseWriter, r *http.Request) { setA(w, r) // 変数の値を取得 val := context.Get(r, a) fmt.Fprintf(w, val.(string)) } func main() { r := mux.NewRouter() r.HandleFunc("/sampleContext", sampleContextHandler) srv := &http.Server{ Handler: r, Addr: "127.0.0.1:8000", WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } log.Fatal(srv.ListenAndServe()) }実行結果$ curl http://localhost:8000/sampleContext fooUT
UTも大体パターン化されています。
リクエスト準備
http.NewRequest
関数の第一引数にHTTPメソッド、第二引数にURL、第三引数にパラメータ(無い場合はnil)を渡し、戻り値(*Request
型)を取得する。req, err := http.NewRequest("GET", "/health", nil)レスポンス準備
httptest.NewRecorder
関数の戻り値(*ResponseRecorder
型)を取得する。
ResponseRecorderはフィールドにBodyやCodeを持つ構造体である。rr := httptest.NewRecorder()リクエスト実行
パラメータ無し版
http.HandlerFunc
関数でテスト対象の関数を指定し、ServeHTTP
メソッドでリクエスト実行する。handler := http.HandlerFunc(HealthCheckHandler) handler.ServeHTTP(rr, req)パラメータ有り版
パラメータを渡す場合は、httpパッケージでなく
mux.NewRouter
メソッドの返り値(*Router
型)を使う必要がある。router := mux.NewRouter() router.HandleFunc("/metrics/{type}", MetricsHandler) router.ServeHTTP(rr, req)具体例
クエリパラメータ無し版
被テストコードpackage main import ( "io" "log" "net/http" "github.com/gorilla/mux" ) func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) io.WriteString(w, `{"alive": true}`) } func main() { r := mux.NewRouter() r.HandleFunc("/health", HealthCheckHandler) log.Fatal(http.ListenAndServe("localhost:8080", r)) }テストコードpackage main import ( "net/http" "net/http/httptest" "testing" ) func TestHealthCheckHandler(t *testing.T) { // リクエスト準備 req, err := http.NewRequest("GET", "/health", nil) if err != nil { t.Fatal(err) } // レスポンス準備 rr := httptest.NewRecorder() // リクエスト実行 handler := http.HandlerFunc(HealthCheckHandler) handler.ServeHTTP(rr, req) // レスポンスのステータスコードが期待通りか確認 if status := rr.Code; status != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) } // レスポンスのボディが期待通りか確認 expected := `{"alive": true}` if rr.Body.String() != expected { t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected) } }クエリパラメータ有り版
被テストコード(一部)func main() { r := mux.NewRouter() // A route with a route variable: r.HandleFunc("/metrics/{type}", MetricsHandler) log.Fatal(http.ListenAndServe("localhost:8080", r)) }テストコード(一部)func TestMetricsHandler(t *testing.T) { // テストデータ群 tt := []struct{ routeVariable string shouldPass bool }{ {"goroutines", true}, {"heap", true}, {"counters", true}, {"queries", true}, {"adhadaeqm3k", false}, } // 各テストデータごとにテスト for _, tc := range tt { // リクエスト準備 path := fmt.Sprintf("/metrics/%s", tc.routeVariable) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } // レスポンス準備 rr := httptest.NewRecorder() // リクエスト実行 router := mux.NewRouter() router.HandleFunc("/metrics/{type}", MetricsHandler) router.ServeHTTP(rr, req) // 照合処理 if rr.Code == http.StatusOK && !tc.shouldPass { t.Errorf("handler should have failed on routeVariable %s: got %v want %v", tc.routeVariable, rr.Code, http.StatusOK) } } }参考サイト
- 投稿日:2019-02-18T03:45:34+09:00
Golang Install on Linux
Go 1.11.5
Go Install Linux
mkdir $HOME/go wget -qO- "https://dl.google.com/go/go1.11.5.linux-amd64.tar.gz" | tar -zx --strip-components=1 -C $HOME/go mkdir $HOME/go-third-party set -x GOPATH $HOME/go-third-party set PATH $HOME/go/bin $PATH set PATH $GOPATH/bin $PATHfishの場合の環境変数設定
string trim ' set -x GOPATH $HOME/go-third-party set PATH $HOME/go/bin $PATH set PATH $GOPATH/bin $PATH ' >> ~/.config/fish/config.fish
- 投稿日:2019-02-18T00:52:00+09:00
xerrors パッケージ - 独自に定義したエラー型はIsメソッドとAsメソッドでデフォルトの振る舞いを変更可能
xerrors パッケージは、 独自に定義したエラー型はIsメソッドとAsメソッドでデフォルトの振る舞いを変更可能です。
Isメソッド
この例ではErrUserNotFound1とErrUserNotFound2のcodeは同じ
101
でlevelとmsgは異なるものをxerrors.Is
メソッドで比較しています。結果はtrue
となっています。 これはfunc (e *ApplicationError) Is(err error) bool
を定義し、codeだけで判定しているためです。var ErrUserNotFound1 = &ApplicationError{ code: 101, level: "Error", msg: "not found", } var ErrUserNotFound2 = &ApplicationError{ code: 101, level: "Fatal", msg: "not found!!!!!!", } type ApplicationError struct { level string code int msg string } func (e *ApplicationError) Is(err error) bool { var appErr *ApplicationError return xerrors.As(err, &appErr) && e.code == appErr.code } func (e *ApplicationError) Error() string { return fmt.Sprintf("%s: code=%d, msg=%s", e.level, e.code, e.msg) } func main() { fmt.Printf("%v\n", xerrors.Is(ErrUserNotFound1, ErrUserNotFound2)) }Asメソッド
Asメソッドも型に合わせて振る舞いを変えられますが、ユースケースが想像できませんでした。
func (e *ApplicationError) As(target interface{}) bool { // ?? }関連情報