Rust验证器:嵌套的Option字段
下面这个结构体用于映射更新实体所需的载荷:
#[derive(Clone, Deserialize, ToSchema, Validate)]
pub struct BookUpdateset {
#[cfg_attr(
any(feature = "test-helpers", test),
serde(skip_serializing_if = "Option::is_none")
)]
#[schema(example = "Demian")]
#[serde(default, with = "::serde_with::rust::double_option")]
#[validate(length(min = 1, max = 1024))]
pub title: Option<Option<String>>,
#[cfg_attr(
any(feature = "test-helpers", test),
serde(skip_serializing_if = "Option::is_none")
)]
#[schema(example = "Hermann Hesse")]
#[serde(default, with = "::serde_with::rust::double_option")]
#[validate(length(min = 1, max = 1024))]
pub author: Option<Option<String>>,
#[cfg_attr(
any(feature = "test-helpers", test),
serde(skip_serializing_if = "Option::is_none")
)]
#[serde(default, with = "::serde_with::rust::double_option")]
pub publish_date: Option<Option<DateTime<Utc>>>,
}
使用“双选项模式”的原因是:若收到的JSON中没有某个字段,则不对该字段进行更新;但如果该字段被显式设为null,就将其设为null。然而只有 publish_date 字段可以为null。
我想用 validator crate验证,确保 author 和 title不会被显式设为null,也就是说它们不能是 Some(None)。
然而,当使用自定义函数时,validator会对最内层的类型进行解包。它试图把 &&String 传给该函数,而不是传给双选项(double option)。
我发现的变通办法是在结构体级别进行验证,在结构体顶部放置一个 #[validate(schema(function = "validate_required_fields"))],并且
fn validate_required_fields(updateset: &BookUpdateset) -> Result<(), ValidationError> {
helpers::validate_not_null(&updateset.title)?;
helpers::validate_not_null(&updateset.author)?;
Ok(())
}
其中
pub fn validate_not_null<T>(field: &Option<Option<T>>) -> Result<(), ValidationError> {
if let Some(None) = field {
return Err(ValidationError::new("Field cannot be null"));
}
Ok(())
}
这样做的问题是,最终的 ValidationError 无法保留是哪个字段未通过验证的详细信息,因为这个信息是在结构体级别出现的。需要澄清的是,这个细节只有在遍历 ValidationErrors 结构体时才会被暴露。我认为这个错误的类型会是 ValidationErrorKind::Struct,而不是 ValidationErrorKind::Field。
我希望能够实现类似这样的做法:
#[validate(length(min = 1, max = 1024), custom(function = "helpers::validate_not_null"))]
pub author: Option<Option<String>>,
但正如前面所说,它并不起作用。
你会怎么解决这个问题?
解决方案
有趣的变通办法——一些对某些类型有特殊处理的派生宏,通常在语法层面(也就是仅凭令牌)就会判断到这一点——因此你可以使用类型别名来规避它:
use chrono::{DateTime, Utc};
use serde::Deserialize;
use validator::{Validate, ValidationError};
type DoubleOption<T> = Option<Option<T>>;
#[derive(Clone, Deserialize, Validate)]
pub struct BookUpdateset {
#[serde(default, with = "::serde_with::rust::double_option")]
#[validate(length(min = 1, max = 1024), custom(function = "validate_not_null"))]
pub title: DoubleOption<String>,
#[serde(default, with = "::serde_with::rust::double_option")]
#[validate(length(min = 1, max = 1024), custom(function = "validate_not_null"))]
pub author: DoubleOption<String>,
#[serde(default, with = "::serde_with::rust::double_option")]
pub publish_date: DoubleOption<DateTime<Utc>>,
}
pub fn validate_not_null<T>(field: &Option<Option<T>>) -> Result<(), ValidationError> {
if let Some(None) = field {
return Err(ValidationError::new("Field cannot be null"));
}
Ok(())
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。