Rust结构体验证库superstruct的使用,superstruct提供强类型数据验证和结构体转换功能

Rust结构体验证库superstruct的使用,superstruct提供强类型数据验证和结构体转换功能

SuperStruct是一个用于处理相关结构体变体的库,每个变体共享一些公共字段,并添加自己的唯一字段。

test status crates.io

项目展示

SuperStruct被用于以下项目:

  • sigp/lighthouse: 以太坊共识客户端

许可证

Apache 2.0

安装

在项目目录中运行以下Cargo命令:

cargo add superstruct

或者在Cargo.toml中添加以下行:

superstruct = "0.10.0"

完整示例代码

use superstruct::superstruct;

// 定义一个基础结构体
#[superstruct(
    variants(V1, V2, V3),  // 定义变体V1,V2,V3
    variant_attributes(derive(Debug, PartialEq))  // 为变体添加属性
)]
#[derive(Debug, PartialEq)]
pub struct Foo {
    pub common_field: String,
    #[superstruct(only(V1))]
    pub v1_field: u32,
    #[superstruct(only(V2))]
    pub v2_field: String,
    #[superstruct(only(V3))]
    pub v3_field: Vec<u8>,
}

fn main() {
    // 创建V1变体实例
    let foo_v1 = Foo::V1 {
        common_field: "common".to_string(),
        v1_field: 42
    };
    
    // 创建V2变体实例
    let foo_v2 = Foo::V2 {
        common_field: "common".to_string(),
        v2_field: "v2 specific".to_string()
    };
    
    // 访问公共字段
    assert_eq!(foo_v1.common_field(), "common");
    assert_eq!(foo_v2.common_field(), "common");
    
    // 访问变体特定字段
    match foo_v1 {
        Foo::V1(v1) => assert_eq!(v1.v1_field, 42),
        _ => panic!("Expected V1 variant")
    }
    
    match foo_v2 {
        Foo::V2(v2) => assert_eq!(v2.v2_field, "v2 specific"),
        _ => panic!("Expected V2 variant")
    }
    
    // 转换示例
    let foo_v1_as_enum: Foo = foo_v1.into();
    match foo_v1_as_enum {
        Foo::V1(_) => println!("It's a V1"),
        _ => panic!("Should be V1")
    }
}

代码说明

  1. 使用#[superstruct]宏定义了一个基础结构体Foo和它的三个变体(V1,V2,V3)
  2. 每个变体都包含一个公共字段common_field
  3. 每个变体有自己的特定字段(v1_field, v2_field, v3_field)
  4. 示例展示了如何创建不同变体的实例
  5. 演示了如何访问公共字段和变体特定字段
  6. 展示了如何将具体变体转换为枚举类型进行模式匹配

这个库特别适合处理协议版本演进或API不同版本的数据结构,可以方便地维护共享字段同时处理变体特定的字段。

完整示例Demo

以下是一个更完整的示例,展示superstruct在实际应用中的使用方式:

use superstruct::superstruct;
use serde::{Serialize, Deserialize};

// 定义一个API响应结构体,支持多个版本
#[superstruct(
    variants(V1, V2),
    variant_attributes(derive(Debug, Clone, PartialEq, Serialize, Deserialize))
)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ApiResponse {
    // 公共字段
    pub status: u16,
    pub message: String,
    
    // V1版本特有字段
    #[superstruct(only(V1))]
    pub timestamp: String,
    
    // V2版本特有字段
    #[superstruct(only(V2))]
    pub data: Vec<u8>,
    #[superstruct(only(V2))]
    pub checksum: String,
}

fn handle_response(response: ApiResponse) {
    println!("Response status: {}", response.status());
    println!("Message: {}", response.message());
    
    match response {
        ApiResponse::V1(v1) => {
            println!("V1 response - Timestamp: {}", v1.timestamp);
        }
        ApiResponse::V2(v2) => {
            println!("V2 response - Data length: {}, Checksum: {}", 
                v2.data.len(), v2.checksum);
        }
    }
}

fn main() {
    // 创建V1响应
    let v1_response = ApiResponse::V1 {
        status: 200,
        message: "OK".to_string(),
        timestamp: "2023-01-01T00:00:00Z".to_string(),
    };
    
    // 创建V2响应
    let v2_response = ApiResponse::V2 {
        status: 200,
        message: "OK".to_string(),
        data: vec![1, 2, 3, 4, 5],
        checksum: "abc123".to_string(),
    };
    
    // 处理不同版本的响应
    handle_response(v1_response.into());
    handle_response(v2_response.into());
    
    // 序列化/反序列化示例
    let json_v1 = serde_json::to_string(&v1_response).unwrap();
    println!("Serialized V1: {}", json_v1);
    
    let json_v2 = serde_json::to_string(&v2_response).unwrap();
    println!("Serialized V2: {}", json_v2);
}

这个完整示例展示了:

  1. 定义支持多版本API响应的结构体
  2. 不同版本有公共字段和特有字段
  3. 如何处理不同版本的响应
  4. 结合serde实现序列化/反序列化
  5. 类型安全地访问不同版本的字段

1 回复

Rust结构体验证库superstruct的使用指南

介绍

superstruct是一个Rust库,专注于提供强类型的数据验证和结构体转换功能。它允许你定义数据结构的预期形状,然后验证输入数据是否符合该结构,并在验证过程中自动转换为强类型结构体。

