布団の中にいたい

Elasticsearchいじったり、Androidアプリ書いたり。最近は数学の勉強が楽しくなってきました。

go-playground/validatorでomitemptyを使う

go-playground/validatorを使って、空の値を許容するクエリパラメータのvalidationをしようとして微妙にハマったのでメモ。

go-playground/validatorのリポジトリは以下。

https://github.com/go-playground/validator

go-playground/validatorを使う場合、まずクエリパラメータに対応する構造体を定義して、validationをかけたい要素に対して、validateタグを付加する形になります。

validatorのexampleにもありますが、だいたいこんな感じ。

type QueryParam struct {
    A string `json:"a" validate:"required"`
    B string `json:"b" validate:"max=10"`
}

このときに、requiredをvalidateタグに使わなくても基本的にrequiredになるので、例えばクエリパラメータとして投げられたときはvalidationしたいけど、空のときはそのまま通したいという場合があって微妙に困りました。そんななかdocumentを漁った結果よさそうなものを発見。

https://godoc.org/gopkg.in/go-playground/validator.v9#hdr-Omit_Empty

ドキュメントにそのまま書かれている通り、validateタグにomitemptyを付加すれば、値がセットされていないときはvalidationが走らないようになりました。

エラーの分岐にpkg/errorsを使う

modelのロジックが複数の種類のエラーを返す想定で出力を変えたいなーと思っていろいろ調べたのでメモ。

どうやったらシンプルにできるかなーと考えてたんですが、pkg/errorsを使う形に落ち着きました。

github.com

だいたい以下のような感じで、独自のエラー用の構造体を作って、pkg/errorsのCauseを使って分岐するだけです。このやり方自体もpkg/errors/errors.goのコメントにかかれています。

errors/errors.go at master · pkg/errors · GitHub

package main

import (
    "log"

    "github.com/pkg/errors"
)

type SampleError struct{}

func (e *SampleError) Error() string {
    return "sample error"
}

func main() {
    err := &SampleError{}

    switch errors.Cause(err).(type) {
    case *SampleError:
        log.Println("sample")
    default:
        log.Println("default")
    }
}

シンプルですが、APIでレスポンスを出し分けたいとかに便利だと思います。

echoでmiddlewareを挟もうとしてハマった話

echoでエンドポイントにmiddlewareをはさもうとしていろいろハマったのでメモ。

echoでルーティングする場合は以下のようにecho.EchoのGET、POSTであったり、Addだったりを呼ぶわけなんですが、それとは別に直接echo.RouterのAddを使ってルーティングしたりもできます。

echo.Echoを使う場合

e.GET("/", func (c echo.Context) error {
    return c.String(http.StatusOK, "HelloWorld")
})

echo.Routerを使う場合

r := e.Router()
r.Add("GET", "/", func(c echo.Context) error {
    return c.String(http.StatusOK, "HelloWorld")
})

私の場合はecho.Routerを使っていてmiddlewareをどうやって挟もうかと考えていたのですが、ドキュメントを見ても見てもやり方が分からず諦めてソースを眺めていたら面倒くさくなりそうなことが発覚。以下がそのソース。

// Add registers a new route for an HTTP method and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
    name := handlerName(handler)
    e.router.Add(method, path, func(c Context) error {
        h := handler
        // Chain middleware
        for i := len(middleware) - 1; i >= 0; i-- {
            h = middleware[i](h)
        }
        return h(c)
    })
    r := &Route{
        Method: method,
        Path:   path,
        Name:   name,
    }
    e.router.routes[method+path] = r
    return r
}

単純な話、echo.Echoを使ってルーティングをする際にecho.Routerを内部で使っていて、そこのHandleFuncにmiddlewareを挟む処理を入れているようでした。さすがにライブラリで隠蔽されている部分を自分で実装する気にはならないので諦めてecho.Echoを使うようにしました。。。

haskellでfizzbuzz

だいぶ昔に少しだけhaskellで遊んでたけど、すでに文法すら頭から消し飛んでたので最初からやり直してて、復習にちょうどいいかと思いfizzbuzzをサクッと書いてみました。

ソースは以下。Integerを引数にStringで返す関数を作って、mapでリストの値を適用してるだけです。普通だと配列ごと渡してやろうのだろうけど、めんどくさかったのでこんな感じ。

fizzbuzz :: Integer -> String
fizzbuzz n
  | n `mod` 15 == 0 = "fizzbuzz"
  | n `mod` 5 == 0 = "buzz"
  | n `mod` 3 == 0 = "fizz"
fizzbuzz n = show n

main :: IO()
main = do
  print $ map fizzbuzz [1..10]

haskellで数値から文字列へのcastがよくわからなかったけど、普通にshowを使えばよかったっぽい。showってもっとごちゃごちゃしてた気がするけど、今はとりあえずcastってことだけ覚えとく。

http://hackage.haskell.org/package/base-4.0.0.0/docs/Prelude.html#22

Google SpreadSheetからGASでSlackに定期的に投稿する

ちょっとしたことを勉強している時に、単語とかすぐ忘れるので単語帳代わりにSpreadSheetに書いてたまに見たりしてるのですが、それでも忘れるのでGASでslackに定期的に投稿するようにしました。

