ozzo-validation
Go言語のための構成可能で高速なルールベースのデータバリデーションライブラリ
ozzo-validationは、Go言語向けの構成可能でカスタマイズ可能なデータバリデーションライブラリです。ルールベースのアプローチを採用し、単純な値から複雑な構造体まで、あらゆるデータ型のバリデーションを簡潔かつ直感的に行えます。
主な特徴
- ルールベースのバリデーション: 再利用可能なバリデーションルールの組み合わせ
- 構成可能: ルールを組み合わせて複雑なバリデーションロジックを構築
- エラーメッセージのカスタマイズ: 多言語対応を含む柔軟なエラーメッセージ設定
- 型に依存しない: interface{} を使用して任意の型をバリデート
- コンテキスト対応: 他のフィールドの値に基づく条件付きバリデーション
- 高速: リフレクションの使用を最小限に抑えた効率的な実装
- 拡張可能: カスタムルールの作成が簡単
インストール
# 最新版(v4)
go get github.com/go-ozzo/ozzo-validation/v4
# is パッケージ(一般的なバリデーションルール)も推奨
go get github.com/go-ozzo/ozzo-validation/v4/is
基本的な使い方
パッケージのインポート
import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
)
単純な値のバリデーション
// 文字列のバリデーション
email := "[email protected]"
err := validation.Validate(email,
validation.Required, // 必須
is.Email, // メールアドレス形式
)
// 数値のバリデーション
age := 25
err := validation.Validate(age,
validation.Required,
validation.Min(18), // 最小値
validation.Max(100), // 最大値
)
// スライスのバリデーション
tags := []string{"go", "validation", ""}
err := validation.Validate(tags,
validation.Required,
validation.Length(1, 5), // 要素数の範囲
validation.Each( // 各要素に対するルール
validation.Required,
validation.Length(1, 20),
),
)
構造体のバリデーション
基本的な構造体バリデーション
type Address struct {
Street string
City string
State string
Zip string
}
func (a Address) Validate() error {
return validation.ValidateStruct(&a,
// Street フィールドのルール
validation.Field(&a.Street, validation.Required, validation.Length(1, 100)),
// City フィールドのルール
validation.Field(&a.City, validation.Required, validation.Length(1, 50)),
// State フィールドのルール
validation.Field(&a.State, validation.Required, validation.Length(2, 2)),
// Zip フィールドのルール(日本の郵便番号形式)
validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^\\d{3}-\\d{4}$"))),
)
}
ネストした構造体のバリデーション
type User struct {
Name string
Email string
Password string
Profile Profile
Address Address
}
type Profile struct {
Age int
Phone string
Website string
}
func (p Profile) Validate() error {
return validation.ValidateStruct(&p,
validation.Field(&p.Age, validation.Required, validation.Min(0), validation.Max(120)),
validation.Field(&p.Phone, validation.Match(regexp.MustCompile("^0\\d{1,4}-\\d{1,4}-\\d{4}$"))),
validation.Field(&p.Website, is.URL),
)
}
func (u User) Validate() error {
return validation.ValidateStruct(&u,
validation.Field(&u.Name, validation.Required, validation.Length(2, 50)),
validation.Field(&u.Email, validation.Required, is.Email),
validation.Field(&u.Password, validation.Required, validation.Length(8, 100)),
validation.Field(&u.Profile), // Profile.Validate() が自動的に呼ばれる
validation.Field(&u.Address), // Address.Validate() が自動的に呼ばれる
)
}
組み込みバリデーションルール
一般的なルール
// 必須チェック
validation.Required
// nil 禁止
validation.NotNil
// 長さ・サイズ
validation.Length(min, max) // 文字列、スライス、マップ、配列の長さ
validation.RuneLength(min, max) // 文字列の文字数(ルーン数)
// 数値の範囲
validation.Min(minValue)
validation.Max(maxValue)
// 含まれる/含まれない
validation.In("apple", "orange", "banana")
validation.NotIn("admin", "root", "superuser")
// 正規表現マッチ
validation.Match(regexp.MustCompile("^[A-Z]+$"))
// 日付
validation.Date("2006-01-02") // 指定フォーマットの日付文字列
is パッケージのルール
// メールアドレス
is.Email
// URL
is.URL
is.RequestURL
is.RequestURI
// アルファベット・数字
is.Alpha // アルファベットのみ
is.Digit // 数字のみ
is.Alphanumeric // アルファベットと数字
is.LowerCase // 小文字のみ
is.UpperCase // 大文字のみ
// ネットワーク
is.IP // IPアドレス(v4 または v6)
is.IPv4 // IPv4アドレス
is.IPv6 // IPv6アドレス
is.MAC // MACアドレス
is.CIDR // CIDR記法
is.Host // ホスト名
is.Port // ポート番号
is.DNSName // DNS名
// 識別子
is.UUID // UUID
is.UUIDv3 // UUID version 3
is.UUIDv4 // UUID version 4
is.UUIDv5 // UUID version 5
// その他
is.JSON // 有効なJSON
is.Base64 // Base64エンコード
is.ASCII // ASCII文字のみ
is.PrintableASCII // 印刷可能なASCII文字
is.Multibyte // マルチバイト文字を含む
is.FullWidth // 全角文字を含む
is.HalfWidth // 半角文字を含む
is.CreditCard // クレジットカード番号(Luhnアルゴリズム)
カスタムバリデーションルール
シンプルなカスタムルール
// 日本の電話番号バリデーションルール
var JapanesePhoneNumber = validation.NewStringRule(
func(value string) bool {
// 固定電話または携帯電話の形式
pattern := `^(0[0-9]{1,4}-[0-9]{1,4}-[0-9]{4}|0[789]0-[0-9]{4}-[0-9]{4})$`
matched, _ := regexp.MatchString(pattern, value)
return matched
},
"は有効な日本の電話番号ではありません",
)
// 使用例
phone := "090-1234-5678"
err := validation.Validate(phone, validation.Required, JapanesePhoneNumber)
Rule インターフェースを実装
// パスワード強度チェックルール
type PasswordStrengthRule struct {
MinLength int
RequireUpper bool
RequireLower bool
RequireNumber bool
RequireSpecial bool
}
func (r PasswordStrengthRule) Validate(value interface{}) error {
s, ok := value.(string)
if !ok {
return fmt.Errorf("パスワードは文字列である必要があります")
}
if len(s) < r.MinLength {
return fmt.Errorf("パスワードは%d文字以上である必要があります", r.MinLength)
}
if r.RequireUpper && !strings.ContainsAny(s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
return fmt.Errorf("パスワードには大文字を含める必要があります")
}
if r.RequireLower && !strings.ContainsAny(s, "abcdefghijklmnopqrstuvwxyz") {
return fmt.Errorf("パスワードには小文字を含める必要があります")
}
if r.RequireNumber && !strings.ContainsAny(s, "0123456789") {
return fmt.Errorf("パスワードには数字を含める必要があります")
}
if r.RequireSpecial && !strings.ContainsAny(s, "!@#$%^&*()_+-=[]{}|;:,.<>?") {
return fmt.Errorf("パスワードには特殊文字を含める必要があります")
}
return nil
}
// 使用例
password := "MyP@ssw0rd"
err := validation.Validate(password, PasswordStrengthRule{
MinLength: 8,
RequireUpper: true,
RequireLower: true,
RequireNumber: true,
RequireSpecial: true,
})
条件付きバリデーション
When を使用した条件付きルール
type Order struct {
PaymentMethod string
CardNumber string
CardCVV string
BankAccount string
}
func (o Order) Validate() error {
return validation.ValidateStruct(&o,
validation.Field(&o.PaymentMethod,
validation.Required,
validation.In("credit_card", "bank_transfer", "cash"),
),
// クレジットカードの場合のみカード情報を必須に
validation.Field(&o.CardNumber,
validation.When(o.PaymentMethod == "credit_card",
validation.Required,
is.CreditCard,
),
),
validation.Field(&o.CardCVV,
validation.When(o.PaymentMethod == "credit_card",
validation.Required,
validation.Match(regexp.MustCompile("^\\d{3,4}$")),
),
),
// 銀行振込の場合のみ口座番号を必須に
validation.Field(&o.BankAccount,
validation.When(o.PaymentMethod == "bank_transfer",
validation.Required,
validation.Match(regexp.MustCompile("^\\d{7}$")),
),
),
)
}
By を使用した動的ルール
type PasswordChangeRequest struct {
CurrentPassword string
NewPassword string
ConfirmPassword string
}
func (r PasswordChangeRequest) Validate() error {
return validation.ValidateStruct(&r,
validation.Field(&r.CurrentPassword, validation.Required),
validation.Field(&r.NewPassword,
validation.Required,
validation.Length(8, 100),
// 現在のパスワードと異なることを確認
validation.By(func(value interface{}) error {
if r.CurrentPassword == value.(string) {
return errors.New("新しいパスワードは現在のパスワードと異なる必要があります")
}
return nil
}),
),
validation.Field(&r.ConfirmPassword,
validation.Required,
// 新しいパスワードと一致することを確認
validation.By(func(value interface{}) error {
if r.NewPassword != value.(string) {
return errors.New("確認用パスワードが一致しません")
}
return nil
}),
),
)
}
エラーハンドリング
エラー構造の取得
user := User{
Name: "",
Email: "invalid-email",
Profile: Profile{
Age: -1,
},
}
err := user.Validate()
if err != nil {
// validation.Errors 型にキャスト
if e, ok := err.(validation.Errors); ok {
// フィールドごとのエラーを取得
for field, fieldErr := range e {
fmt.Printf("フィールド %s: %v\n", field, fieldErr)
}
}
}
カスタムエラーメッセージ
// ルールごとのエラーメッセージカスタマイズ
err := validation.Validate(email,
validation.Required.Error("メールアドレスは必須です"),
is.Email.Error("有効なメールアドレスを入力してください"),
)
// グローバルなエラーメッセージの設定
validation.ErrorTag = "ja" // エラーメッセージの言語タグ
// カスタムエラーメッセージテンプレート
type CustomRequiredRule struct{}
func (r CustomRequiredRule) Validate(value interface{}) error {
return validation.Validate(value, validation.Required)
}
func (r CustomRequiredRule) Error(message string) validation.Rule {
return validation.Required.Error(message)
}
実用的な例
RESTful API のリクエストバリデーション
// ユーザー登録リクエスト
type RegisterRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
PasswordConfirm string `json:"password_confirm"`
Age int `json:"age"`
AcceptTerms bool `json:"accept_terms"`
}
func (r RegisterRequest) Validate() error {
return validation.ValidateStruct(&r,
// ユーザー名: 3-20文字、英数字とアンダースコアのみ
validation.Field(&r.Username,
validation.Required,
validation.Length(3, 20),
validation.Match(regexp.MustCompile("^[a-zA-Z0-9_]+$")).
Error("ユーザー名は英数字とアンダースコアのみ使用できます"),
),
// メールアドレス
validation.Field(&r.Email,
validation.Required,
is.Email,
// 重複チェック(カスタムルール)
validation.By(checkEmailUniqueness),
),
// パスワード
validation.Field(&r.Password,
validation.Required,
validation.Length(8, 100),
PasswordStrengthRule{
MinLength: 8,
RequireUpper: true,
RequireLower: true,
RequireNumber: true,
RequireSpecial: false,
},
),
// パスワード確認
validation.Field(&r.PasswordConfirm,
validation.Required,
validation.By(func(value interface{}) error {
if r.Password != value.(string) {
return errors.New("パスワードが一致しません")
}
return nil
}),
),
// 年齢
validation.Field(&r.Age,
validation.Required,
validation.Min(13).Error("13歳以上である必要があります"),
validation.Max(120),
),
// 利用規約への同意
validation.Field(&r.AcceptTerms,
validation.By(func(value interface{}) error {
if !value.(bool) {
return errors.New("利用規約に同意する必要があります")
}
return nil
}),
),
)
}
func checkEmailUniqueness(value interface{}) error {
email := value.(string)
// データベースでメールアドレスの重複をチェック
exists, err := db.EmailExists(email)
if err != nil {
return err
}
if exists {
return errors.New("このメールアドレスは既に使用されています")
}
return nil
}
設定ファイルのバリデーション
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
TLS TLSConfig `json:"tls"`
Database DatabaseConfig `json:"database"`
Cache CacheConfig `json:"cache"`
RateLimiting RateLimitConfig `json:"rate_limiting"`
}
type TLSConfig struct {
Enabled bool `json:"enabled"`
CertFile string `json:"cert_file"`
KeyFile string `json:"key_file"`
}
type DatabaseConfig struct {
Driver string `json:"driver"`
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Database string `json:"database"`
MaxConns int `json:"max_conns"`
}
func (c ServerConfig) Validate() error {
return validation.ValidateStruct(&c,
validation.Field(&c.Host, validation.Required, is.Host),
validation.Field(&c.Port, validation.Required, is.Port),
validation.Field(&c.TLS),
validation.Field(&c.Database),
validation.Field(&c.Cache),
validation.Field(&c.RateLimiting),
)
}
func (t TLSConfig) Validate() error {
return validation.ValidateStruct(&t,
validation.Field(&t.CertFile,
validation.When(t.Enabled, validation.Required),
),
validation.Field(&t.KeyFile,
validation.When(t.Enabled, validation.Required),
),
)
}
func (d DatabaseConfig) Validate() error {
return validation.ValidateStruct(&d,
validation.Field(&d.Driver, validation.Required, validation.In("mysql", "postgres", "sqlite")),
validation.Field(&d.Host, validation.When(d.Driver != "sqlite", validation.Required, is.Host)),
validation.Field(&d.Port, validation.When(d.Driver != "sqlite", validation.Required, is.Port)),
validation.Field(&d.Username, validation.When(d.Driver != "sqlite", validation.Required)),
validation.Field(&d.Password, validation.When(d.Driver != "sqlite", validation.Required)),
validation.Field(&d.Database, validation.Required),
validation.Field(&d.MaxConns, validation.Min(1), validation.Max(100)),
)
}
パフォーマンスとベストプラクティス
パフォーマンスの最適化
// ルールの再利用
var (
emailRule = []validation.Rule{
validation.Required,
is.Email,
}
passwordRule = []validation.Rule{
validation.Required,
validation.Length(8, 100),
}
)
// 正規表現のプリコンパイル
var (
phoneRegex = regexp.MustCompile(`^0\d{1,4}-\d{1,4}-\d{4}$`)
zipRegex = regexp.MustCompile(`^\d{3}-\d{4}$`)
)
ベストプラクティス
- Validate メソッドの実装: 構造体には
Validate() errorメソッドを実装 - エラーメッセージの一貫性: アプリケーション全体で統一されたエラーメッセージ
- 再利用可能なルール: 共通のバリデーションルールは変数として定義
- テスタビリティ: バリデーションロジックは単体テストを作成
- 段階的なバリデーション: 基本的なチェックから複雑なチェックへ
// テストの例
func TestUserValidation(t *testing.T) {
tests := []struct {
name string
user User
wantErr bool
}{
{
name: "有効なユーザー",
user: User{
Name: "田中太郎",
Email: "[email protected]",
Password: "MyP@ssw0rd",
},
wantErr: false,
},
{
name: "無効なメールアドレス",
user: User{
Name: "田中太郎",
Email: "invalid-email",
Password: "MyP@ssw0rd",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.user.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
まとめ
ozzo-validationは、Go言語で柔軟かつ強力なバリデーションを実現するための優れたライブラリです。ルールベースのアプローチにより、シンプルなバリデーションから複雑なビジネスルールまで、クリーンで保守しやすいコードで実装できます。カスタムルールの作成も容易で、アプリケーションの要求に応じて拡張できる点が大きな利点です。