1use std::borrow::Cow;
3
4use crate::Violation;
5
6#[macro_export]
79macro_rules! validator {
80 (
81 @impl [$(#[$meta:meta])*]
82 $vis:vis $name:ident($arg:ident: &$ty:ty) $body:block ($code:expr)
83 ) => {
84 $(#[$meta])*
85 $vis struct $name;
86
87 impl<T> $crate::Validator<T> for $name
88 where
89 T: ?::std::marker::Sized + ::std::borrow::Borrow<$ty>,
90 {
91 fn validate(value: &T) -> ::std::result::Result<(), $crate::Violation> {
92 let check = |$arg: &$ty| $body;
93 $crate::__private::into_result(
94 check(::std::borrow::Borrow::borrow(value)),
95 ::std::stringify!($name),
96 $code,
97 )
98 }
99 }
100 };
101 (
102 $(#[$meta:meta])*
103 $vis:vis $name:ident($arg:ident: &$ty:ty) => $cond:expr, $message:expr,
104 code = $code:literal $(,)?
105 ) => {
106 $crate::validator!(
107 @impl [$(#[$meta])*] $vis $name($arg: &$ty) {
108 if $cond { Ok(()) } else { Err($message) }
109 } (::std::option::Option::Some($code))
110 );
111 };
112 (
113 $(#[$meta:meta])*
114 $vis:vis $name:ident($arg:ident: &$ty:ty) => $cond:expr, $message:expr $(,)?
115 ) => {
116 $crate::validator!(
117 @impl [$(#[$meta])*] $vis $name($arg: &$ty) {
118 if $cond { Ok(()) } else { Err($message) }
119 } (::std::option::Option::None)
120 );
121 };
122 (
123 $(#[$meta:meta])*
124 $vis:vis $name:ident($arg:ident: &$ty:ty) = $func:path, code = $code:literal $(,)?
125 ) => {
126 $crate::validator!(
127 @impl [$(#[$meta])*] $vis $name($arg: &$ty) {
128 $func($arg)
129 } (::std::option::Option::Some($code))
130 );
131 };
132 (
133 $(#[$meta:meta])*
134 $vis:vis $name:ident($arg:ident: &$ty:ty) = $func:path $(,)?
135 ) => {
136 $crate::validator!(
137 @impl [$(#[$meta])*] $vis $name($arg: &$ty) {
138 $func($arg)
139 } (::std::option::Option::None)
140 );
141 };
142 (
143 $(#[$meta:meta])*
144 $vis:vis $name:ident($arg:ident: &$ty:ty) $body:block, code = $code:literal $(,)?
145 ) => {
146 $crate::validator!(
147 @impl [$(#[$meta])*] $vis $name($arg: &$ty) $body (::std::option::Option::Some($code))
148 );
149 };
150 (
151 $(#[$meta:meta])*
152 $vis:vis $name:ident($arg:ident: &$ty:ty) $body:block $(,)?
153 ) => {
154 $crate::validator!(
155 @impl [$(#[$meta])*] $vis $name($arg: &$ty) $body (::std::option::Option::None)
156 );
157 };
158}
159
160pub trait ValidationResult {
162 fn into_result(self, name: &'static str, code: Option<&'static str>) -> Result<(), Violation>;
166}
167
168impl ValidationResult for bool {
169 fn into_result(self, name: &'static str, code: Option<&'static str>) -> Result<(), Violation> {
170 match self {
171 true => Ok(()),
172 false => Err("is not valid".into_violation(name, code)),
173 }
174 }
175}
176
177impl<E: IntoViolation> ValidationResult for Result<(), E> {
178 fn into_result(self, name: &'static str, code: Option<&'static str>) -> Result<(), Violation> {
179 self.map_err(|err| err.into_violation(name, code))
180 }
181}
182
183pub trait IntoViolation {
185 fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation;
191}
192
193impl IntoViolation for Violation {
194 fn into_violation(self, _name: &'static str, _code: Option<&'static str>) -> Violation {
195 self
196 }
197}
198
199fn code_of(name: &'static str, code: Option<&'static str>) -> Cow<'static, str> {
201 match code {
202 Some(code) => Cow::Borrowed(code),
203 None => Cow::Owned(snake_case(name)),
204 }
205}
206
207impl IntoViolation for &'static str {
208 fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation {
209 Violation::new(code_of(name, code), self)
210 }
211}
212
213impl IntoViolation for String {
214 fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation {
215 Violation::new(code_of(name, code), self)
216 }
217}
218
219impl IntoViolation for Cow<'static, str> {
220 fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation {
221 Violation::new(code_of(name, code), self)
222 }
223}
224
225fn snake_case(name: &str) -> String {
229 let chars: Vec<char> = name.chars().collect();
230 let mut rv = String::with_capacity(name.len() + 4);
231 for (idx, &c) in chars.iter().enumerate() {
232 if c.is_uppercase() && idx > 0 {
233 let prev = chars[idx - 1];
234 let next_is_lower = chars.get(idx + 1).is_some_and(|x| x.is_lowercase());
235 if prev.is_lowercase()
236 || prev.is_ascii_digit()
237 || (prev.is_uppercase() && next_is_lower)
238 {
239 rv.push('_');
240 }
241 }
242 rv.extend(c.to_lowercase());
243 }
244 rv
245}
246
247#[doc(hidden)]
249pub mod __private {
250 use super::ValidationResult;
251 use crate::Violation;
252
253 #[inline]
254 pub fn into_result<R: ValidationResult>(
255 rv: R,
256 name: &'static str,
257 code: Option<&'static str>,
258 ) -> Result<(), Violation> {
259 rv.into_result(name, code)
260 }
261}
262
263#[test]
264fn test_snake_case() {
265 assert_eq!(snake_case("NonZero"), "non_zero");
266 assert_eq!(snake_case("Slug"), "slug");
267 assert_eq!(snake_case("URL"), "url");
268 assert_eq!(snake_case("URLCheck"), "url_check");
269 assert_eq!(snake_case("MaxLen2"), "max_len2");
270}