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#[allow(clippy::wrong_self_convention)]
186pub trait WithMeta: Validator + Sized {
187 fn name(&self) -> &str {
189 &self.meta().name
190 }
191
192 fn description(&self) -> Option<&str> {
194 self.meta().description.as_deref()
195 }
196
197 fn examples(&self) -> &[(Value, Option<String>)] {
199 &self.meta().examples
200 }
201
202 fn default_value(&self) -> Option<&Value> {
204 self.meta().default.as_ref()
205 }
206
207 fn convert(&self) -> Option<ValueType> {
209 self.meta().convert
210 }
211
212 fn with_name(mut self, name: impl Into<String>) -> Self {
214 self.meta_mut().name = name.into();
215 self
216 }
217
218 fn with_description(mut self, text: impl Into<String>) -> Self {
220 self.meta_mut().description = Some(text.into());
221 self
222 }
223
224 fn with_example(mut self, value: impl Into<Value>) -> Self {
226 self.meta_mut().examples.push((value.into(), None));
227 self
228 }
229
230 fn with_example_noted(mut self, value: impl Into<Value>, note: impl Into<String>) -> Self {
232 self.meta_mut()
233 .examples
234 .push((value.into(), Some(note.into())));
235 self
236 }
237
238 fn with_default(mut self, value: impl Into<Value>) -> Self {
240 self.meta_mut().default = Some(value.into());
241 self
242 }
243
244 fn to_string(mut self) -> Self {
246 self.meta_mut().convert = Some(ValueType::String);
247 self
248 }
249
250 fn to_int(mut self) -> Self {
252 self.meta_mut().convert = Some(ValueType::Int);
253 self
254 }
255
256 fn to_float(mut self) -> Self {
258 self.meta_mut().convert = Some(ValueType::Float);
259 self
260 }
261
262 fn to_bool(mut self) -> Self {
264 self.meta_mut().convert = Some(ValueType::Bool);
265 self
266 }
267}
268
269impl<T: Validator> WithMeta for T {}
270
271fn cast(value: &mut Value, target: ValueType) -> Result<(), Error> {
274 if matches!(value, Value::Null) {
275 return Ok(());
276 }
277 if value.type_name() == target {
278 return Ok(());
279 }
280 let converted = match target {
281 ValueType::String => Value::String(match value {
282 Value::Bool(inner) => inner.to_string(),
283 Value::Int(inner) => inner.to_string(),
284 Value::Float(inner) => inner.to_string(),
285 Value::String(inner) => std::mem::take(inner),
286 _ => {
287 return Err(Error::new(ErrorKind::NotConvertible {
288 target,
289 found: value.type_name(),
290 }));
291 }
292 }),
293 ValueType::Int => match value {
294 Value::Int(inner) => Value::Int(*inner),
295 Value::Bool(inner) => Value::Int(*inner as isize),
296 Value::Float(inner) if inner.fract() == 0.0 => Value::Int(*inner as isize),
297 Value::String(inner) if inner.parse::<isize>().is_ok() => {
298 Value::Int(inner.parse::<isize>().unwrap())
299 }
300 _ => {
301 return Err(Error::new(ErrorKind::NotConvertible {
302 target,
303 found: value.type_name(),
304 }));
305 }
306 },
307 ValueType::Float => match value {
308 Value::Float(inner) => Value::Float(*inner),
309 Value::Int(inner) => Value::Float(*inner as f64),
310 Value::String(inner) if inner.parse::<f64>().is_ok() => {
311 Value::Float(inner.parse::<f64>().unwrap())
312 }
313 _ => {
314 return Err(Error::new(ErrorKind::NotConvertible {
315 target,
316 found: value.type_name(),
317 }));
318 }
319 },
320 ValueType::Bool => match value {
321 Value::Bool(inner) => Value::Bool(*inner),
322 Value::String(inner) if inner.eq_ignore_ascii_case("true") => Value::Bool(true),
323 Value::String(inner) if inner.eq_ignore_ascii_case("false") => Value::Bool(false),
324 _ => {
325 return Err(Error::new(ErrorKind::NotConvertible {
326 target,
327 found: value.type_name(),
328 }));
329 }
330 },
331 ValueType::List | ValueType::Map | ValueType::Null => {
332 return Err(Error::new(ErrorKind::NotConvertible {
333 target,
334 found: value.type_name(),
335 }));
336 }
337 };
338 *value = converted;
339 Ok(())
340}
341
342pub fn validate(validator: &dyn Validator, value: &mut LocatedValue) -> Result<(), Error> {
356 match validator.validate(value.value_mut()) {
357 Ok(()) => Ok(()),
358 Err(error) => Err(error.with_location(value.location())),
359 }
360}