use std::borrow::Cow;
use std::cell::Cell;
use std::fmt::Display;
use std::mem::transmute;
use std::ptr::NonNull;
pub use crate::rules::ip::IpKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidEmail {
Empty,
MissingAt,
UserLengthExceeded,
InvalidUser,
DomainLengthExceeded,
InvalidDomain,
}
impl Display for InvalidEmail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvalidEmail::Empty => f.write_str("value is empty"),
InvalidEmail::MissingAt => f.write_str("value is missing `@`"),
InvalidEmail::UserLengthExceeded => {
f.write_str("user length exceeded maximum of 64 characters")
}
InvalidEmail::InvalidUser => f.write_str("user contains unexpected characters"),
InvalidEmail::DomainLengthExceeded => {
f.write_str("domain length exceeded maximum of 255 characters")
}
InvalidEmail::InvalidDomain => f.write_str("domain contains unexpected characters"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidUrl {
EmptyHost,
IdnaError,
InvalidPort,
InvalidIpv4Address,
InvalidIpv6Address,
InvalidDomainCharacter,
RelativeUrlWithoutBase,
RelativeUrlWithCannotBeABaseBase,
SetHostOnCannotBeABaseUrl,
Overflow,
Other,
}
impl Display for InvalidUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvalidUrl::EmptyHost => f.write_str("empty host"),
InvalidUrl::IdnaError => f.write_str("invalid international domain name"),
InvalidUrl::InvalidPort => f.write_str("invalid port number"),
InvalidUrl::InvalidIpv4Address => f.write_str("invalid IPv4 address"),
InvalidUrl::InvalidIpv6Address => f.write_str("invalid IPv6 address"),
InvalidUrl::InvalidDomainCharacter => f.write_str("invalid domain character"),
InvalidUrl::RelativeUrlWithoutBase => f.write_str("relative URL without a base"),
InvalidUrl::RelativeUrlWithCannotBeABaseBase => {
f.write_str("relative URL with a cannot-be-a-base base")
}
InvalidUrl::SetHostOnCannotBeABaseUrl => {
f.write_str("a cannot-be-a-base URL doesn\u{2019}t have a host to set")
}
InvalidUrl::Overflow => f.write_str("URLs more than 4 GB are not supported"),
InvalidUrl::Other => f.write_str("invalid url"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidCreditCard {
InvalidFormat,
InvalidLength,
InvalidLuhn,
UnknownType,
Other,
}
impl Display for InvalidCreditCard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvalidCreditCard::InvalidFormat => f.write_str("invalid format"),
InvalidCreditCard::InvalidLength => f.write_str("invalid length"),
InvalidCreditCard::InvalidLuhn => f.write_str("invalid luhn"),
InvalidCreditCard::UnknownType => f.write_str("unknown type"),
InvalidCreditCard::Other => f.write_str("invalid credit card"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidPhoneNumber {
Invalid,
NotANumber,
InvalidCountryCode,
TooShortAfterIdd,
TooShortNsn,
TooLong,
MalformedInteger,
Other,
}
impl Display for InvalidPhoneNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvalidPhoneNumber::Invalid => f.write_str("not a valid phone number"),
InvalidPhoneNumber::NotANumber => f.write_str("not a number"),
InvalidPhoneNumber::InvalidCountryCode => f.write_str("invalid country code"),
InvalidPhoneNumber::TooShortAfterIdd => {
f.write_str("the number is too short after IDD")
}
InvalidPhoneNumber::TooShortNsn => {
f.write_str("the number is too short after the country code")
}
InvalidPhoneNumber::TooLong => f.write_str("the number is too long"),
InvalidPhoneNumber::MalformedInteger => {
f.write_str("malformed integer part in phone number")
}
InvalidPhoneNumber::Other => f.write_str("invalid phone number"),
}
}
}
pub trait I18n {
fn length_lower_than(&self, min: usize) -> Cow<'static, str>;
fn length_greater_than(&self, max: usize) -> Cow<'static, str>;
fn range_lower_than(&self, min: &dyn Display) -> Cow<'static, str>;
fn range_greater_than(&self, max: &dyn Display) -> Cow<'static, str>;
fn credit_card_invalid(&self, reason: InvalidCreditCard) -> Cow<'static, str>;
fn pattern_no_match(&self, pattern: &dyn Display) -> Cow<'static, str>;
fn contains_missing(&self, pattern: &dyn Display) -> Cow<'static, str>;
fn url_invalid(&self, reason: InvalidUrl) -> Cow<'static, str>;
fn prefix_missing(&self, pattern: &dyn Display) -> Cow<'static, str>;
fn suffix_missing(&self, pattern: &dyn Display) -> Cow<'static, str>;
fn phone_number_invalid(&self, reason: InvalidPhoneNumber) -> Cow<'static, str>;
fn ip_invalid(&self, kind: IpKind) -> Cow<'static, str>;
fn matches_field_mismatch(&self, field: &dyn Display) -> Cow<'static, str>;
fn email_invalid(&self, reason: InvalidEmail) -> Cow<'static, str>;
fn ascii_invalid(&self) -> Cow<'static, str>;
fn alphanumeric_invalid(&self) -> Cow<'static, str>;
fn required_not_set(&self) -> Cow<'static, str>;
}
impl<T: I18n + ?Sized> I18n for &T {
#[inline]
fn length_lower_than(&self, min: usize) -> Cow<'static, str> {
(**self).length_lower_than(min)
}
#[inline]
fn length_greater_than(&self, max: usize) -> Cow<'static, str> {
(**self).length_greater_than(max)
}
#[inline]
fn range_lower_than(&self, min: &dyn Display) -> Cow<'static, str> {
(**self).range_lower_than(min)
}
#[inline]
fn range_greater_than(&self, max: &dyn Display) -> Cow<'static, str> {
(**self).range_greater_than(max)
}
#[inline]
fn credit_card_invalid(&self, reason: InvalidCreditCard) -> Cow<'static, str> {
(**self).credit_card_invalid(reason)
}
#[inline]
fn pattern_no_match(&self, pattern: &dyn Display) -> Cow<'static, str> {
(**self).pattern_no_match(pattern)
}
#[inline]
fn contains_missing(&self, pattern: &dyn Display) -> Cow<'static, str> {
(**self).contains_missing(pattern)
}
#[inline]
fn url_invalid(&self, reason: InvalidUrl) -> Cow<'static, str> {
(**self).url_invalid(reason)
}
#[inline]
fn prefix_missing(&self, pattern: &dyn Display) -> Cow<'static, str> {
(**self).prefix_missing(pattern)
}
#[inline]
fn suffix_missing(&self, pattern: &dyn Display) -> Cow<'static, str> {
(**self).suffix_missing(pattern)
}
#[inline]
fn phone_number_invalid(&self, reason: InvalidPhoneNumber) -> Cow<'static, str> {
(**self).phone_number_invalid(reason)
}
#[inline]
fn ip_invalid(&self, kind: IpKind) -> Cow<'static, str> {
(**self).ip_invalid(kind)
}
#[inline]
fn matches_field_mismatch(&self, field: &dyn Display) -> Cow<'static, str> {
(**self).matches_field_mismatch(field)
}
#[inline]
fn email_invalid(&self, reason: InvalidEmail) -> Cow<'static, str> {
(**self).email_invalid(reason)
}
#[inline]
fn ascii_invalid(&self) -> Cow<'static, str> {
(**self).ascii_invalid()
}
#[inline]
fn alphanumeric_invalid(&self) -> Cow<'static, str> {
(**self).alphanumeric_invalid()
}
#[inline]
fn required_not_set(&self) -> Cow<'static, str> {
(**self).required_not_set()
}
}
pub struct DefaultI18n;
impl I18n for DefaultI18n {
fn length_lower_than(&self, min: usize) -> Cow<'static, str> {
format!("length is lower than {min}").into()
}
fn length_greater_than(&self, max: usize) -> Cow<'static, str> {
format!("length is greater than {max}").into()
}
fn range_lower_than(&self, min: &dyn Display) -> Cow<'static, str> {
format!("lower than {min}").into()
}
fn range_greater_than(&self, max: &dyn Display) -> Cow<'static, str> {
format!("greater than {max}").into()
}
fn credit_card_invalid(&self, reason: InvalidCreditCard) -> Cow<'static, str> {
format!("not a valid credit card number: {reason}").into()
}
fn pattern_no_match(&self, pattern: &dyn Display) -> Cow<'static, str> {
format!("does not match pattern /{pattern}/").into()
}
fn contains_missing(&self, pattern: &dyn Display) -> Cow<'static, str> {
format!("does not contain \"{pattern}\"").into()
}
fn url_invalid(&self, reason: InvalidUrl) -> Cow<'static, str> {
format!("not a valid url: {reason}").into()
}
fn prefix_missing(&self, pattern: &dyn Display) -> Cow<'static, str> {
format!("value does not begin with \"{pattern}\"").into()
}
fn suffix_missing(&self, pattern: &dyn Display) -> Cow<'static, str> {
format!("does not end with \"{pattern}\"").into()
}
fn phone_number_invalid(&self, reason: InvalidPhoneNumber) -> Cow<'static, str> {
match reason {
InvalidPhoneNumber::Invalid => Cow::Borrowed("not a valid phone number"),
_ => format!("not a valid phone number: {reason}").into(),
}
}
fn ip_invalid(&self, kind: IpKind) -> Cow<'static, str> {
format!("not a valid {kind} address").into()
}
fn matches_field_mismatch(&self, field: &dyn Display) -> Cow<'static, str> {
format!("does not match {field} field").into()
}
fn email_invalid(&self, reason: InvalidEmail) -> Cow<'static, str> {
format!("not a valid email: {reason}").into()
}
fn ascii_invalid(&self) -> Cow<'static, str> {
Cow::Borrowed("not ascii")
}
fn alphanumeric_invalid(&self) -> Cow<'static, str> {
Cow::Borrowed("not alphanumeric")
}
fn required_not_set(&self) -> Cow<'static, str> {
Cow::Borrowed("not set")
}
}
thread_local! {
pub(crate) static I18N: Cell<Option<NonNull<dyn I18n + 'static>>> =
const { Cell::new(None) };
}
macro_rules! i18n {
($handler:ident $(, $($args:expr),*)?) => {
$crate::i18n::I18N.with(|slot| match slot.get() {
None => <$crate::i18n::DefaultI18n as $crate::i18n::I18n>::$handler(
&$crate::i18n::DefaultI18n,
$($($args),*)?
),
Some(p) => unsafe { p.as_ref() }.$handler($($($args),*)?),
})
};
}
pub fn with_i18n<'a, R>(mut handler: impl I18n + 'a, f: impl FnOnce() -> R) -> R {
let handler: *mut (dyn I18n + 'a) = &raw mut handler;
let ptr: NonNull<dyn I18n + 'static> = unsafe {
NonNull::new_unchecked(
transmute::<*mut (dyn I18n + 'a), *mut (dyn I18n + 'static)>(handler),
)
};
struct Reset {
prev: Option<NonNull<dyn I18n + 'static>>,
}
impl Drop for Reset {
fn drop(&mut self) {
I18N.with(|c| c.set(self.prev));
}
}
let _reset = Reset {
prev: I18N.with(|c| c.replace(Some(ptr))),
};
f()
}