Skip to content

API リクエストボディ

govalid を net/http(あるいは任意のフレームワーク)の HTTP ハン ドラーに組み込みます。バリデーションは JSON デコード後・ビジネス ロジック前に走らせます。

net/http と組み合わせる

go
package main

import (
    "encoding/json"
    "errors"
    "net/http"

    "github.com/wuhan005/govalid"
    "golang.org/x/text/language"
)

type CreatePost struct {
    Title    string   `valid:"required;minlen:3;maxlen:200" label:"标题"`
    Body     string   `valid:"required;minlen:10"           label:"正文"`
    Tags     []string `valid:"maxlen:5"                     label:"标签"`
    Author   string   `valid:"required;username"            label:"作者"`
    Category string   `valid:"list:tech,life,news"          label:"分类"`
}

func (p *CreatePost) Validate() error {
    if len(p.Body) > 100 && len(p.Tags) == 0 {
        return errors.New("长文章必须至少包含一个标签")
    }
    return nil
}

func createPost(w http.ResponseWriter, r *http.Request) {
    var body CreatePost
    if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    locale := negotiate(r)
    if errs, ok := govalid.Check(&body, locale); !ok {
        respondValidationErrors(w, errs)
        return
    }

    // ……ビジネスロジック……
    w.WriteHeader(http.StatusCreated)
}

func negotiate(r *http.Request) language.Tag {
    matcher := language.NewMatcher([]language.Tag{
        language.Chinese, language.English,
    })
    accept, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
    tag, _, _ := matcher.Match(accept...)
    return tag
}

func respondValidationErrors(w http.ResponseWriter, errs []*govalid.ErrContext) {
    type item struct {
        Field   string `json:"field"`
        Label   string `json:"label"`
        Message string `json:"message"`
    }
    out := make([]item, 0, len(errs))
    for _, e := range errs {
        out = append(out, item{
            Field:   e.FieldName,
            Label:   e.FieldLabel,
            Message: e.Error(),
        })
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusUnprocessableEntity)
    _ = json.NewEncoder(w).Encode(map[string]any{"errors": out})
}

レスポンス例

json
{
  "errors": [
    { "field": "Title",  "label": "标题",  "message": "标题长度应大于3" },
    { "field": "Author", "label": "作者", "message": "作者的第一个字符必须为字母" }
  ]
}

パターンのポイント

  • Check はデコードに呼び出します。バリデーションタグは JSON 構文エラーを救えません。
  • ネゴシエートした language.TagCheck に渡し、レスポンスを 呼び出し側のロケールに揃えます。
  • バリデーション失敗には 400 Bad Request ではなく 422 Unprocessable Entity を返すと、フロントエンドが「ゴミを送った」 と「データの修正が必要」を区別しやすくなります。

MIT ライセンスのもとで公開されています。