#![doc = include_str!("../README.md")]
mod error;
#[cfg(feature = "boolean")]
mod boolean;
#[cfg(feature = "either")]
mod either;
#[cfg(feature = "enumeration")]
mod enumeration;
#[cfg(feature = "float")]
mod float;
#[cfg(feature = "integer")]
mod integer;
#[cfg(feature = "list")]
mod list;
#[cfg(feature = "net")]
mod net;
#[cfg(feature = "non_empty")]
mod non_empty;
#[cfg(feature = "number")]
mod number;
#[cfg(feature = "path")]
mod path;
#[cfg(feature = "percentage")]
mod percentage;
#[cfg(feature = "static_map")]
mod static_map;
#[cfg(feature = "string")]
mod string;
#[cfg(feature = "bytesize")]
mod bytesize;
#[cfg(feature = "cidr")]
mod cidr;
#[cfg(feature = "datetime")]
mod datetime;
#[cfg(feature = "duration")]
mod duration;
#[cfg(feature = "dynamic_map")]
mod dynamic_map;
#[cfg(feature = "encoding")]
mod encoding;
#[cfg(feature = "regex")]
mod regex;
#[cfg(feature = "schema")]
mod schema;
#[cfg(feature = "semver")]
mod semver;
#[cfg(feature = "url")]
mod url;
#[cfg(feature = "uuid")]
mod uuid;
pub use error::{Error, ErrorKind, Segment};
pub use tanzim_value::{LocatedValue, Location, Map, Value, ValueType};
#[cfg(feature = "boolean")]
pub use boolean::Bool;
#[cfg(feature = "dynamic_map")]
pub use dynamic_map::DynamicMap;
#[cfg(feature = "either")]
pub use either::Either;
#[cfg(feature = "enumeration")]
pub use enumeration::Enum;
#[cfg(feature = "float")]
pub use float::Float;
#[cfg(feature = "integer")]
pub use integer::Integer;
#[cfg(feature = "list")]
pub use list::List;
#[cfg(feature = "net")]
pub use net::{Domain, Email, Host, IpAddr, Port, SocketAddr};
#[cfg(feature = "non_empty")]
pub use non_empty::NonEmpty;
#[cfg(feature = "number")]
pub use number::Number;
#[cfg(feature = "path")]
pub use path::{Path, PathKind};
#[cfg(feature = "percentage")]
pub use percentage::Percentage;
#[cfg(feature = "static_map")]
pub use static_map::StaticMap;
#[cfg(feature = "string")]
pub use string::Str;
#[cfg(feature = "bytesize")]
pub use bytesize::ByteSize;
#[cfg(feature = "cidr")]
pub use cidr::Cidr;
#[cfg(feature = "datetime")]
pub use datetime::{Date, DateTime};
#[cfg(feature = "duration")]
pub use duration::Duration;
#[cfg(feature = "encoding")]
pub use encoding::{Base64, Hex};
#[cfg(feature = "regex")]
pub use regex::RegexPattern;
#[cfg(feature = "schema")]
pub use schema::{
Constructor, Node, Registry, SchemaError, SchemaErrorKind, SchemaValue, build, build_value,
};
#[cfg(feature = "semver")]
pub use semver::Semver;
#[cfg(feature = "url")]
pub use url::Url;
#[cfg(feature = "uuid")]
pub use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Meta {
pub name: String,
pub description: Option<String>,
pub examples: Vec<(Value, Option<String>)>,
pub default: Option<Value>,
pub convert: Option<ValueType>,
}
impl Meta {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Self::default()
}
}
}
pub trait Validator {
fn meta(&self) -> &Meta;
fn meta_mut(&mut self) -> &mut Meta;
fn check(&self, value: &mut Value) -> Result<(), Error>;
fn validate(&self, value: &mut Value) -> Result<(), Error> {
if matches!(value, Value::Null)
&& let Some(default) = self.meta().default.as_ref()
{
*value = default.clone();
}
if let Err(error) = self.check(value) {
return Err(error.with_meta(self.meta()));
}
if let Some(target) = self.meta().convert {
cast(value, target).map_err(|error| error.with_meta(self.meta()))?;
}
Ok(())
}
}
impl<V: Validator + 'static> From<V> for Box<dyn Validator> {
fn from(validator: V) -> Self {
Box::new(validator)
}
}
#[macro_export]
macro_rules! impl_meta_methods {
($ty:ty) => {
#[allow(clippy::wrong_self_convention)]
impl $ty {
pub fn name(&self) -> &str {
&<$ty as $crate::Validator>::meta(self).name
}
pub fn description(&self) -> Option<&str> {
<$ty as $crate::Validator>::meta(self)
.description
.as_deref()
}
pub fn examples(&self) -> &[(tanzim_value::Value, Option<String>)] {
&<$ty as $crate::Validator>::meta(self).examples
}
pub fn default_value(&self) -> Option<&tanzim_value::Value> {
<$ty as $crate::Validator>::meta(self).default.as_ref()
}
pub fn convert(&self) -> Option<tanzim_value::ValueType> {
<$ty as $crate::Validator>::meta(self).convert
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).name = name.into();
self
}
pub fn with_description(mut self, text: impl Into<String>) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).description = Some(text.into());
self
}
pub fn with_example(mut self, value: impl Into<tanzim_value::Value>) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self)
.examples
.push((value.into(), None));
self
}
pub fn with_example_noted(
mut self,
value: impl Into<tanzim_value::Value>,
note: impl Into<String>,
) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self)
.examples
.push((value.into(), Some(note.into())));
self
}
pub fn with_default(mut self, value: impl Into<tanzim_value::Value>) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).default = Some(value.into());
self
}
pub fn to_string(mut self) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).convert =
Some(tanzim_value::ValueType::String);
self
}
pub fn to_int(mut self) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).convert =
Some(tanzim_value::ValueType::Int);
self
}
pub fn to_float(mut self) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).convert =
Some(tanzim_value::ValueType::Float);
self
}
pub fn to_bool(mut self) -> Self {
<$ty as $crate::Validator>::meta_mut(&mut self).convert =
Some(tanzim_value::ValueType::Bool);
self
}
}
};
}
fn cast(value: &mut Value, target: ValueType) -> Result<(), Error> {
if matches!(value, Value::Null) {
return Ok(());
}
if value.type_name() == target {
return Ok(());
}
let converted = match target {
ValueType::String => Value::String(match value {
Value::Bool(inner) => inner.to_string(),
Value::Int(inner) => inner.to_string(),
Value::Float(inner) => inner.to_string(),
Value::String(inner) => std::mem::take(inner),
_ => {
return Err(Error::new(ErrorKind::NotConvertible {
target,
found: value.type_name(),
}));
}
}),
ValueType::Int => match value {
Value::Int(inner) => Value::Int(*inner),
Value::Bool(inner) => Value::Int(*inner as isize),
Value::Float(inner) if inner.fract() == 0.0 => Value::Int(*inner as isize),
Value::String(inner) if inner.parse::<isize>().is_ok() => {
Value::Int(inner.parse::<isize>().unwrap())
}
_ => {
return Err(Error::new(ErrorKind::NotConvertible {
target,
found: value.type_name(),
}));
}
},
ValueType::Float => match value {
Value::Float(inner) => Value::Float(*inner),
Value::Int(inner) => Value::Float(*inner as f64),
Value::String(inner) if inner.parse::<f64>().is_ok() => {
Value::Float(inner.parse::<f64>().unwrap())
}
_ => {
return Err(Error::new(ErrorKind::NotConvertible {
target,
found: value.type_name(),
}));
}
},
ValueType::Bool => match value {
Value::Bool(inner) => Value::Bool(*inner),
Value::String(inner) if inner.eq_ignore_ascii_case("true") => Value::Bool(true),
Value::String(inner) if inner.eq_ignore_ascii_case("false") => Value::Bool(false),
_ => {
return Err(Error::new(ErrorKind::NotConvertible {
target,
found: value.type_name(),
}));
}
},
ValueType::List | ValueType::Map | ValueType::Null => {
return Err(Error::new(ErrorKind::NotConvertible {
target,
found: value.type_name(),
}));
}
};
*value = converted;
Ok(())
}
pub fn validate(validator: &dyn Validator, value: &mut LocatedValue) -> Result<(), Error> {
match validator.validate(value.value_mut()) {
Ok(()) => Ok(()),
Err(error) => Err(error.with_location(value.location())),
}
}