govalidator

Go言語のための包括的なバリデーションとサニタイゼーションライブラリ

govalidatorは、文字列、数値、スライス、構造体に対する包括的なバリデータとサニタイザーを提供するGo言語向けのパッケージです。JavaScript の validator.js をベースに設計されており、豊富な組み込みバリデータとカスタムバリデータのサポートを提供します。

主な特徴

  • 豊富な組み込みバリデータ: 120以上の組み込みバリデーション関数
  • 構造体タグベースのバリデーション: タグを使用した宣言的なバリデーション
  • カスタムバリデータのサポート: 独自のバリデーションロジックを簡単に追加
  • 型安全: Go の型システムを活用したバリデーション
  • エラーメッセージのカスタマイズ: フィールドごとのエラーメッセージ設定
  • マップのバリデーション: 構造体だけでなくマップのバリデーションも可能

インストール

# 最新版
go get github.com/asaskevich/govalidator/v11

# 特定のバージョン
go get gopkg.in/asaskevich/govalidator.v10

基本的な使い方

パッケージのインポート

import "github.com/asaskevich/govalidator/v11"

// エイリアスを使用する場合
import (
  valid "github.com/asaskevich/govalidator/v11"
)

構造体のバリデーション

type User struct {
    Name     string `valid:"alpha,required"`
    Email    string `valid:"email,required"`
    Age      int    `valid:"range(0|120)"`
    Phone    string `valid:"numeric,length(10|11)"`
    Website  string `valid:"url,optional"`
}

user := User{
    Name:    "TaroYamada",
    Email:   "[email protected]",
    Age:     25,
    Phone:   "09012345678",
    Website: "https://example.com",
}

result, err := govalidator.ValidateStruct(user)
if err != nil {
    fmt.Println("バリデーションエラー:", err.Error())
} else {
    fmt.Println("バリデーション成功")
}

組み込みバリデータ

文字列バリデータ

// メールアドレス
govalidator.IsEmail("[email protected]") // true

// URL
govalidator.IsURL("https://example.com") // true

// アルファベット
govalidator.IsAlpha("HelloWorld") // true

// 数値文字列
govalidator.IsNumeric("12345") // true

// 日本語を含むマルチバイト文字
govalidator.IsMultibyte("こんにちは") // true

// Base64
govalidator.IsBase64("SGVsbG8gV29ybGQ=") // true

ネットワーク関連

// IPアドレス
govalidator.IsIP("192.168.1.1") // true
govalidator.IsIPv4("192.168.1.1") // true
govalidator.IsIPv6("2001:db8::1") // true

// MACアドレス
govalidator.IsMAC("00:00:5e:00:53:01") // true

// ポート番号
govalidator.IsPort("8080") // true

// CIDR
govalidator.IsCIDR("192.168.1.0/24") // true

識別子とフォーマット

// UUID
govalidator.IsUUID("550e8400-e29b-41d4-a716-446655440000") // true

// クレジットカード番号
govalidator.IsCreditCard("4111111111111111") // true

// ISBN
govalidator.IsISBN10("0596529260") // true
govalidator.IsISBN13("9780596529260") // true

// JSON
govalidator.IsJSON(`{"name":"value"}`) // true

// 緯度・経度
govalidator.IsLatitude("35.6762") // true
govalidator.IsLongitude("139.6503") // true

構造体タグの詳細

基本的なタグ

type Product struct {
    // 必須フィールド
    Name string `valid:"required"`
    
    // オプショナルフィールド
    Description string `valid:"optional"`
    
    // バリデーションをスキップ
    InternalID string `valid:"-"`
    
    // 複数のバリデータ
    SKU string `valid:"alphanum,required,length(8|12)"`
    
    // カスタムエラーメッセージ
    Price float64 `valid:"required~価格は必須です,range(0|999999)~価格は0〜999999の範囲で入力してください"`
}

パラメータ付きバリデータ

type ValidationExample struct {
    // 範囲指定
    Score int `valid:"range(0|100)"`
    
    // 文字列長(バイト数)
    Username string `valid:"length(3|20)"`
    
    // 文字列長(文字数)
    DisplayName string `valid:"runelength(1|10)"`
    
    // 正規表現マッチ
    Code string `valid:"matches(^[A-Z]{3}[0-9]{4}$)"`
    
    // 選択肢から選ぶ
    Status string `valid:"in(active|inactive|pending)"`
    
    // 型チェック
    Data interface{} `valid:"type(string)"`
}

カスタムバリデータ

シンプルなカスタムバリデータ

// 日本の郵便番号バリデータ
govalidator.TagMap["jpzipcode"] = govalidator.Validator(func(str string) bool {
    return govalidator.Matches(str, "^\\d{3}-\\d{4}$")
})

type Address struct {
    ZipCode string `valid:"jpzipcode,required"`
}

パラメータ付きカスタムバリデータ

