Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-config/src/validator.rs')
| -rw-r--r-- | helix-config/src/validator.rs | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/helix-config/src/validator.rs b/helix-config/src/validator.rs new file mode 100644 index 00000000..7c56c358 --- /dev/null +++ b/helix-config/src/validator.rs @@ -0,0 +1,296 @@ +use std::any::{type_name, Any}; +use std::error::Error; +use std::fmt::Debug; +use std::marker::PhantomData; + +use anyhow::{bail, ensure, Result}; + +use crate::any::ConfigData; +use crate::Value; + +pub trait Validator: 'static + Debug { + fn validate(&self, val: Value) -> Result<ConfigData>; +} + +pub trait Ty: Sized + Clone + 'static { + fn from_value(val: Value) -> Result<Self>; + fn to_value(&self) -> Value; +} + +#[derive(Clone, Copy)] +pub struct IntegerRangeValidator<T> { + pub min: isize, + pub max: isize, + ty: PhantomData<T>, +} +impl<E, T> IntegerRangeValidator<T> +where + E: Debug, + T: TryInto<isize, Error = E>, +{ + pub fn new(min: T, max: T) -> Self { + Self { + min: min.try_into().unwrap(), + max: max.try_into().unwrap(), + ty: PhantomData, + } + } +} + +impl<T> Debug for IntegerRangeValidator<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IntegerRangeValidator") + .field("min", &self.min) + .field("max", &self.max) + .field("ty", &type_name::<T>()) + .finish() + } +} + +impl<E, T> IntegerRangeValidator<T> +where + E: Error + Sync + Send + 'static, + T: Any + TryFrom<isize, Error = E>, +{ + pub fn validate(&self, val: Value) -> Result<T> { + let IntegerRangeValidator { min, max, .. } = *self; + let Value::Int(val) = val else { + bail!("expected an integer") + }; + ensure!( + min <= val && val <= max, + "expected an integer between {min} and {max} (got {val})", + ); + Ok(T::try_from(val)?) + } +} +impl<E, T> Validator for IntegerRangeValidator<T> +where + E: Error + Sync + Send + 'static, + T: Any + TryFrom<isize, Error = E>, +{ + fn validate(&self, val: Value) -> Result<ConfigData> { + Ok(ConfigData::new(self.validate(val))) + } +} + +macro_rules! integer_tys { + ($($ty: ident),*) => { + $( + impl Ty for $ty { + fn to_value(&self) -> Value { + Value::Int((*self).try_into().unwrap()) + } + + fn from_value(val: Value) -> Result<Self> { + IntegerRangeValidator::new($ty::MIN, $ty::MAX).validate(val) + } + } + )* + + }; +} + +integer_tys! { + i8, i16, i32, isize, + u8, u16, u32 +} + +impl Ty for usize { + fn to_value(&self) -> Value { + Value::Int((*self).try_into().unwrap()) + } + + fn from_value(val: Value) -> Result<Self> { + IntegerRangeValidator::new(0usize, isize::MAX as usize).validate(val) + } +} + +impl Ty for u64 { + fn to_value(&self) -> Value { + Value::Int((*self).try_into().unwrap()) + } + + fn from_value(val: Value) -> Result<Self> { + IntegerRangeValidator::new(0u64, isize::MAX as u64).validate(val) + } +} + +impl Ty for bool { + fn to_value(&self) -> Value { + Value::Bool(*self) + } + fn from_value(val: Value) -> Result<Self> { + let Value::Bool(val) = val else { + bail!("expected a boolean") + }; + Ok(val) + } +} + +impl Ty for Box<str> { + fn to_value(&self) -> Value { + Value::String(self.clone().into_string()) + } + fn from_value(val: Value) -> Result<Self> { + let Value::String(val) = val else { + bail!("expected a string") + }; + Ok(val.into_boxed_str()) + } +} + +impl Ty for char { + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } + + fn from_value(val: Value) -> Result<Self> { + let Value::String(val) = val else { + bail!("expected a string") + }; + ensure!( + val.chars().count() == 1, + "expecet a single character (got {val:?})" + ); + Ok(val.chars().next().unwrap()) + } +} + +impl Ty for std::string::String { + fn to_value(&self) -> Value { + Value::String(self.clone()) + } + fn from_value(val: Value) -> Result<Self> { + let Value::String(val) = val else { + bail!("expected a string") + }; + Ok(val) + } +} + +impl<T: Ty> Ty for Option<T> { + fn to_value(&self) -> Value { + match self { + Some(_) => todo!(), + None => todo!(), + } + } + + fn from_value(val: Value) -> Result<Self> { + if val == Value::Null { + return Ok(None); + } + Ok(Some(T::from_value(val)?)) + } +} + +impl<T: Ty> Ty for Box<T> { + fn from_value(val: Value) -> Result<Self> { + Ok(Box::new(T::from_value(val)?)) + } + + fn to_value(&self) -> Value { + T::to_value(self) + } +} + +impl<T: Ty> Ty for indexmap::IndexMap<Box<str>, T, ahash::RandomState> { + fn from_value(val: Value) -> Result<Self> { + let Value::Map(map) = val else { + bail!("expected a map"); + }; + map.into_iter() + .map(|(k, v)| Ok((k, T::from_value(v)?))) + .collect() + } + + fn to_value(&self) -> Value { + let map = self + .iter() + .map(|(k, v)| (k.clone(), v.to_value())) + .collect(); + Value::Map(Box::new(map)) + } +} + +impl<T: Ty> Ty for Box<[T]> { + fn to_value(&self) -> Value { + Value::List(self.iter().map(T::to_value).collect()) + } + fn from_value(val: Value) -> Result<Self> { + let Value::List(val) = val else { + bail!("expected a list") + }; + val.iter().cloned().map(T::from_value).collect() + } +} + +impl Ty for serde_json::Value { + fn from_value(val: Value) -> Result<Self> { + Ok(val.into()) + } + + fn to_value(&self) -> Value { + self.into() + } +} + +pub(super) struct StaticValidator<T: Ty> { + pub(super) ty: PhantomData<fn(&T)>, +} + +impl<T: Ty> Debug for StaticValidator<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StaticValidator") + .field("ty", &type_name::<T>()) + .finish() + } +} + +impl<T: Ty> Validator for StaticValidator<T> { + fn validate(&self, val: Value) -> Result<ConfigData> { + let val = <T as Ty>::from_value(val)?; + Ok(ConfigData::new(val)) + } +} + +pub struct TyValidator<F, T: Ty> { + pub(super) ty: PhantomData<fn(&T)>, + f: F, +} + +impl<T: Ty, F> Debug for TyValidator<F, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TyValidator") + .field("ty", &type_name::<T>()) + .finish() + } +} + +impl<T, F> Validator for TyValidator<F, T> +where + T: Ty, + F: Fn(&T) -> anyhow::Result<()> + 'static, +{ + fn validate(&self, val: Value) -> Result<ConfigData> { + let val = <T as Ty>::from_value(val)?; + (self.f)(&val)?; + Ok(ConfigData::new(val)) + } +} + +pub fn ty_validator<T, F>(f: F) -> impl Validator +where + T: Ty, + F: Fn(&T) -> anyhow::Result<()> + 'static, +{ + TyValidator { ty: PhantomData, f } +} + +pub fn regex_str_validator() -> impl Validator { + ty_validator(|val: &crate::String| { + regex_syntax::parse(val)?; + Ok(()) + }) +} |