#[cfg(feature = "validate")]
use facet_reflect::Partial;
#[cfg(feature = "validate")]
use crate::DeserializeError;
use crate::{FormatDeserializer, FormatParser};
impl<'input, const BORROW: bool, P> FormatDeserializer<'input, BORROW, P>
where
P: FormatParser<'input>,
{
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
pub(crate) fn run_field_validators(
&self,
field: &facet_core::Field,
wip: &Partial<'input, BORROW>,
) -> Result<(), DeserializeError<P::Error>> {
use facet_core::ValidatorFn;
let Some(data_ptr) = wip.data_ptr() else {
return Ok(());
};
for attr in field.attributes.iter() {
if attr.ns != Some("validate") {
continue;
}
let validation_result: Result<(), String> = match attr.key {
"custom" => {
let validator_fn = unsafe { *attr.data.ptr().get::<ValidatorFn>() };
unsafe { validator_fn(data_ptr) }
}
"min" => {
let min_val = unsafe { *attr.data.ptr().get::<i64>() };
self.validate_min(data_ptr, wip.shape(), min_val)
}
"max" => {
let max_val = unsafe { *attr.data.ptr().get::<i64>() };
self.validate_max(data_ptr, wip.shape(), max_val)
}
"min_length" => {
let min_len = unsafe { *attr.data.ptr().get::<usize>() };
self.validate_min_length(data_ptr, wip.shape(), min_len)
}
"max_length" => {
let max_len = unsafe { *attr.data.ptr().get::<usize>() };
self.validate_max_length(data_ptr, wip.shape(), max_len)
}
"email" => self.validate_email(data_ptr, wip.shape()),
"url" => self.validate_url(data_ptr, wip.shape()),
"regex" => {
let pattern = unsafe { *attr.data.ptr().get::<&'static str>() };
self.validate_regex(data_ptr, wip.shape(), pattern)
}
"contains" => {
let needle = unsafe { *attr.data.ptr().get::<&'static str>() };
self.validate_contains(data_ptr, wip.shape(), needle)
}
_ => Ok(()), };
if let Err(message) = validation_result {
return Err(DeserializeError::Validation {
field: field.name,
message,
span: self.last_span,
path: Some(self.current_path.clone()),
});
}
}
Ok(())
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_min(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
min_val: i64,
) -> Result<(), String> {
use facet_core::ScalarType;
let actual = match shape.scalar_type() {
Some(ScalarType::I8) => (unsafe { *ptr.get::<i8>() }) as i64,
Some(ScalarType::I16) => (unsafe { *ptr.get::<i16>() }) as i64,
Some(ScalarType::I32) => (unsafe { *ptr.get::<i32>() }) as i64,
Some(ScalarType::I64) => unsafe { *ptr.get::<i64>() },
Some(ScalarType::U8) => (unsafe { *ptr.get::<u8>() }) as i64,
Some(ScalarType::U16) => (unsafe { *ptr.get::<u16>() }) as i64,
Some(ScalarType::U32) => (unsafe { *ptr.get::<u32>() }) as i64,
Some(ScalarType::U64) => {
let v = unsafe { *ptr.get::<u64>() };
if v > i64::MAX as u64 {
return Ok(()); }
v as i64
}
_ => return Ok(()), };
if actual < min_val {
Err(format!("must be >= {}, got {}", min_val, actual))
} else {
Ok(())
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_max(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
max_val: i64,
) -> Result<(), String> {
use facet_core::ScalarType;
let actual = match shape.scalar_type() {
Some(ScalarType::I8) => (unsafe { *ptr.get::<i8>() }) as i64,
Some(ScalarType::I16) => (unsafe { *ptr.get::<i16>() }) as i64,
Some(ScalarType::I32) => (unsafe { *ptr.get::<i32>() }) as i64,
Some(ScalarType::I64) => unsafe { *ptr.get::<i64>() },
Some(ScalarType::U8) => (unsafe { *ptr.get::<u8>() }) as i64,
Some(ScalarType::U16) => (unsafe { *ptr.get::<u16>() }) as i64,
Some(ScalarType::U32) => (unsafe { *ptr.get::<u32>() }) as i64,
Some(ScalarType::U64) => {
let v = unsafe { *ptr.get::<u64>() };
if v > i64::MAX as u64 {
return Err(format!("must be <= {}, got {}", max_val, v));
}
v as i64
}
_ => return Ok(()), };
if actual > max_val {
Err(format!("must be <= {}, got {}", max_val, actual))
} else {
Ok(())
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_min_length(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
min_len: usize,
) -> Result<(), String> {
let len = self.get_length(ptr, shape)?;
if len < min_len {
Err(format!("length must be >= {}, got {}", min_len, len))
} else {
Ok(())
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_max_length(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
max_len: usize,
) -> Result<(), String> {
let len = self.get_length(ptr, shape)?;
if len > max_len {
Err(format!("length must be <= {}, got {}", max_len, len))
} else {
Ok(())
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn get_length(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
) -> Result<usize, String> {
if shape.is_type::<String>() {
let s = unsafe { ptr.get::<String>() };
return Ok(s.len());
}
if shape.is_type::<&str>() {
let s = unsafe { *ptr.get::<&str>() };
return Ok(s.len());
}
Ok(0)
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_email(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
) -> Result<(), String> {
let s = self.get_string(ptr, shape)?;
if facet_validate::is_valid_email(s) {
Ok(())
} else {
Err(format!("'{}' is not a valid email address", s))
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_url(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
) -> Result<(), String> {
let s = self.get_string(ptr, shape)?;
if facet_validate::is_valid_url(s) {
Ok(())
} else {
Err(format!("'{}' is not a valid URL", s))
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_regex(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
pattern: &str,
) -> Result<(), String> {
let s = self.get_string(ptr, shape)?;
if facet_validate::matches_pattern(s, pattern) {
Ok(())
} else {
Err(format!("'{}' does not match pattern '{}'", s, pattern))
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn validate_contains(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
needle: &str,
) -> Result<(), String> {
let s = self.get_string(ptr, shape)?;
if s.contains(needle) {
Ok(())
} else {
Err(format!("'{}' does not contain '{}'", s, needle))
}
}
#[cfg(feature = "validate")]
#[allow(unsafe_code)]
fn get_string<'s>(
&self,
ptr: facet_core::PtrConst,
shape: &'static facet_core::Shape,
) -> Result<&'s str, String> {
if shape.is_type::<String>() {
let s = unsafe { ptr.get::<String>() };
return Ok(s.as_str());
}
if shape.is_type::<&str>() {
let s = unsafe { *ptr.get::<&str>() };
return Ok(s);
}
Err("expected string type".to_string())
}
}