Serde

Rustの強力なシリアライゼーション・デシリアライゼーションフレームワーク。カスタムバリデーションと型安全性を提供

概要

Serdeは、Rustにおけるデータのシリアライゼーション(直列化)とデシリアライゼーション(逆直列化)のための事実上の標準フレームワークです。JSON、YAML、TOML、MessagePackなど、様々なデータフォーマットに対応し、デシリアライゼーション時の強力なバリデーション機能を提供します。

主な特徴

  • ゼロコスト抽象化: コンパイル時に最適化され、手書きのコードと同等のパフォーマンス
  • 型安全性: Rustの型システムを活用した堅牢なバリデーション
  • Deriveマクロ: #[derive(Serialize, Deserialize)]による簡単な実装
  • カスタマイズ可能: 独自のシリアライゼーション・バリデーションロジックの実装が可能
  • エコシステム: 多数のデータフォーマットクレートとの統合

インストール

Cargo.tomlに以下を追加:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"  # JSON形式を使用する場合

基本的な使い方

シンプルな構造体のデシリアライゼーション

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
    age: u8,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let json_data = r#"
        {
            "id": 1,
            "name": "田中太郎",
            "email": "[email protected]",
            "age": 25
        }
    "#;
    
    // デシリアライゼーション(自動的に型バリデーション)
    let user: User = serde_json::from_str(json_data)?;
    println!("{:?}", user);
    
    // 不正なデータの場合、エラーが発生
    let invalid_json = r#"{"id": "文字列", "name": "田中"}"#;
    match serde_json::from_str::<User>(invalid_json) {
        Ok(_) => println!("成功"),
        Err(e) => println!("バリデーションエラー: {}", e),
    }
    
    Ok(())
}

バリデーション機能

1. 型ベースのバリデーション

Rustの型システムを活用した基本的なバリデーション:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Product {
    name: String,
    price: f64,        // 負の値も許可される
    quantity: u32,     // 負の値は自動的に拒否
    in_stock: bool,
}

// Option型を使った必須・任意フィールド
#[derive(Debug, Serialize, Deserialize)]
struct Order {
    id: u64,
    product_id: u64,
    quantity: u32,
    notes: Option<String>,  // 任意フィールド
}

2. カスタムバリデーション

デシリアライゼーション時のカスタムバリデーション実装:

use serde::{Deserialize, Deserializer, Serialize};
use serde::de::{self, Visitor};
use std::fmt;

#[derive(Debug, Serialize, Deserialize)]
struct Account {
    username: String,
    #[serde(deserialize_with = "validate_email")]
    email: String,
    #[serde(deserialize_with = "validate_age")]
    age: u8,
}

// メールアドレスのバリデーション
fn validate_email<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let email = String::deserialize(deserializer)?;
    
    if email.contains('@') && email.contains('.') {
        Ok(email)
    } else {
        Err(de::Error::custom("不正なメールアドレス形式です"))
    }
}

// 年齢のバリデーション
fn validate_age<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
    D: Deserializer<'de>,
{
    let age = u8::deserialize(deserializer)?;
    
    if age >= 18 && age <= 120 {
        Ok(age)
    } else {
        Err(de::Error::custom("年齢は18歳以上120歳以下である必要があります"))
    }
}

3. 新型(NewType)パターンによるバリデーション

より強力な型安全性を実現:

use serde::{Deserialize, Serialize};
use std::str::FromStr;

// メールアドレス型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "String")]
struct Email(String);

impl TryFrom<String> for Email {
    type Error = String;
    
    fn try_from(value: String) -> Result<Self, Self::Error> {
        if value.contains('@') && value.contains('.') {
            Ok(Email(value))
        } else {
            Err("不正なメールアドレス形式です".to_string())
        }
    }
}

// 正の整数型
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(try_from = "i32")]
struct PositiveInt(i32);

impl TryFrom<i32> for PositiveInt {
    type Error = String;
    
    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value > 0 {
            Ok(PositiveInt(value))
        } else {
            Err("値は正の整数である必要があります".to_string())
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct ValidatedUser {
    email: Email,
    score: PositiveInt,
}

4. 複雑なバリデーションルール

構造体全体のバリデーション:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(try_from = "RawPassword")]
struct Password {
    value: String,
}

#[derive(Deserialize)]
struct RawPassword {
    value: String,
}

impl TryFrom<RawPassword> for Password {
    type Error = String;
    
