use std::borrow::Cow;
use crate::Violation;
#[macro_export]
macro_rules! validator {
(
@impl [$(#[$meta:meta])*]
$vis:vis $name:ident($arg:ident: &$ty:ty) $body:block ($code:expr)
) => {
$(#[$meta])*
$vis struct $name;
impl<T> $crate::Validator<T> for $name
where
T: ?::std::marker::Sized + ::std::borrow::Borrow<$ty>,
{
fn validate(value: &T) -> ::std::result::Result<(), $crate::Violation> {
let check = |$arg: &$ty| $body;
$crate::__private::into_result(
check(::std::borrow::Borrow::borrow(value)),
::std::stringify!($name),
$code,
)
}
}
};
(
$(#[$meta:meta])*
$vis:vis $name:ident($arg:ident: &$ty:ty) => $cond:expr, $message:expr,
code = $code:literal $(,)?
) => {
$crate::validator!(
@impl [$(#[$meta])*] $vis $name($arg: &$ty) {
if $cond { Ok(()) } else { Err($message) }
} (::std::option::Option::Some($code))
);
};
(
$(#[$meta:meta])*
$vis:vis $name:ident($arg:ident: &$ty:ty) => $cond:expr, $message:expr $(,)?
) => {
$crate::validator!(
@impl [$(#[$meta])*] $vis $name($arg: &$ty) {
if $cond { Ok(()) } else { Err($message) }
} (::std::option::Option::None)
);
};
(
$(#[$meta:meta])*
$vis:vis $name:ident($arg:ident: &$ty:ty) = $func:path, code = $code:literal $(,)?
) => {
$crate::validator!(
@impl [$(#[$meta])*] $vis $name($arg: &$ty) {
$func($arg)
} (::std::option::Option::Some($code))
);
};
(
$(#[$meta:meta])*
$vis:vis $name:ident($arg:ident: &$ty:ty) = $func:path $(,)?
) => {
$crate::validator!(
@impl [$(#[$meta])*] $vis $name($arg: &$ty) {
$func($arg)
} (::std::option::Option::None)
);
};
(
$(#[$meta:meta])*
$vis:vis $name:ident($arg:ident: &$ty:ty) $body:block, code = $code:literal $(,)?
) => {
$crate::validator!(
@impl [$(#[$meta])*] $vis $name($arg: &$ty) $body (::std::option::Option::Some($code))
);
};
(
$(#[$meta:meta])*
$vis:vis $name:ident($arg:ident: &$ty:ty) $body:block $(,)?
) => {
$crate::validator!(
@impl [$(#[$meta])*] $vis $name($arg: &$ty) $body (::std::option::Option::None)
);
};
}
pub trait ValidationResult {
fn into_result(self, name: &'static str, code: Option<&'static str>) -> Result<(), Violation>;
}
impl ValidationResult for bool {
fn into_result(self, name: &'static str, code: Option<&'static str>) -> Result<(), Violation> {
match self {
true => Ok(()),
false => Err("is not valid".into_violation(name, code)),
}
}
}
impl<E: IntoViolation> ValidationResult for Result<(), E> {
fn into_result(self, name: &'static str, code: Option<&'static str>) -> Result<(), Violation> {
self.map_err(|err| err.into_violation(name, code))
}
}
pub trait IntoViolation {
fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation;
}
impl IntoViolation for Violation {
fn into_violation(self, _name: &'static str, _code: Option<&'static str>) -> Violation {
self
}
}
fn code_of(name: &'static str, code: Option<&'static str>) -> Cow<'static, str> {
match code {
Some(code) => Cow::Borrowed(code),
None => Cow::Owned(snake_case(name)),
}
}
impl IntoViolation for &'static str {
fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation {
Violation::new(code_of(name, code), self)
}
}
impl IntoViolation for String {
fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation {
Violation::new(code_of(name, code), self)
}
}
impl IntoViolation for Cow<'static, str> {
fn into_violation(self, name: &'static str, code: Option<&'static str>) -> Violation {
Violation::new(code_of(name, code), self)
}
}
fn snake_case(name: &str) -> String {
let chars: Vec<char> = name.chars().collect();
let mut rv = String::with_capacity(name.len() + 4);
for (idx, &c) in chars.iter().enumerate() {
if c.is_uppercase() && idx > 0 {
let prev = chars[idx - 1];
let next_is_lower = chars.get(idx + 1).is_some_and(|x| x.is_lowercase());
if prev.is_lowercase()
|| prev.is_ascii_digit()
|| (prev.is_uppercase() && next_is_lower)
{
rv.push('_');
}
}
rv.extend(c.to_lowercase());
}
rv
}
#[doc(hidden)]
pub mod __private {
use super::ValidationResult;
use crate::Violation;
#[inline]
pub fn into_result<R: ValidationResult>(
rv: R,
name: &'static str,
code: Option<&'static str>,
) -> Result<(), Violation> {
rv.into_result(name, code)
}
}
#[test]
fn test_snake_case() {
assert_eq!(snake_case("NonZero"), "non_zero");
assert_eq!(snake_case("Slug"), "slug");
assert_eq!(snake_case("URL"), "url");
assert_eq!(snake_case("URLCheck"), "url_check");
assert_eq!(snake_case("MaxLen2"), "max_len2");
}