go の構造体の cross field validation
はじめに
goccy/go-yaml とgo-playground/validator を使って構造体のフィールド間の validation をする方法をまとめます。
go の 構造体の cross field validation
go-playground/validatorのドキュメント にある通り構造体の tag としての cross field validation は gtfield
や ltfield
など単純な値の比較のみが提供されているようです。
今回は Custom Validation Functions を使って構造体のフィールド間の validation を行います。
Custom Validation Functions
RegisterStructValidation()
を使って構造体ごとまるっと引っ張ってきて validation を行います。
今回は以下のような構造体を扱います。
package main import ( "fmt" "strings" "github.com/go-playground/validator/v10" "github.com/goccy/go-yaml" ) type API struct { A int B string } var yml = `--- a: 10 b: "small" ` func main() { var api API validate := validator.New() validate.RegisterStructValidation(custom_validation, api) dec := yaml.NewDecoder( strings.NewReader(yml), // yaml.Validator(validate), // <- ここで呼べないので ) err := dec.Decode(&api) if err != nil { fmt.Println(yaml.FormatError(err, true, true)) } // ここで呼ぶ err = validate.Struct(api) if err != nil { fmt.Println(err) } } func custom_validation(sl validator.StructLevel) { api := sl.Current().Interface().(API) if api.A > 5 && api.B == "small" { source, err := yamlSourceByPath(yml, "$.b") if err != nil { panic(err) } fmt.Printf("b value expected \"large\" but actual %s:\n%s\n", api.B, source) } } // https://github.com/goccy/go-yaml#51-print-customized-error-with-yaml-source-code func yamlSourceByPath(originalSource string, pathStr string) (string, error) { file, err := parser.ParseBytes([]byte(originalSource), 0) if err != nil { return "", err } path, err := yaml.PathString(pathStr) if err != nil { return "", err } node, err := path.FilterFile(file) if err != nil { return "", err } var p printer.Printer return p.PrintErrorToken(node.GetToken(), true), nil }
yaml.NewDecoder() は validator を渡すことで invalid な箇所がとても綺麗に出力される魅力があるのですが RegisterStructValidation()
を利用した時に invalid すると SIGSEGV するので validate.Struct(api)
のように Decoder の外で呼ぶことにしました。
追記
goccy/go-yaml の author様に YAMLPath なるものを教えていただき Decoder 外の validator でも綺麗に出力することができました。
$ go run main.go b value expected "large" but actual small: 1 | --- 2 | a: 10 > 3 | b: "small" ^
まとめ
go-playground/validator を使って構造体のフィールド間の validation をする方法をまとめました。
参考サイト
qiita.com