1#![doc = include_str!("../README.md")]
2
3mod error;
4
5#[cfg(feature = "boolean")]
6mod boolean;
7#[cfg(feature = "either")]
8mod either;
9#[cfg(feature = "enumeration")]
10mod enumeration;
11#[cfg(feature = "float")]
12mod float;
13#[cfg(feature = "integer")]
14mod integer;
15#[cfg(feature = "list")]
16mod list;
17#[cfg(feature = "net")]
18mod net;
19#[cfg(feature = "non_empty")]
20mod non_empty;
21#[cfg(feature = "number")]
22mod number;
23#[cfg(feature = "path")]
24mod path;
25#[cfg(feature = "percentage")]
26mod percentage;
27#[cfg(feature = "static_map")]
28mod static_map;
29#[cfg(feature = "string")]
30mod string;
31
32#[cfg(feature = "bytesize")]
33mod bytesize;
34#[cfg(feature = "cidr")]
35mod cidr;
36#[cfg(feature = "datetime")]
37mod datetime;
38#[cfg(feature = "duration")]
39mod duration;
40#[cfg(feature = "dynamic_map")]
41mod dynamic_map;
42#[cfg(feature = "encoding")]
43mod encoding;
44#[cfg(feature = "regex")]
45mod regex;
46#[cfg(feature = "schema")]
47mod schema;
48#[cfg(feature = "semver")]
49mod semver;
50#[cfg(feature = "url")]
51mod url;
52#[cfg(feature = "uuid")]
53mod uuid;
54
55pub use error::{Error, ErrorKind, Segment};
56pub use tanzim_value::{LocatedValue, Location, Map, Value, ValueType};
57
58#[cfg(feature = "boolean")]
59pub use boolean::Bool;
60#[cfg(feature = "dynamic_map")]
61pub use dynamic_map::DynamicMap;
62#[cfg(feature = "either")]
63pub use either::Either;
64#[cfg(feature = "enumeration")]
65pub use enumeration::Enum;
66#[cfg(feature = "float")]
67pub use float::Float;
68#[cfg(feature = "integer")]
69pub use integer::Integer;
70#[cfg(feature = "list")]
71pub use list::List;
72#[cfg(feature = "net")]
73pub use net::{Domain, Email, Host, IpAddr, Port, SocketAddr};
74#[cfg(feature = "non_empty")]
75pub use non_empty::NonEmpty;
76#[cfg(feature = "number")]
77pub use number::Number;
78#[cfg(feature = "path")]
79pub use path::{Path, PathKind};
80#[cfg(feature = "percentage")]
81pub use percentage::Percentage;
82#[cfg(feature = "static_map")]
83pub use static_map::StaticMap;
84#[cfg(feature = "string")]
85pub use string::Str;
86
87#[cfg(feature = "bytesize")]
88pub use bytesize::ByteSize;
89#[cfg(feature = "cidr")]
90pub use cidr::Cidr;
91#[cfg(feature = "datetime")]
92pub use datetime::{Date, DateTime};
93#[cfg(feature = "duration")]
94pub use duration::Duration;
95#[cfg(feature = "encoding")]
96pub use encoding::{Base64, Hex};
97#[cfg(feature = "regex")]
98pub use regex::RegexPattern;
99#[cfg(feature = "schema")]
100pub use schema::{
101 Constructor, Node, Registry, SchemaError, SchemaErrorKind, SchemaValue, build, build_value,
102};
103#[cfg(feature = "semver")]
104pub use semver::Semver;
105#[cfg(feature = "url")]
106pub use url::Url;
107#[cfg(feature = "uuid")]
108pub use uuid::Uuid;
109
110#[derive(Debug, Clone, PartialEq, Default)]
118pub struct Meta {
119 pub name: String,
120 pub description: Option<String>,
121 pub examples: Vec<(Value, Option<String>)>,
123 pub default: Option<Value>,
124 pub convert: Option<ValueType>,
126}
127
128impl Meta {
129 pub fn new(name: impl Into<String>) -> Self {
131 Self {
132 name: name.into(),
133 ..Self::default()
134 }
135 }
136}
137
138pub trait Validator {
147 fn meta(&self) -> &Meta;
149
150 fn meta_mut(&mut self) -> &mut Meta;
152
153 fn check(&self, value: &mut Value) -> Result<(), Error>;
155
156 fn validate(&self, value: &mut Value) -> Result<(), Error> {
159 if matches!(value, Value::Null)
160 && let Some(default) = self.meta().default.as_ref()
161 {
162 *value = default.clone();
163 }
164 if let Err(error) = self.check(value) {
165 return Err(error.with_meta(self.meta()));
166 }
167 if let Some(target) = self.meta().convert {
168 cast(value, target).map_err(|error| error.with_meta(self.meta()))?;
169 }
170 Ok(())
171 }
172}
173
174impl<V: Validator + 'static> From<V> for Box<dyn Validator> {
175 fn from(validator: V) -> Self {
176 Box::new(validator)
177 }
178}
179
180#[macro_export]
186macro_rules! impl_meta_methods {
187 ($ty:ty) => {
188 #[allow(clippy::wrong_self_convention)]
189 impl $ty {
190 pub fn name(&self) -> &str {
192 &<$ty as $crate::Validator>::meta(self).name
193 }
194
195 pub fn description(&self) -> Option<&str> {
197 <$ty as $crate::Validator>::meta(self)
198 .description
199 .as_deref()
200 }
201
202 pub fn examples(&self) -> &[(tanzim_value::Value, Option<String>)] {
204 &<$ty as $crate::Validator>::meta(self).examples
205 }
206
207 pub fn default_value(&self) -> Option<&tanzim_value::Value> {
209 <$ty as $crate::Validator>::meta(self).default.as_ref()
210 }
211
212 pub fn convert(&self) -> Option<tanzim_value::ValueType> {
214 <$ty as $crate::Validator>::meta(self).convert
215 }
216
217 pub fn with_name(mut self, name: impl Into<String>) -> Self {
219 <$ty as $crate::Validator>::meta_mut(&mut self).name = name.into();
220 self
221 }
222
223 pub fn with_description(mut self, text: impl Into<String>) -> Self {
225 <$ty as $crate::Validator>::meta_mut(&mut self).description = Some(text.into());
226 self
227 }
228
229 pub fn with_example(mut self, value: impl Into<tanzim_value::Value>) -> Self {
231 <$ty as $crate::Validator>::meta_mut(&mut self)
232 .examples
233 .push((value.into(), None));
234 self
235 }
236
237 pub fn with_example_noted(
239 mut self,
240 value: impl Into<tanzim_value::Value>,
241 note: impl Into<String>,
242 ) -> Self {
243 <$ty as $crate::Validator>::meta_mut(&mut self)
244 .examples
245 .push((value.into(), Some(note.into())));
246 self
247 }
248
249 pub fn with_default(mut self, value: impl Into<tanzim_value::Value>) -> Self {
251 <$ty as $crate::Validator>::meta_mut(&mut self).default = Some(value.into());
252 self
253 }
254
255 pub fn to_string(mut self) -> Self {
257 <$ty as $crate::Validator>::meta_mut(&mut self).convert =
258 Some(tanzim_value::ValueType::String);
259 self
260 }
261
262 pub fn to_int(mut self) -> Self {
264 <$ty as $crate::Validator>::meta_mut(&mut self).convert =
265 Some(tanzim_value::ValueType::Int);
266 self
267 }
268
269 pub fn to_float(mut self) -> Self {
271 <$ty as $crate::Validator>::meta_mut(&mut self).convert =
272 Some(tanzim_value::ValueType::Float);
273 self
274 }
275
276 pub fn to_bool(mut self) -> Self {
278 <$ty as $crate::Validator>::meta_mut(&mut self).convert =
279 Some(tanzim_value::ValueType::Bool);
280 self
281 }
282 }
283 };
284}
285
286fn cast(value: &mut Value, target: ValueType) -> Result<(), Error> {
289 if matches!(value, Value::Null) {
290 return Ok(());
291 }
292 if value.type_name() == target {
293 return Ok(());
294 }
295 let converted = match target {
296 ValueType::String => Value::String(match value {
297 Value::Bool(inner) => inner.to_string(),
298 Value::Int(inner) => inner.to_string(),
299 Value::Float(inner) => inner.to_string(),
300 Value::String(inner) => std::mem::take(inner),
301 _ => {
302 return Err(Error::new(ErrorKind::NotConvertible {
303 target,
304 found: value.type_name(),
305 }));
306 }
307 }),
308 ValueType::Int => match value {
309 Value::Int(inner) => Value::Int(*inner),
310 Value::Bool(inner) => Value::Int(*inner as isize),
311 Value::Float(inner) if inner.fract() == 0.0 => Value::Int(*inner as isize),
312 Value::String(inner) if inner.parse::<isize>().is_ok() => {
313 Value::Int(inner.parse::<isize>().unwrap())
314 }
315 _ => {
316 return Err(Error::new(ErrorKind::NotConvertible {
317 target,
318 found: value.type_name(),
319 }));
320 }
321 },
322 ValueType::Float => match value {
323 Value::Float(inner) => Value::Float(*inner),
324 Value::Int(inner) => Value::Float(*inner as f64),
325 Value::String(inner) if inner.parse::<f64>().is_ok() => {
326 Value::Float(inner.parse::<f64>().unwrap())
327 }
328 _ => {
329 return Err(Error::new(ErrorKind::NotConvertible {
330 target,
331 found: value.type_name(),
332 }));
333 }
334 },
335 ValueType::Bool => match value {
336 Value::Bool(inner) => Value::Bool(*inner),
337 Value::String(inner) if inner.eq_ignore_ascii_case("true") => Value::Bool(true),
338 Value::String(inner) if inner.eq_ignore_ascii_case("false") => Value::Bool(false),
339 _ => {
340 return Err(Error::new(ErrorKind::NotConvertible {
341 target,
342 found: value.type_name(),
343 }));
344 }
345 },
346 ValueType::List | ValueType::Map | ValueType::Null => {
347 return Err(Error::new(ErrorKind::NotConvertible {
348 target,
349 found: value.type_name(),
350 }));
351 }
352 };
353 *value = converted;
354 Ok(())
355}
356
357pub fn validate(validator: &dyn Validator, value: &mut LocatedValue) -> Result<(), Error> {
371 match validator.validate(value.value_mut()) {
372 Ok(()) => Ok(()),
373 Err(error) => Err(error.with_location(value.location())),
374 }
375}