やったことは以下。

  1. slackのアプリ登録
  2. GASでslackに投稿するようにする
  3. トリガーの登録

slackのアプリ登録

他のアプリ連携でもそうですが、Slackに投稿するためのアプリケーションを作成します。

Slack API: Applications | Slack

Create NewAppからアプリケーションを作成して、Bot Userに追加します。

GASでslackに投稿するようにする

GASからslackに投稿するためのライブラリみたいなのは多いと思いますが、やりたいことは定期的に投稿するだけなので、incoming webhookを使ってpostリクエストを投げるだけにします。

slackで作成したアプリケーションの画面でFeatures > Incoming Webhooksに行って、webhookを使えるようにします。 f:id:asahima_194h:20180716015641p:plain

webhookを使えるようにすると、Workspaceに追加できるようになるので追加します。追加すると、webhookのURLが表示されるので、そちらにpostリクエストを送ればslackに投稿できるようになります。

GASからhttpリクエストを送るためには、UrlFetchAppのfetchを使えばいいです。

Class UrlFetchApp  |  Apps Script  |  Google Developers

トリガーの登録

あとは定期的に実行するために、トリガーに登録します。

編集 > 現状のプロジェクトのトリガーを選択して、トリガーを追加します。分単位・時間単位などで指定できるので大変便利です。

f:id:asahima_194h:20180716020221p:plain

まとめ

さっくりGASを使って、slackに定期的に投稿できるようにしました。GAS自体はjavascriptとほとんど同じでかつ、googleのアプリケーションに接続するライブラリが内包されていてすぐに呼べるので実際ほとんど処理を書く必要がなくて大変楽です。ただ、ブラウザでコード書くのは大変なので、githubにpushしたら反映されるようにしたいなーと思ったので、次回はそのへんをやりましょうかね。

angularの勉強にTour of Heroesをやった

仕事でangularを使うことになりそうなので、入門がてらに、Tour of Heroesをやってみました。

Angular Docs

Tour of Heroes は、Angularの基礎を学ぶためのチュートリアルで、angular-cliを使ってプロジェクトを作成するところから、最終的にはangularに付属しているhttpライブラリを使用して、APIを叩くところまでやります。その間にもcomponentを追加したり、分離するためにServiceを挟んだりと色々勉強になります。

まだ入門なので、大したことはわかりませんが、React、vueと違って全部入りのフレームワークなので、頻繁にyarnで何がしかのライブラリを引っ張ってこなくていいというのは個人的には楽だなーという印象ですが、Angular自体が半年ごとにメジャーバージョンがあがるので、破壊的な変更が複数あった場合に関連する箇所を一気に変更しないといけないのは少々怖いなーと思ったりします(にわかなので間違ってたらすいません)。

一通り入門は終わったので次は自分で何か作ってみようかと思います。

追記

終わったあとに気づいた、Tour of Heroesの日本語訳。

Angular 日本語ドキュメンテーション

goでDBのテストを書くときに、go-sqlmockを使ってみた

goでDB周りのテストをするときに、毎度テスト用のDBにデータを流し込んで終わったら削除する、みたいなことをしていたのだけれど、若干面倒だなーと思い始めたのでgo-sqlmockを使ってみました。

go-sqlmockはgo用のmockライブラリです。

github.com

公式のサンプルではそのままdatabase/sqlを使っていますが、普通にgormでも使えて以下のような感じで使えます。


func main()  {
    db, mock, err := sqlmock.New()
    if err != nil {
        log.Fatal(err)
    }

    gdb, err := gorm.Open("sqlmock", db)
    if err != nil {
        log.Fatal(err)
    }
        
       // 後は、gorm.Openの返り値を使って色々やる
}

gorm.Openの引数にsqlmockを使っていますが、内部でそれに対する値があるわけではないのでwarningが出ますが、特に問題なく使えます。

具体的な使い方ですが、リポジトリのexampleがあるのでそちらを参考にするといいと思います。

加えて、微妙にハマった点ですが、go-sqlmockではクエリを正規表現でマッチングしている都合上、テストコードに正規表現混じりのSQLを書くか各メタ文字をエスケープする必要があります。そのせいでテストがなかなか通らなかったりしたのですが、regexpのQuoteMetaを使うと、一括でメタ文字をエスケープしてくれるので大変便利です。

regexp - The Go Programming Language

QuoteMetaを使うとだいたい以下のような形で書き換えられます。

// regexp.QuoteMetaを使わないもの
mock.ExpectQuery("^SELECT (.+) FROM samples$")


// regexp.QuoteMetaを使ったもの
mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM samples"))

感想

goのDBのテストにgo-sqlmockを使ってみました。テスト用のDBから読み出すことに比べて事前に何のデータは入っているかを意識しなくていいので心持ち的にはだいぶ楽になったように思います。加えて、regexp.QuoteMetaを使えば細かいこと意識しなくてもよくなるのでこちらも楽になりました。ただ、細かい速度比較はしていないので、大規模なアプリケーションで使う場合は確認したほうがよいかと思います。