garde
Rust用の型安全なデータ検証ライブラリ。deriveマクロを使用した宣言的な検証ルールの定義が可能
概要
gardeは、Rustで型安全なデータ検証を提供する強力なライブラリです。deriveマクロを使用して、構造体やenumに対して宣言的に検証ルールを定義できます。コンパイル時の型チェックと実行時の検証を組み合わせることで、安全で表現力豊かな検証システムを実現しています。
主な特徴
- Deriveマクロベース:
#[derive(Validate)]で簡単に検証を追加 - 豊富な組み込みバリデータ: 長さ、範囲、パターンマッチングなど
- カスタムバリデータ: 独自の検証ロジックを簡単に実装
- 詳細なエラー情報: フィールドごとの検証エラーを取得
- Serde統合: JSONやその他の形式のデータ検証に対応
- ゼロコスト抽象化: 実行時のオーバーヘッドを最小限に
インストール
[dependencies]
garde = "0.18"
# Serde統合を使用する場合
garde = { version = "0.18", features = ["serde"] }
# 正規表現バリデータを使用する場合
garde = { version = "0.18", features = ["regex"] }
# Email検証を使用する場合
garde = { version = "0.18", features = ["email"] }
# URL検証を使用する場合
garde = { version = "0.18", features = ["url"] }
基本的な使い方
シンプルな検証
use garde::Validate;
#[derive(Debug, Validate)]
struct User {
#[garde(length(min = 3, max = 20))]
username: String,
#[garde(email)]
email: String,
#[garde(range(min = 18, max = 150))]
age: u8,
}
fn main() {
let user = User {
username: "john_doe".to_string(),
email: "[email protected]".to_string(),
age: 25,
};
match user.validate() {
Ok(()) => println!("検証成功!"),
Err(e) => println!("検証エラー: {}", e),
}
}
複数の検証ルール
use garde::Validate;
#[derive(Debug, Validate)]
struct Product {
#[garde(length(min = 1, max = 100))]
#[garde(ascii)]
name: String,
#[garde(range(min = 0.01))]
price: f64,
#[garde(length(max = 500))]
#[garde(optional)]
description: Option<String>,
#[garde(contains("category-"))]
category_id: String,
}
組み込みバリデータ
文字列検証
#[derive(Validate)]
struct StringValidation {
// 長さ検証
#[garde(length(min = 5, max = 50))]
title: String,
// パターンマッチング
#[garde(pattern(r"^[a-zA-Z0-9_]+$"))]
identifier: String,
// 前後の空白を除去
#[garde(trim)]
name: String,
// ASCII文字のみ
#[garde(ascii)]
ascii_only: String,
// 英数字のみ
#[garde(alphanumeric)]
code: String,
// プレフィックス検証
#[garde(prefix("user_"))]
user_id: String,
// サフィックス検証
#[garde(suffix(".com"))]
domain: String,
// 含む文字列検証
#[garde(contains("@"))]
email_like: String,
}
数値検証
#[derive(Validate)]
struct NumberValidation {
// 範囲検証
#[garde(range(min = 0, max = 100))]
percentage: u8,
// 最小値のみ
#[garde(range(min = 0.0))]
positive_amount: f64,
// 最大値のみ
#[garde(range(max = 1000))]
limited_quantity: i32,
}
コレクション検証
#[derive(Validate)]
struct CollectionValidation {
// 配列の長さ検証
#[garde(length(min = 1, max = 10))]
tags: Vec<String>,
// 要素ごとの検証
#[garde(inner(length(min = 3, max = 20)))]
usernames: Vec<String>,
// HashMapの検証
#[garde(length(min = 1))]
#[garde(inner(value(range(min = 0))))]
scores: std::collections::HashMap<String, i32>,
}
カスタムバリデータ
関数ベースのカスタムバリデータ
use garde::{Validate, ValidationError};
fn validate_password(password: &str) -> Result<(), ValidationError> {
if password.len() < 8 {
return Err(ValidationError::new("パスワードは8文字以上必要です"));
}
let has_uppercase = password.chars().any(|c| c.is_uppercase());
let has_lowercase = password.chars().any(|c| c.is_lowercase());
let has_digit = password.chars().any(|c| c.is_numeric());
if !has_uppercase || !has_lowercase || !has_digit {
return Err(ValidationError::new(
"パスワードは大文字、小文字、数字を含む必要があります"
));
}
Ok(())
}
#[derive(Validate)]
struct Account {
#[garde(custom(validate_password))]
password: String,
}
クロージャを使用したカスタム検証
#[derive(Validate)]
struct Order {
#[garde(custom(|value: &f64| {
if *value <= 0.0 {
return Err(ValidationError::new("金額は0より大きい必要があります"));
}
if (*value * 100.0).fract() != 0.0 {
return Err(ValidationError::new("小数点以下は2桁までです"));
}
Ok(())
}))]
amount: f64,
}
条件付き検証
#[derive(Validate)]
struct ConditionalValidation {
is_premium: bool,
// is_premiumがtrueの場合のみ検証
#[garde(length(min = 10), if = "is_premium")]
premium_code: Option<String>,
// カスタム条件
#[garde(range(min = 100), if = |obj: &Self| obj.is_premium)]
credit_limit: u32,
}
ネストした構造体の検証
#[derive(Validate)]
struct Address {
#[garde(length(min = 1, max = 100))]
street: String,
#[garde(length(min = 1, max = 50))]
city: String,
#[garde(pattern(r"^\d{3}-\d{4}$"))]
postal_code: String,
}
#[derive(Validate)]
struct Customer {
#[garde(length(min = 1, max = 50))]
name: String,
#[garde(email)]
email: String,
// ネストした構造体の検証
#[garde(nested)]
address: Address,
// オプショナルなネスト
#[garde(nested)]
billing_address: Option<Address>,
}
エラーハンドリング
use garde::{Validate, Report};
fn validate_user_input(user: User) -> Result<(), String> {
match user.validate() {
Ok(()) => Ok(()),
Err(report) => {
// エラーレポートの詳細を取得
let errors = format_validation_errors(&report);
Err(errors)
}
}
}
fn format_validation_errors(report: &Report) -> String {
let mut errors = Vec::new();
for (field, field_errors) in report.iter() {
for error in field_errors {
errors.push(format!("{}: {}", field, error.message()));
}
}
errors.join(", ")
}
Serde統合
use garde::Validate;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Validate)]
struct ApiRequest {
#[garde(length(min = 1, max = 100))]
title: String,
#[garde(range(min = 1))]
count: u32,
#[garde(email)]
contact_email: String,
}
// JSON APIでの使用例
fn handle_api_request(json_str: &str) -> Result<ApiRequest, String> {
// JSONをデシリアライズ
let request: ApiRequest = serde_json::from_str(json_str)
.map_err(|e| format!("JSONパースエラー: {}", e))?;
// 検証
request.validate()
.map_err(|e| format!("検証エラー: {}", e))?;
Ok(request)
}
高度な使用例
複雑なビジネスルール
use garde::{Validate, ValidationError};
use chrono::{NaiveDate, Utc};
#[derive(Validate)]
struct EventRegistration {
#[garde(length(min = 1, max = 100))]
event_name: String,
#[garde(custom(validate_future_date))]
event_date: NaiveDate,
#[garde(range(min = 1, max = 1000))]
attendees: u32,
#[garde(custom(validate_registration))]
registration: RegistrationDetails,
}
#[derive(Clone)]
struct RegistrationDetails {
early_bird: bool,
discount_code: Option<String>,
total_price: f64,
}
fn validate_future_date(date: &NaiveDate) -> Result<(), ValidationError> {
let today = Utc::now().naive_utc().date();
if *date <= today {
return Err(ValidationError::new("イベント日は未来の日付である必要があります"));
}
Ok(())
}
fn validate_registration(reg: &RegistrationDetails) -> Result<(), ValidationError> {
if reg.early_bird && reg.discount_code.is_some() {
return Err(ValidationError::new(
"早期割引とクーポンコードは併用できません"
));
}
if reg.total_price < 0.0 {
return Err(ValidationError::new("価格は0以上である必要があります"));
}
Ok(())
}
バリデーションコンテキスト
use garde::{Validate, ValidationContext};
#[derive(Validate)]
#[garde(context = ValidationConfig)]
struct ConfigurableValidation {
#[garde(custom(validate_with_config))]
value: String,
}
struct ValidationConfig {
min_length: usize,
allowed_prefixes: Vec<String>,
}
fn validate_with_config(
value: &str,
context: &ValidationConfig
) -> Result<(), ValidationError> {
if value.len() < context.min_length {
return Err(ValidationError::new(
format!("最小{}文字必要です", context.min_length)
));
}
let has_valid_prefix = context.allowed_prefixes.iter()
.any(|prefix| value.starts_with(prefix));
if !has_valid_prefix {
return Err(ValidationError::new("許可されたプレフィックスで始まる必要があります"));
}
Ok(())
}
パフォーマンス最適化
use garde::Validate;
use once_cell::sync::Lazy;
use regex::Regex;
// 正規表現をキャッシュ
static PHONE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^\+?[1-9]\d{1,14}$").unwrap()
});
#[derive(Validate)]
struct OptimizedValidation {
// キャッシュされた正規表現を使用
#[garde(custom(validate_phone))]
phone: String,
}
fn validate_phone(phone: &str) -> Result<(), ValidationError> {
if !PHONE_REGEX.is_match(phone) {
return Err(ValidationError::new("無効な電話番号形式です"));
}
Ok(())
}
まとめ
gardeは、Rustにおける型安全で表現力豊かなデータ検証を実現する優れたライブラリです。deriveマクロによる宣言的な検証定義、豊富な組み込みバリデータ、柔軟なカスタム検証機能により、複雑なビジネスルールも簡潔に実装できます。Serde統合により、Web APIやデータ処理パイプラインでの使用も容易です。