// 特定の値と一致するかチェック
govalidator.ParamTagMap["equals"] = govalidator.ParamValidator(func(str string, params ...string) bool {
    if len(params) > 0 {
        return str == params[0]
    }
    return false
})
govalidator.ParamTagRegexMap["equals"] = regexp.MustCompile("^equals\\(([^)]+)\\)$")

type Config struct {
    Environment string `valid:"equals(production)|equals(development)"`
}

高度なカスタムバリデータ(コンテキスト対応)

type User struct {
    Password        string `valid:"required,length(8|100)"`
    PasswordConfirm string `valid:"passwordmatch"`
}

// 他のフィールドを参照するバリデータ
govalidator.CustomTypeTagMap.Set("passwordmatch", func(i interface{}, context interface{}) bool {
    switch v := context.(type) {
    case User:
        return i.(string) == v.Password
    }
    return false
})

エラーハンドリング

詳細なエラー情報の取得

result, err := govalidator.ValidateStruct(user)
if err != nil {
    // 個別のエラーを取得
    errs := err.(govalidator.Errors).Errors()
    for _, e := range errs {
        fmt.Printf("エラー: %s\n", e.Error())
    }
    
    // フィールドごとのエラー
    errorsByField := govalidator.ErrorsByField(err)
    for field, errMsg := range errorsByField {
        fmt.Printf("フィールド '%s': %s\n", field, errMsg)
    }
}

高度な使い方

デフォルトの挙動設定

func init() {
    // すべてのフィールドにバリデーションタグを必須にする
    govalidator.SetFieldsRequiredByDefault(true)
    
    // required タグで nil を許可する
    govalidator.SetNilPtrAllowedByRequired(true)
}

マップのバリデーション

// バリデーションテンプレート
var userTemplate = map[string]interface{}{
    "name":     "required,alpha",
    "email":    "required,email",
    "age":      "required,int,range(0|120)",
    "address": map[string]interface{}{
        "street":  "required,alphanum",
        "city":    "required,alpha",
        "zipcode": "required,matches(^\\d{3}-\\d{4}$)",
    },
}

// 入力データ
inputData := map[string]interface{}{
    "name":  "Taro",
    "email": "[email protected]",
    "age":   30,
    "address": map[string]interface{}{
        "street":  "Shibuya1234",
        "city":    "Tokyo",
        "zipcode": "150-0002",
    },
}

result, err := govalidator.ValidateMap(inputData, userTemplate)

サニタイゼーション

// HTMLタグの除去
cleaned := govalidator.RemoveTags("<p>Hello World</p>") // "Hello World"

// ホワイトリスト
filtered := govalidator.WhiteList("abc123!@#", "a-z") // "abc"

// ブラックリスト
filtered := govalidator.BlackList("abc123", "0-9") // "abc"

// トリミング
trimmed := govalidator.Trim("  hello  ", " ") // "hello"

// メールアドレスの正規化
normalized, _ := govalidator.NormalizeEmail("[email protected]")
// "[email protected]"

実用的な例

APIリクエストのバリデーション

type CreateUserRequest struct {
    Username string `json:"username" valid:"required,alphanum,length(3|20)"`
    Email    string `json:"email" valid:"required,email"`
    Password string `json:"password" valid:"required,length(8|100)"`
    Age      int    `json:"age" valid:"required,range(13|120)"`
    Terms    bool   `json:"terms" valid:"required"`
}

func handleCreateUser(req CreateUserRequest) error {
    // バリデーション実行
    if _, err := govalidator.ValidateStruct(req); err != nil {
        return fmt.Errorf("入力データが無効です: %w", err)
    }
    
    // ユーザー作成処理
    return createUser(req)
}

設定ファイルのバリデーション

type DatabaseConfig struct {
    Host     string `valid:"required,host"`
    Port     int    `valid:"required,port"`
    Username string `valid:"required,alphanum"`
    Password string `valid:"required"`
    Database string `valid:"required,alphanum"`
    SSL      bool   `valid:"-"`
}

type AppConfig struct {
    Database    DatabaseConfig `valid:"required"`
    ServerPort  int           `valid:"required,port"`
    Environment string        `valid:"required,in(development|staging|production)"`
    LogLevel    string        `valid:"required,in(debug|info|warn|error)"`
}

パフォーマンス考慮事項

  1. バリデーションのキャッシュ: 同じ構造体を繰り返しバリデーションする場合、結果をキャッシュすることを検討
  2. 必要なバリデータのみ使用: 不要なバリデーションは避ける
  3. カスタムバリデータの最適化: 複雑な正規表現は事前にコンパイル

ベストプラクティス

  1. 明確なエラーメッセージ: カスタムエラーメッセージを使用してユーザーフレンドリーなフィードバックを提供
  2. バリデーションの分離: ビジネスロジックとバリデーションロジックを分離
  3. テスト: カスタムバリデータには必ずテストを作成
  4. ドキュメント化: カスタムバリデータの動作を明確にドキュメント化