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