    fn try_from(raw: RawPassword) -> Result<Self, Self::Error> {
        let value = raw.value;
        
        // パスワードポリシーのチェック
        if value.len() < 8 {
            return Err("パスワードは8文字以上である必要があります".to_string());
        }
        
        let has_upper = value.chars().any(|c| c.is_uppercase());
        let has_lower = value.chars().any(|c| c.is_lowercase());
        let has_digit = value.chars().any(|c| c.is_digit(10));
        let has_special = value.chars().any(|c| !c.is_alphanumeric());
        
        if !has_upper || !has_lower || !has_digit || !has_special {
            return Err(
                "パスワードは大文字、小文字、数字、特殊文字を含む必要があります"
                    .to_string()
            );
        }
        
        Ok(Password { value })
    }
}

エラーハンドリング

カスタムエラー型の実装

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug)]
enum ValidationError {
    #[error("必須フィールドが不足しています: {0}")]
    MissingField(String),
    
    #[error("値が範囲外です: {value} (許可範囲: {min}..{max})")]
    OutOfRange { value: i32, min: i32, max: i32 },
    
    #[error("不正な形式: {0}")]
    InvalidFormat(String),
}

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    #[serde(deserialize_with = "validate_port")]
    port: u16,
    #[serde(deserialize_with = "validate_timeout")]
    timeout: u32,
}

fn validate_port<'de, D>(deserializer: D) -> Result<u16, D::Error>
where
    D: Deserializer<'de>,
{
    let port = u16::deserialize(deserializer)?;
    
    if port < 1024 {
        Err(de::Error::custom(
            ValidationError::OutOfRange {
                value: port as i32,
                min: 1024,
                max: 65535,
            }
        ))
    } else {
        Ok(port)
    }
}

fn validate_timeout<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
    D: Deserializer<'de>,
{
    let timeout = u32::deserialize(deserializer)?;
    
    if timeout == 0 || timeout > 300 {
        Err(de::Error::custom(
            ValidationError::OutOfRange {
                value: timeout as i32,
                min: 1,
                max: 300,
            }
        ))
    } else {
        Ok(timeout)
    }
}

実践的な例

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

use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};

#[derive(Debug, Serialize, Deserialize)]
struct CreateUserRequest {
    #[serde(deserialize_with = "validate_username")]
    username: String,
    #[serde(deserialize_with = "validate_email")]
    email: String,
    #[serde(deserialize_with = "validate_password")]
    password: String,
    #[serde(default = "default_role")]
    role: UserRole,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum UserRole {
    Admin,
    User,
    Guest,
}

fn default_role() -> UserRole {
    UserRole::User
}

fn validate_username<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let username = String::deserialize(deserializer)?;
    
    if username.len() < 3 || username.len() > 20 {
        return Err(de::Error::custom(
            "ユーザー名は3文字以上20文字以下である必要があります"
        ));
    }
    
    if !username.chars().all(|c| c.is_alphanumeric() || c == '_') {
        return Err(de::Error::custom(
            "ユーザー名は英数字とアンダースコアのみ使用可能です"
        ));
    }
    
    Ok(username)
}

fn validate_password<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let password = String::deserialize(deserializer)?;
    
    if password.len() < 8 {
        return Err(de::Error::custom(
            "パスワードは8文字以上である必要があります"
        ));
    }
    
    Ok(password)
}

// 使用例
fn handle_create_user(json: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request: CreateUserRequest = serde_json::from_str(json)?;
    
    println!("新規ユーザー作成: {:?}", request);
    // ここでユーザー作成処理を実行
    
    Ok(())
}

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

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize)]
struct AppConfig {
    #[serde(deserialize_with = "validate_app_name")]
    app_name: String,
    
    #[serde(deserialize_with = "validate_version")]
    version: String,
    
    database: DatabaseConfig,
    
    #[serde(default)]
    features: HashMap<String, bool>,
}

#[derive(Debug, Serialize, Deserialize)]
struct DatabaseConfig {
    #[serde(deserialize_with = "validate_connection_string")]
    connection_string: String,
    
    #[serde(default = "default_pool_size")]
    pool_size: u32,
    