主要特性:

  • 声明式验证规则
  • 自动错误收集
  • 支持嵌套结构验证
  • 类型安全的转换
  • 友好的错误信息

安装

在Cargo.toml中添加依赖:

[dependencies]
superstruct = "0.6"

基本使用方法

1. 定义结构体

use superstruct::superstruct;

#[superstruct]
struct User {
    id: String,
    name: String,
    #[superstruct(only(V2))]
    email: String,
    age: u8,
}

2. 验证和转换

let data = serde_json::json!({
    "id": "123",
    "name": "Alice",
    "age": 25
});

let user = User::from_value(data).unwrap();
println!("User: {:?}", user);

进阶用法

版本化结构体

#[superstruct]
struct Message {
    text: String,
    #[superstruct(only(V2))]
    timestamp: i64,
    #[superstruct(only(V1))]
    is_read: bool,
}

// 使用V1版本
let v1_data = serde_json::json!({
    "text": "Hello",
    "is_read": true
});
let msg_v1 = Message::from_value(v1_data).unwrap();

// 使用V2版本
let v2_data = serde_json::json!({
    "text": "Hello",
    "timestamp": 1672531200
});
let msg_v2 = Message::from_value(v2_data).unwrap();

自定义验证

#[superstruct]
struct Product {
    id: String,
    #[superstruct(validator = "validate_price")]
    price: f64,
}

fn validate_price(price: &f64) -> Result<(), String> {
    if *price > 0.0 {
        Ok(())
    } else {
        Err("Price must be positive".to_string())
    }
}

错误处理

let invalid_data = serde_json::json!({
    "id": "123",
    "price": -10.0
});

match Product::from_value(invalid_data) {
    Ok(_) => println!("Valid product"),
    Err(e) => println!("Validation errors: {:?}", e),
}

实际示例

API请求验证

#[superstruct]
struct ApiRequest {
    #[superstruct(validator = "validate_api_key")]
    api_key: String,
    action: String,
    #[superstruct(only(PremiumRequest))]
    premium_feature: bool,
}

fn validate_api_key(key: &str) -> Result<(), String> {
    if key.starts_with("sk_") && key.len() == 32 {
        Ok(())
    } else {
        Err("Invalid API key format".to_string())
    }
}

// 使用
let request_data = serde_json::json!({
    "api_key": "sk_123456789012345678901234567890",
    "action": "create"
});

let request = ApiRequest::from_value(request_data).unwrap();

配置验证

#[superstruct]
struct AppConfig {
    #[superstruct(validator = "validate_port")]
    port: u16,
    db_url: String,
    #[superstruct(default = "false")]
    debug_mode: bool,
}

fn validate_port(port: &u16) -> Result<(), String> {
    if *port > 1024 {
        Ok(())
    } else {
        Err("Port must be greater than 1024".to_string())
    }
}

// 使用默认值
let config_data = serde_json::json!({
    "port": 8080,
    "db_url": "postgres://user:pass@localhost/db"
});

let config = AppConfig::from_value(config_data).unwrap();
assert_eq!(config.debug_mode(), false);

完整示例demo

下面是一个完整的示例,展示了如何使用superstruct进行用户注册数据的验证:

use superstruct::superstruct;
use serde_json;

// 定义用户结构体
#[superstruct]
struct UserRegistration {
    #[superstruct(validator = "validate_username")]
    username: String,
    #[superstruct(validator = "validate_email")]
    email: String,
    #[superstruct(validator = "validate_password")]
    password: String,
    #[superstruct(default = "false")]
    is_verified: bool,
}

// 用户名验证函数
fn validate_username(username: &str) -> Result<(), String> {
    if username.len() >= 3 && username.len() <= 20 {
        Ok(())
    } else {
        Err("Username must be between 3 and 20 characters".to_string())
    }
}

// 邮箱验证函数
fn validate_email(email: &str) -> Result<(), String> {
    if email.contains('@') && email.contains('.') {
        Ok(())
    } else {
        Err("Invalid email format".to_string())
    }
}

// 密码验证函数
fn validate_password(password: &str) -> Result<(), String> {
    if password.len() >= 8 {
        Ok(())
    } else {
        Err("Password must be at least 8 characters".to_string())
    }
}

fn main() {
    // 有效数据示例
    let valid_data = serde_json::json!({
        "username": "rustfan",
        "email": "user@example.com",
        "password": "secure123"
    });
    
    match UserRegistration::from_value(valid_data) {
        Ok(user) => {
            println!("Registration successful!");
            println!("Username: {}", user.username());
            println!("Email: {}", user.email());
            println!("Verified: {}", user.is_verified());
        }
        Err(e) => println!("Validation errors: {:?}", e),
    }

    // 无效数据示例
    let invalid_data = serde_json::json!({
        "username": "a",  // 太短
        "email": "invalid",  // 格式错误
        "password": "short"  // 太短
    });
    
    match UserRegistration::from_value(invalid_data) {
        Ok(_) => println!("This should not happen!"),
        Err(e) => {
            println!("Registration failed with errors:");
            for error in e {
                println!("- {}", error);
            }
        }
    }
}

这个完整示例展示了:

  1. 定义带有验证规则的结构体
  2. 实现自定义验证函数
  3. 使用默认值
  4. 处理验证成功和失败的情况
  5. 输出友好的错误信息

superstruct通过提供类型安全的验证和转换,可以帮助你在Rust应用中构建更健壮的数据处理流程,特别是在处理外部输入(如API请求、配置文件等)时非常有用。

回到顶部