20190218のGoに関する記事は7件です。

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 ."
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)
}

https://play.golang.org/p/UBnzkD5Bb7L

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

xerrors - 関連情報

日本語情報

xerrors

error一般

2019/1/25 最新提案

今までのドラフトや議論を元に現在の最新の提案がまとめられている。
xerrorsパッケージとして実装も用意されている。以前との大きな違いは、Asメソッドがcontractを利用した実装ではなくなったこと。

Go 1.13からerrorsパッケージに組み込まれる予定である。

2018/8/27 初期提案

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 を実装しているため Istrue となる。

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 を実装していないため Isfalse となる。

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

関連情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go言語】うほうほ!!gorillaを分かりやすくまとめてみた

はじめに

出オチです。
gorillaとはgolang向けの有名なWEBツールキットです。
複数のパッケージで構成されていますが、今回はよく使用されるgorilla/mux(ルーティング処理機能)とgorilla/context(リクエストスコープで変数を保持しておく機能)を対象にします。
日本語の記事があまり多くなかったので、まとめてみました。

gorilla.png

インストール

$ go get -u github.com/gorilla/mux
$ go get -u github.com/gorilla/context

gorilla/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
foo

UT

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)
        }
    }
}

参考サイト

http://www.gorillatoolkit.org

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 $PATH

fishの場合の環境変数設定

string trim '
set -x GOPATH $HOME/go-third-party
set PATH $HOME/go/bin $PATH
set PATH $GOPATH/bin $PATH
' >> ~/.config/fish/config.fish
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 {
    // ??
}

関連情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む