    #[serde(deserialize_with = "validate_timeout")]
    timeout_seconds: u32,
}

fn validate_app_name<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let name = String::deserialize(deserializer)?;
    
    if name.is_empty() {
        Err(de::Error::custom("アプリケーション名は必須です"))
    } else {
        Ok(name)
    }
}

fn validate_version<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let version = String::deserialize(deserializer)?;
    
    // セマンティックバージョニングの簡易チェック
    let parts: Vec<&str> = version.split('.').collect();
    if parts.len() != 3 {
        return Err(de::Error::custom(
            "バージョンはX.Y.Z形式である必要があります"
        ));
    }
    
    for part in parts {
        if part.parse::<u32>().is_err() {
            return Err(de::Error::custom(
                "バージョン番号は数値である必要があります"
            ));
        }
    }
    
    Ok(version)
}

fn validate_connection_string<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let conn_str = String::deserialize(deserializer)?;
    
    if !conn_str.starts_with("postgresql://") && !conn_str.starts_with("mysql://") {
        return Err(de::Error::custom(
            "サポートされていないデータベース接続文字列です"
        ));
    }
    
    Ok(conn_str)
}

fn default_pool_size() -> u32 {
    10
}

// 使用例
fn load_config(toml_str: &str) -> Result<AppConfig, Box<dyn std::error::Error>> {
    let config: AppConfig = toml::from_str(toml_str)?;
    Ok(config)
}

ベストプラクティス

1. エラーメッセージの明確化

fn validate_field<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let value = String::deserialize(deserializer)?;
    
    // 具体的で分かりやすいエラーメッセージ
    if value.is_empty() {
        Err(de::Error::custom("フィールドは空にできません"))
    } else if value.len() > 100 {
        Err(de::Error::custom(format!(
            "フィールドの長さは100文字以下である必要があります(現在: {}文字)",
            value.len()
        )))
    } else {
        Ok(value)
    }
}

2. 再利用可能なバリデータの作成

mod validators {
    use serde::{Deserialize, Deserializer};
    use serde::de;
    
    pub fn non_empty_string<'de, D>(deserializer: D) -> Result<String, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        if s.trim().is_empty() {
            Err(de::Error::custom("空の文字列は許可されていません"))
        } else {
            Ok(s)
        }
    }
    
    pub fn positive_number<'de, D>(deserializer: D) -> Result<i32, D::Error>
    where
        D: Deserializer<'de>,
    {
        let n = i32::deserialize(deserializer)?;
        if n <= 0 {
            Err(de::Error::custom("正の数値である必要があります"))
        } else {
            Ok(n)
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Product {
    #[serde(deserialize_with = "validators::non_empty_string")]
    name: String,
    #[serde(deserialize_with = "validators::positive_number")]
    price: i32,
}

3. テストの作成

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_valid_user() {
        let json = r#"
            {
                "username": "test_user",
                "email": "[email protected]",
                "password": "SecurePass123!"
            }
        "#;
        
        let result = serde_json::from_str::<CreateUserRequest>(json);
        assert!(result.is_ok());
    }
    
    #[test]
    fn test_invalid_email() {
        let json = r#"
            {
                "username": "test_user",
                "email": "invalid-email",
                "password": "SecurePass123!"
            }
        "#;
        
        let result = serde_json::from_str::<CreateUserRequest>(json);
        assert!(result.is_err());
        
        let error = result.unwrap_err();
        assert!(error.to_string().contains("メールアドレス"));
    }
    
    #[test]
    fn test_short_password() {
        let json = r#"
            {
                "username": "test_user",
                "email": "[email protected]",
                "password": "short"
            }
        "#;
        
        let result = serde_json::from_str::<CreateUserRequest>(json);
        assert!(result.is_err());
        
        let error = result.unwrap_err();
        assert!(error.to_string().contains("8文字以上"));
    }
}

まとめ

Serdeは、Rustの型システムと組み合わせることで、強力かつ柔軟なバリデーション機能を提供します。型ベースの基本的なバリデーションから、カスタムバリデータ、新型パターン、複雑なビジネスルールの実装まで、様々なレベルのバリデーションが可能です。エラーハンドリングを適切に実装し、明確なエラーメッセージを提供することで、堅牢なアプリケーションを構築できます。