为何Rocket框架的验证逻辑无法在Json类型参数上生效?
问题描述
在使用Rocket框架时,为CreateUserDto结构体字段添加的#[field(validate)]验证规则(含自定义邮箱验证、内置范围验证),仅在端点使用Form<CreateUserDto>作为参数时生效;改用Json<CreateUserDto>作为参数时,所有验证逻辑完全不触发。
相关代码
带验证规则的DTO定义
#[derive(Debug, Deserialize, FromForm)] pub struct CreateUserDto<'r> { #[field(validate = range(2..10))] id: usize, #[field(validate = email_validation())] pub email: &'r str, pub password: &'r str, } fn email_validation(email: &str) -> Result<()> { let re: Regex = Regex::new(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$").unwrap(); if !re.is_match(email) { Err(Error::validation("invalid email address"))?; } Ok(()) }
验证不生效的JSON端点
#[post("/", data = "<input>")] fn create_user(input: Json<CreateUserDto>) { let user = input.into_inner(); println!("email {0}, password: {1}", user.email, user.password); }
验证生效的Form端点
#[post("/", data = "<input>")] fn create_user(input: Form<CreateUserDto>) { let user = input.into_inner(); println!("email {0}, password: {1}", user.email, user.password); }
解决方案
Rocket的#[field(validate)]注解是专为FromForm trait设计的,仅在解析表单数据(Form<T>类型)时触发验证;而Json<T>依赖serde完成反序列化,不会识别这类验证规则。要让验证逻辑对JSON请求生效,可采用以下方案:
方案1:端点内手动调用验证
为DTO实现统一的验证方法,在端点获取JSON数据后手动触发验证:
#[derive(Debug, Deserialize, FromForm)] pub struct CreateUserDto<'r> { pub id: usize, pub email: &'r str, pub password: &'r str, } impl<'r> CreateUserDto<'r> { fn validate(&self) -> Result<(), Error> { // 验证id范围 if !(2..10).contains(&self.id) { return Err(Error::validation("id must be between 2 and 9")); } // 验证邮箱格式 let re = Regex::new(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$").unwrap(); if !re.is_match(self.email) { return Err(Error::validation("invalid email address")); } Ok(()) } } #[post("/", data = "<input>")] fn create_user(input: Json<CreateUserDto>) -> Result<(), Error> { let user = input.into_inner(); user.validate()?; println!("email {0}, password: {1}", user.email, user.password); Ok(()) }
方案2:自定义serde反序列化嵌入验证
在JSON反序列化过程中直接嵌入验证逻辑,确保反序列化完成后数据必然合法:
use serde::{Deserialize, Deserializer}; use rocket::serde::de::Error as SerdeError; #[derive(Debug, FromForm)] pub struct CreateUserDto<'r> { pub id: usize, pub email: &'r str, pub password: &'r str, } impl<'de: 'r, 'r> Deserialize<'de> for CreateUserDto<'r> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { // 定义内部结构体完成基础反序列化 #[derive(Deserialize)] struct InnerDto<'r> { id: usize, email: &'r str, password: &'r str, } let inner = InnerDto::deserialize(deserializer)?; // 验证id范围 if !(2..10).contains(&inner.id) { return Err(SerdeError::custom("id must be between 2 and 9")); } // 验证邮箱格式 let re = Regex::new(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$").unwrap(); if !re.is_match(inner.email) { return Err(SerdeError::custom("invalid email address")); } Ok(CreateUserDto { id: inner.id, email: inner.email, password: inner.password, }) } } #[post("/", data = "<input>")] fn create_user(input: Json<CreateUserDto>) { let user = input.into_inner(); println!("email {0}, password: {1}", user.email, user.password); }
方案3:使用第三方验证库扩展
引入rocket_validation这类第三方库,实现类似表单验证的注解式JSON验证:
- 添加依赖到
Cargo.toml:
[dependencies] rocket_validation = "0.1.0" # 请使用最新版本
- 修改DTO定义:
use rocket_validation::Validate; #[derive(Debug, Deserialize, FromForm, Validate)] pub struct CreateUserDto<'r> { #[validate(range(min = 2, max = 9))] pub id: usize, #[validate(email)] pub email: &'r str, pub password: &'r str, }
- 端点内调用验证:
#[post("/", data = "<input>")] fn create_user(input: Json<CreateUserDto>) -> Result<(), Error> { let user = input.into_inner(); user.validate()?; println!("email {0}, password: {1}", user.email, user.password); Ok(()) }
内容的提问来源于stack exchange,提问作者vlio20
相关产品推荐
相关产品推荐

