GolangのgoaでAPIをデザインしよう(バリデーション編)
goaはいいぞ!
Golangのgoaの勉強に役立つ情報まとめ - ぺい
goaの情報をもっと見たい方は、上のリンクから確認してください
わかっちゃいるけど、さぼりたいバリデーション
APIは単純なJSONやText返したり、用途は色々あると思います。ですけど、実際返す処理の前にクライアントから飛んでくるリクエストが間違っていないかバリデーションが必要になりますよね。これがだるい!
しかも、プログラム側で実装するだけでも大変なのに、それをドキュメントにどういう条件でOKとするかとか書き出すと、結構細かく記述する必要があり、正直面倒でしかないと思います。そして、仕様変更があれば楽しい修正が始まります。無駄無駄無駄無駄無駄無駄無駄!!!!!!
goaでやったら秒で終わるよ
そこで、今回はgoaでバリデーションすると、幸せになれる一例を示したいと思います。サンプルコードと説明を貼っていくので、この楽さを実感してほしいです。
goaはコード生成する時に少し長めのコマンドが必要になるので、Makefileを作っておくと良いと思います。
ちなみに、コマンドやフォルダ構成は毎回同じものを使うので、テンプレートを作成しました。よろしければ利用してください。
もし、もっと便利なフォーマットがあればPRをください。
$ go get github.com/tikasan/goa-stater または $ ghq get github.com/tikasan/goa-stater
デザイン定義
以下のような感じで、用意されているメソッドを使えば大体なんとかなります。
ちなみにFormay関数には、email以外にもいくつかあります。
- “date-time”: RFC3339 date time
- “email”: RFC5322 email address
- “hostname”: RFC1035 internet host name
- “ipv4”, “ipv6”, “ip”: RFC2373 IPv4, IPv6 address or either
- “uri”: RFC3986 URI
- “mac”: IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address
- “cidr”: RFC4632 or RFC4291 CIDR notation IP address
- “regexp”: RE2 regular expression
var _ = Resource("validation", func() { BasePath("/validation") Action("validation", func() { Description("Validation") Routing( GET("/"), ) Params(func() { // Integer型 Param("id", Integer, "id", func() { Example(1) }) // Integer型かつ1〜10以下 Param("integerType", Integer, "数字(1〜10)", func() { Minimum(0) Maximum(10) Example(2) }) // String型かつ1〜10文字以下 Param("stringType", String, "文字(1~10文字)", func() { MinLength(1) MaxLength(10) Example("あいうえお") }) // String型かつemailフォーマット Param("email", String, "メールアドレス", func() { Format("email") Example("example@gmail.com") }) // String型でEnumで指定されたいずれかの文字列 Param("enumType", String, "列挙型", func() { Enum("A", "B", "C") Example("A") }) // String型で何も指定が無ければ”でふぉ”という文字列が自動でセットされる Param("defaultType", String, "デフォルト値", func() { Default("でふぉ") Example("でふぉ") }) // String型で正規表現で指定したパターンの文字列 Param("reg", String, "正規表現", func() { Pattern("^[a-z0-9]{5}$") Example("12abc") }) // 全て必須パラメーター Required("id", "integerType", "stringType", "email", "enumType", "defaultType", "reg") }) Response(OK, ValidationMedia) Response(BadRequest, ErrorMedia) }) })
MediaTypeの定義
とりあえず、返すだけ。
var ValidationMedia = MediaType("application/vnd.validation+json", func() { Description("example") Attributes(func() { Attribute("id", Integer, "id", func() { Example(1) }) Attribute("integerType", Integer, "数字(1〜10)", func() { Example(5) }) Attribute("stringType", String, "文字(1~10文字)", func() { Example("あいうえお") }) Attribute("email", String, "メールアドレス", func() { Example("example@gmail.com") }) Attribute("enumType", String, "列挙型", func() { Example("A") }) Attribute("defaultType", String, "デフォルト値", func() { Example("でふぉ") }) Attribute("reg", String, "デフォルト値", func() { Example("12abc") }) }) Required("id", "integerType", "stringType", "email", "enumType", "defaultType", "reg") View("default", func() { Attribute("id") Attribute("integerType") Attribute("stringType") Attribute("email") Attribute("enumType") Attribute("defaultType") Attribute("reg") }) })
コントローラー定義
コントローラーは、バリデーションが完了した値が来た時だけを想定した処理をすればおkです。デザイン定義が完了したら、デザインを元にコードを生成しましょう。
$ goagen bootstrap -d github.com/tikasan/goa-stater/design
package main import ( "github.com/goadesign/goa" "github.com/tikasan/goa-stater/app" ) // ValidationController implements the validation resource. type ValidationController struct { *goa.Controller } // NewValidationController creates a validation controller. func NewValidationController(service *goa.Service) *ValidationController { return &ValidationController{Controller: service.NewController("ValidationController")} } // Validation runs the validation action. func (c *ValidationController) Validation(ctx *app.ValidationValidationContext) error { // ValidationController_Validation: start_implement // Put your logic here // ValidationController_Validation: end_implement res := &app.Validation{} res.ID = ctx.ID res.IntegerType = ctx.IntegerType res.StringType = ctx.StringType res.Email = ctx.Email res.EnumType = ctx.EnumType res.DefaultType = ctx.DefaultType res.Reg = ctx.Reg return ctx.OK(res) }
実際に動かしてみよう
$ go run main.go
$ curl -v 'http://localhost:8080/api/v1/validation?id=1&defaultType=&email=satak%40gmail.com&enumType=A&integerType=10&stringType=foo®=12abc' * Trying ::1... * Connected to localhost (::1) port 8080 (#0) > GET /api/v1/validation?id=1&defaultType=&email=satak%40gmail.com&enumType=A&integerType=10&stringType=foo®=12abc HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.43.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/vnd.validation+json < Date: Sat, 06 May 2017 01:47:26 GMT < Content-Length: 117 < {"id":1,"defaultType":"","email":"satak@gmail.com","enumType":"A","integerType":10,"reg":"12abc","stringType":"foo"} * Connection #0 to host localhost left intact
goa紹介で使うソース達は、以下のリポジトリに随時更新していきます。
github.com