use serde_json::Value;
use crate::error::{value_type_name, IssueCode, StringValidation, VldError};
use crate::schema::VldSchema;
fn is_valid_email(s: &str) -> bool {
let at = match s.find('@') {
Some(pos) if pos > 0 => pos,
_ => return false,
};
let local = &s[..at];
let domain = &s[at + 1..];
if local.is_empty() || domain.is_empty() {
return false;
}
for ch in local.chars() {
if ch.is_ascii_alphanumeric() || "!#$%&'*+/=?^_`{|}~.-".contains(ch) {
continue;
}
return false;
}
is_valid_hostname(domain)
}
fn is_valid_uuid(s: &str) -> bool {
if s.len() != 36 {
return false;
}
let bytes = s.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
match i {
8 | 13 | 18 | 23 => {
if b != b'-' {
return false;
}
}
_ => {
if !b.is_ascii_hexdigit() {
return false;
}
}
}
}
true
}
#[cfg(feature = "string-advanced")]
fn is_valid_uuid_version(s: &str, version: usize) -> bool {
let u = match uuid::Uuid::parse_str(s) {
Ok(v) => v,
Err(_) => return false,
};
u.get_version_num() == version
}
fn is_valid_url(s: &str) -> bool {
let rest = if let Some(r) = s.strip_prefix("https://") {
r
} else if let Some(r) = s.strip_prefix("http://") {
r
} else {
return false;
};
if rest.is_empty() {
return false;
}
!rest.contains(char::is_whitespace)
}
#[cfg(feature = "string-advanced")]
fn is_valid_url_strict(s: &str) -> bool {
let parsed = match url::Url::parse(s) {
Ok(v) => v,
Err(_) => return false,
};
if parsed.scheme() != "http" && parsed.scheme() != "https" {
return false;
}
let host = match parsed.host_str() {
Some(h) => h,
None => return false,
};
if host.eq_ignore_ascii_case("localhost") {
return false;
}
if let Ok(ip) = host.parse::<std::net::IpAddr>() {
match ip {
std::net::IpAddr::V4(v4) => {
if v4.is_private() || v4.is_loopback() || v4.is_link_local() {
return false;
}
}
std::net::IpAddr::V6(v6) => {
let seg0 = v6.segments()[0];
let is_link_local = (seg0 & 0xffc0) == 0xfe80; let is_unique_local = (seg0 & 0xfe00) == 0xfc00; if v6.is_loopback() || is_link_local || is_unique_local {
return false;
}
}
}
}
true
}
#[cfg(feature = "string-advanced")]
fn is_valid_uri(s: &str) -> bool {
url::Url::parse(s).is_ok()
}
fn is_valid_ipv4(s: &str) -> bool {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 4 {
return false;
}
for part in parts {
if part.is_empty() || part.len() > 3 {
return false;
}
if part.len() > 1 && part.starts_with('0') {
return false;
}
match part.parse::<u16>() {
Ok(n) if n <= 255 => {}
_ => return false,
}
}
true
}
fn is_valid_ipv6(s: &str) -> bool {
if s == "::" {
return true;
}
let (left, right) = if let Some(pos) = s.find("::") {
(&s[..pos], &s[pos + 2..])
} else {
(s, "")
};
let has_double_colon = s.contains("::");
let left_groups: Vec<&str> = if left.is_empty() {
vec![]
} else {
left.split(':').collect()
};
let right_groups: Vec<&str> = if right.is_empty() {
vec![]
} else {
right.split(':').collect()
};
let total = left_groups.len() + right_groups.len();
if has_double_colon {
if total > 7 {
return false;
}
} else if total != 8 {
return false;
}
for group in left_groups.iter().chain(right_groups.iter()) {
if group.is_empty() || group.len() > 4 {
return false;
}
if !group.chars().all(|c| c.is_ascii_hexdigit()) {
return false;
}
}
true
}
fn is_valid_ip(s: &str) -> bool {
is_valid_ipv4(s) || is_valid_ipv6(s)
}
fn is_valid_cidr(s: &str) -> bool {
let (ip, prefix) = match s.split_once('/') {
Some(v) => v,
None => return false,
};
let prefix = match prefix.parse::<u8>() {
Ok(v) => v,
Err(_) => return false,
};
if is_valid_ipv4(ip) {
prefix <= 32
} else if is_valid_ipv6(ip) {
prefix <= 128
} else {
false
}
}
fn is_valid_mac(s: &str) -> bool {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 6 {
return false;
}
parts
.iter()
.all(|p| p.len() == 2 && p.bytes().all(|b| b.is_ascii_hexdigit()))
}
fn is_valid_hex(s: &str) -> bool {
!s.is_empty() && s.bytes().all(|b| b.is_ascii_hexdigit())
}
fn is_valid_credit_card(s: &str) -> bool {
let digits: Vec<u32> = s
.chars()
.filter(|c| c.is_ascii_digit())
.filter_map(|c| c.to_digit(10))
.collect();
if digits.len() < 12 || digits.len() > 19 {
return false;
}
let mut sum = 0;
let mut double = false;
for d in digits.iter().rev() {
let mut v = *d;
if double {
v *= 2;
if v > 9 {
v -= 9;
}
}
sum += v;
double = !double;
}
sum % 10 == 0
}
fn is_valid_phone(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
let first = chars.next().unwrap_or_default();
if first != '+' {
return false;
}
let digits: String = chars.filter(|c| c.is_ascii_digit()).collect();
(8..=15).contains(&digits.len())
}
#[cfg(feature = "string-advanced")]
fn is_valid_phone_e164_strict(s: &str) -> bool {
let num = match phonenumber::parse(None, s) {
Ok(n) => n,
Err(_) => return false,
};
phonenumber::is_valid(&num)
}
fn is_valid_slug(s: &str) -> bool {
if s.is_empty() || s.starts_with('-') || s.ends_with('-') {
return false;
}
s.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
}
fn is_valid_color(s: &str) -> bool {
if let Some(hex) = s.strip_prefix('#') {
return (hex.len() == 6 || hex.len() == 8) && hex.bytes().all(|b| b.is_ascii_hexdigit());
}
let lower = s.to_ascii_lowercase();
if lower.starts_with("rgb(") && lower.ends_with(')') {
let inner = &lower[4..lower.len() - 1];
let parts: Vec<&str> = inner.split(',').map(|p| p.trim()).collect();
if parts.len() != 3 {
return false;
}
return parts.iter().all(|p| p.parse::<u8>().is_ok());
}
if lower.starts_with("hsl(") && lower.ends_with(')') {
let inner = &lower[4..lower.len() - 1];
let parts: Vec<&str> = inner.split(',').map(|p| p.trim()).collect();
if parts.len() != 3 {
return false;
}
let h_ok = parts[0].parse::<u16>().map(|h| h <= 360).unwrap_or(false);
let s_ok = parts[1]
.strip_suffix('%')
.and_then(|v| v.parse::<u8>().ok())
.map(|v| v <= 100)
.unwrap_or(false);
let l_ok = parts[2]
.strip_suffix('%')
.and_then(|v| v.parse::<u8>().ok())
.map(|v| v <= 100)
.unwrap_or(false);
return h_ok && s_ok && l_ok;
}
false
}
fn is_valid_currency_code(s: &str) -> bool {
s.len() == 3 && s.bytes().all(|b| b.is_ascii_uppercase())
}
fn is_valid_country_code(s: &str) -> bool {
s.len() == 2 && s.bytes().all(|b| b.is_ascii_uppercase())
}
fn is_valid_locale(s: &str) -> bool {
let bytes = s.as_bytes();
if bytes.len() == 2 {
return bytes[0].is_ascii_lowercase() && bytes[1].is_ascii_lowercase();
}
if bytes.len() == 5 && bytes[2] == b'-' {
return bytes[0].is_ascii_lowercase()
&& bytes[1].is_ascii_lowercase()
&& bytes[3].is_ascii_uppercase()
&& bytes[4].is_ascii_uppercase();
}
false
}
fn is_valid_cron(s: &str) -> bool {
let fields: Vec<&str> = s.split_whitespace().collect();
if fields.len() != 5 && fields.len() != 6 {
return false;
}
fields.iter().all(|f| {
!f.is_empty()
&& f.bytes().all(|b| {
b.is_ascii_digit() || b == b'*' || b == b'/' || b == b',' || b == b'-' || b == b'?'
})
})
}
fn is_valid_semver(s: &str) -> bool {
let s = s.trim();
if s.is_empty() {
return false;
}
let s = s.strip_prefix('v').unwrap_or(s);
let main = s.split(['-', '+']).next().unwrap_or("");
let parts: Vec<&str> = main.split('.').collect();
if parts.len() != 3 {
return false;
}
parts
.iter()
.all(|p| !p.is_empty() && p.bytes().all(|b| b.is_ascii_digit()))
}
#[cfg(feature = "string-advanced")]
fn is_valid_semver_full(s: &str) -> bool {
semver::Version::parse(s).is_ok()
}
fn is_valid_base64_url(s: &str) -> bool {
!s.is_empty()
&& s.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
}
fn is_valid_jwt(s: &str) -> bool {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return false;
}
parts.iter().all(|p| is_valid_base64_url(p))
}
fn is_valid_base64(s: &str) -> bool {
if s.is_empty() || s.len() % 4 != 0 {
return false;
}
let mut pad_started = false;
for &b in s.as_bytes() {
if pad_started {
if b != b'=' {
return false;
}
} else if b == b'=' {
pad_started = true;
} else if !(b.is_ascii_alphanumeric() || b == b'+' || b == b'/') {
return false;
}
}
let pad_count = s.bytes().rev().take_while(|&b| b == b'=').count();
pad_count <= 2
}
fn parse_digits(s: &str, n: usize) -> Option<(u32, &str)> {
if s.len() < n {
return None;
}
let (digits, rest) = s.split_at(n);
if !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
digits.parse::<u32>().ok().map(|v| (v, rest))
}
fn is_valid_iso_date(s: &str) -> bool {
if s.len() != 10 {
return false;
}
let (year, rest) = match parse_digits(s, 4) {
Some(v) => v,
None => return false,
};
let rest = rest.strip_prefix('-').unwrap_or("");
let (month, rest) = match parse_digits(rest, 2) {
Some(v) => v,
None => return false,
};
let rest = rest.strip_prefix('-').unwrap_or("");
let (day, rest) = match parse_digits(rest, 2) {
Some(v) => v,
None => return false,
};
if !rest.is_empty() {
return false;
}
let _ = year; (1..=12).contains(&month) && (1..=31).contains(&day)
}
fn is_valid_iso_time(s: &str) -> bool {
if s.len() < 5 {
return false;
}
let (hour, rest) = match parse_digits(s, 2) {
Some(v) => v,
None => return false,
};
let rest = match rest.strip_prefix(':') {
Some(r) => r,
None => return false,
};
let (min, rest) = match parse_digits(rest, 2) {
Some(v) => v,
None => return false,
};
if !(0..=23).contains(&hour) || !(0..=59).contains(&min) {
return false;
}
if rest.is_empty() {
return true;
}
let rest = match rest.strip_prefix(':') {
Some(r) => r,
None => return false,
};
let (sec, rest) = match parse_digits(rest, 2) {
Some(v) => v,
None => return false,
};
if !(0..=59).contains(&sec) {
return false;
}
if rest.is_empty() {
return true;
}
let rest = match rest.strip_prefix('.') {
Some(r) => r,
None => return false,
};
!rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit())
}
fn is_valid_iso_datetime(s: &str) -> bool {
let t_pos = match s.find('T') {
Some(p) => p,
None => return false,
};
let date_part = &s[..t_pos];
let after_t = &s[t_pos + 1..];
if !is_valid_iso_date(date_part) {
return false;
}
let (time_part, _tz_part) = if let Some(pos) = after_t.rfind('Z') {
(&after_t[..pos], &after_t[pos..])
} else if let Some(pos) = after_t.rfind('+') {
(&after_t[..pos], &after_t[pos..])
} else if let Some(pos) = after_t[1..].rfind('-') {
let actual = pos + 1;
(&after_t[..actual], &after_t[actual..])
} else {
(after_t, "")
};
is_valid_iso_time(time_part)
}
fn is_valid_hostname(s: &str) -> bool {
if s.is_empty() || s.len() > 253 {
return false;
}
for label in s.split('.') {
if label.is_empty() || label.len() > 63 {
return false;
}
if label.starts_with('-') || label.ends_with('-') {
return false;
}
if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
return false;
}
}
true
}
fn is_valid_cuid2(s: &str) -> bool {
if s.is_empty() {
return false;
}
if !s.as_bytes()[0].is_ascii_lowercase() {
return false;
}
s.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
}
fn is_valid_ulid(s: &str) -> bool {
if s.len() != 26 {
return false;
}
s.chars().all(|c| {
let c = c.to_ascii_uppercase();
matches!(c, '0'..='9' | 'A'..='H' | 'J' | 'K' | 'M' | 'N' | 'P'..='T' | 'V'..='Z')
})
}
fn is_valid_nanoid(s: &str) -> bool {
if s.is_empty() {
return false;
}
s.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}
fn is_valid_emoji(s: &str) -> bool {
s.chars().any(|c| {
let cp = c as u32;
(0x1F600..=0x1F64F).contains(&cp)
|| (0x1F300..=0x1F5FF).contains(&cp)
|| (0x1F680..=0x1F6FF).contains(&cp)
|| (0x1F1E0..=0x1F1FF).contains(&cp)
|| (0x2702..=0x27B0).contains(&cp)
|| (0x2600..=0x26FF).contains(&cp)
|| (0xFE00..=0xFE0F).contains(&cp)
|| (0x1F900..=0x1F9FF).contains(&cp)
|| (0x1FA00..=0x1FA6F).contains(&cp)
|| (0x1FA70..=0x1FAFF).contains(&cp)
|| (0x231A..=0x231B).contains(&cp)
|| (0x23E9..=0x23F3).contains(&cp)
|| (0x23F8..=0x23FA).contains(&cp)
|| cp == 0x200D
|| cp == 0x2B50
|| cp == 0x2764
})
}
#[derive(Clone)]
enum StringCheck {
Min(usize, String),
Max(usize, String),
Len(usize, String),
Email(String),
Url(String),
#[cfg(feature = "string-advanced")]
UrlStrict(String),
#[cfg(feature = "string-advanced")]
Uri(String),
Uuid(String),
#[cfg(feature = "string-advanced")]
UuidV1(String),
#[cfg(feature = "string-advanced")]
UuidV4(String),
#[cfg(feature = "string-advanced")]
UuidV7(String),
Ip(String),
Slug(String),
Color(String),
Currency(String),
CountryCode(String),
Locale(String),
Cron(String),
#[cfg(feature = "regex")]
Regex(regex_lite::Regex, String),
StartsWith(String, String),
EndsWith(String, String),
Contains(String, String),
NonEmpty(String),
Ipv4(String),
Ipv6(String),
Cidr(String),
Mac(String),
Hex(String),
CreditCard(String),
Phone(String),
#[cfg(feature = "string-advanced")]
PhoneE164(String),
Semver(String),
#[cfg(feature = "string-advanced")]
SemverFull(String),
Jwt(String),
Ascii(String),
Alpha(String),
Alphanumeric(String),
Lowercase(String),
Uppercase(String),
Base64(String),
IsoDate(String),
IsoDatetime(String),
IsoTime(String),
Hostname(String),
Cuid2(String),
Ulid(String),
Nanoid(String),
Emoji(String),
}
impl StringCheck {
fn key(&self) -> &str {
match self {
StringCheck::Min(..) => "too_small",
StringCheck::Max(..) => "too_big",
StringCheck::Len(..) => "invalid_length",
StringCheck::Email(..) => "invalid_email",
StringCheck::Url(..) => "invalid_url",
#[cfg(feature = "string-advanced")]
StringCheck::UrlStrict(..) => "invalid_url_strict",
#[cfg(feature = "string-advanced")]
StringCheck::Uri(..) => "invalid_uri",
StringCheck::Uuid(..) => "invalid_uuid",
#[cfg(feature = "string-advanced")]
StringCheck::UuidV1(..) => "invalid_uuid_v1",
#[cfg(feature = "string-advanced")]
StringCheck::UuidV4(..) => "invalid_uuid_v4",
#[cfg(feature = "string-advanced")]
StringCheck::UuidV7(..) => "invalid_uuid_v7",
StringCheck::Ip(..) => "invalid_ip",
StringCheck::Slug(..) => "invalid_slug",
StringCheck::Color(..) => "invalid_color",
StringCheck::Currency(..) => "invalid_currency",
StringCheck::CountryCode(..) => "invalid_country_code",
StringCheck::Locale(..) => "invalid_locale",
StringCheck::Cron(..) => "invalid_cron",
#[cfg(feature = "regex")]
StringCheck::Regex(..) => "invalid_regex",
StringCheck::StartsWith(..) => "invalid_starts_with",
StringCheck::EndsWith(..) => "invalid_ends_with",
StringCheck::Contains(..) => "invalid_contains",
StringCheck::NonEmpty(..) => "non_empty",
StringCheck::Ipv4(..) => "invalid_ipv4",
StringCheck::Ipv6(..) => "invalid_ipv6",
StringCheck::Cidr(..) => "invalid_cidr",
StringCheck::Mac(..) => "invalid_mac",
StringCheck::Hex(..) => "invalid_hex",
StringCheck::CreditCard(..) => "invalid_credit_card",
StringCheck::Phone(..) => "invalid_phone",
#[cfg(feature = "string-advanced")]
StringCheck::PhoneE164(..) => "invalid_phone_e164",
StringCheck::Semver(..) => "invalid_semver",
#[cfg(feature = "string-advanced")]
StringCheck::SemverFull(..) => "invalid_semver_full",
StringCheck::Jwt(..) => "invalid_jwt",
StringCheck::Ascii(..) => "invalid_ascii",
StringCheck::Alpha(..) => "invalid_alpha",
StringCheck::Alphanumeric(..) => "invalid_alphanumeric",
StringCheck::Lowercase(..) => "invalid_lowercase",
StringCheck::Uppercase(..) => "invalid_uppercase",
StringCheck::Base64(..) => "invalid_base64",
StringCheck::IsoDate(..) => "invalid_iso_date",
StringCheck::IsoDatetime(..) => "invalid_iso_datetime",
StringCheck::IsoTime(..) => "invalid_iso_time",
StringCheck::Hostname(..) => "invalid_hostname",
StringCheck::Cuid2(..) => "invalid_cuid2",
StringCheck::Ulid(..) => "invalid_ulid",
StringCheck::Nanoid(..) => "invalid_nanoid",
StringCheck::Emoji(..) => "invalid_emoji",
}
}
fn set_message(&mut self, msg: String) {
match self {
#[cfg(feature = "string-advanced")]
StringCheck::UrlStrict(ref mut m)
| StringCheck::Uri(ref mut m)
| StringCheck::UuidV1(ref mut m)
| StringCheck::UuidV4(ref mut m)
| StringCheck::UuidV7(ref mut m)
| StringCheck::PhoneE164(ref mut m)
| StringCheck::SemverFull(ref mut m) => *m = msg,
StringCheck::Min(_, ref mut m)
| StringCheck::Max(_, ref mut m)
| StringCheck::Len(_, ref mut m)
| StringCheck::Email(ref mut m)
| StringCheck::Url(ref mut m)
| StringCheck::Uuid(ref mut m)
| StringCheck::Ip(ref mut m)
| StringCheck::Slug(ref mut m)
| StringCheck::Color(ref mut m)
| StringCheck::Currency(ref mut m)
| StringCheck::CountryCode(ref mut m)
| StringCheck::Locale(ref mut m)
| StringCheck::Cron(ref mut m)
| StringCheck::NonEmpty(ref mut m)
| StringCheck::Ipv4(ref mut m)
| StringCheck::Ipv6(ref mut m)
| StringCheck::Cidr(ref mut m)
| StringCheck::Mac(ref mut m)
| StringCheck::Hex(ref mut m)
| StringCheck::CreditCard(ref mut m)
| StringCheck::Phone(ref mut m)
| StringCheck::Semver(ref mut m)
| StringCheck::Jwt(ref mut m)
| StringCheck::Ascii(ref mut m)
| StringCheck::Alpha(ref mut m)
| StringCheck::Alphanumeric(ref mut m)
| StringCheck::Lowercase(ref mut m)
| StringCheck::Uppercase(ref mut m)
| StringCheck::Base64(ref mut m)
| StringCheck::IsoDate(ref mut m)
| StringCheck::IsoDatetime(ref mut m)
| StringCheck::IsoTime(ref mut m)
| StringCheck::Hostname(ref mut m)
| StringCheck::Cuid2(ref mut m)
| StringCheck::Ulid(ref mut m)
| StringCheck::Nanoid(ref mut m)
| StringCheck::Emoji(ref mut m) => *m = msg,
StringCheck::StartsWith(_, ref mut m)
| StringCheck::EndsWith(_, ref mut m)
| StringCheck::Contains(_, ref mut m) => *m = msg,
#[cfg(feature = "regex")]
StringCheck::Regex(_, ref mut m) => *m = msg,
}
}
}
#[derive(Clone)]
enum StringTransform {
Trim,
ToLowerCase,
ToUpperCase,
}
#[derive(Clone)]
pub struct ZString {
checks: Vec<StringCheck>,
transforms: Vec<StringTransform>,
coerce: bool,
custom_type_error: Option<String>,
}
impl ZString {
pub fn new() -> Self {
Self {
checks: vec![],
transforms: vec![],
coerce: false,
custom_type_error: None,
}
}
pub fn type_error(mut self, msg: impl Into<String>) -> Self {
self.custom_type_error = Some(msg.into());
self
}
pub fn with_messages<F>(mut self, f: F) -> Self
where
F: Fn(&str) -> Option<String>,
{
for check in &mut self.checks {
if let Some(msg) = f(check.key()) {
check.set_message(msg);
}
}
self
}
pub fn min(self, len: usize) -> Self {
self.min_msg(len, format!("String must be at least {} characters", len))
}
pub fn min_msg(mut self, len: usize, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Min(len, msg.into()));
self
}
pub fn max(self, len: usize) -> Self {
self.max_msg(len, format!("String must be at most {} characters", len))
}
pub fn max_msg(mut self, len: usize, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Max(len, msg.into()));
self
}
pub fn len(self, len: usize) -> Self {
self.len_msg(len, format!("String must be exactly {} characters", len))
}
pub fn len_msg(mut self, len: usize, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Len(len, msg.into()));
self
}
pub fn email(self) -> Self {
self.email_msg("Invalid email address")
}
pub fn email_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Email(msg.into()));
self
}
pub fn url(self) -> Self {
self.url_msg("Invalid URL")
}
pub fn url_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Url(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn url_strict(self) -> Self {
self.url_strict_msg("Invalid strict URL")
}
#[cfg(feature = "string-advanced")]
pub fn url_strict_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::UrlStrict(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn uri(self) -> Self {
self.uri_msg("Invalid URI")
}
#[cfg(feature = "string-advanced")]
pub fn uri_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Uri(msg.into()));
self
}
pub fn uuid(self) -> Self {
self.uuid_msg("Invalid UUID")
}
pub fn uuid_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Uuid(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn uuid_v1(self) -> Self {
self.uuid_v1_msg("Invalid UUID v1")
}
#[cfg(feature = "string-advanced")]
pub fn uuid_v1_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::UuidV1(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn uuid_v4(self) -> Self {
self.uuid_v4_msg("Invalid UUID v4")
}
#[cfg(feature = "string-advanced")]
pub fn uuid_v4_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::UuidV4(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn uuid_v7(self) -> Self {
self.uuid_v7_msg("Invalid UUID v7")
}
#[cfg(feature = "string-advanced")]
pub fn uuid_v7_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::UuidV7(msg.into()));
self
}
pub fn ip(self) -> Self {
self.ip_msg("Invalid IP address")
}
pub fn ip_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Ip(msg.into()));
self
}
#[cfg(feature = "regex")]
pub fn regex(self, re: regex_lite::Regex) -> Self {
self.regex_msg(re, "String does not match pattern")
}
#[cfg(feature = "regex")]
pub fn regex_msg(mut self, re: regex_lite::Regex, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Regex(re, msg.into()));
self
}
pub fn starts_with(self, prefix: impl Into<String>) -> Self {
let p = prefix.into();
let msg = format!("String must start with \"{}\"", p);
self.starts_with_msg(p, msg)
}
pub fn starts_with_msg(mut self, prefix: impl Into<String>, msg: impl Into<String>) -> Self {
self.checks
.push(StringCheck::StartsWith(prefix.into(), msg.into()));
self
}
pub fn ends_with(self, suffix: impl Into<String>) -> Self {
let s = suffix.into();
let msg = format!("String must end with \"{}\"", s);
self.ends_with_msg(s, msg)
}
pub fn ends_with_msg(mut self, suffix: impl Into<String>, msg: impl Into<String>) -> Self {
self.checks
.push(StringCheck::EndsWith(suffix.into(), msg.into()));
self
}
pub fn contains(self, sub: impl Into<String>) -> Self {
let s = sub.into();
let msg = format!("String must contain \"{}\"", s);
self.contains_msg(s, msg)
}
pub fn contains_msg(mut self, sub: impl Into<String>, msg: impl Into<String>) -> Self {
self.checks
.push(StringCheck::Contains(sub.into(), msg.into()));
self
}
pub fn non_empty(self) -> Self {
self.non_empty_msg("String must not be empty")
}
pub fn non_empty_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::NonEmpty(msg.into()));
self
}
pub fn trim(mut self) -> Self {
self.transforms.push(StringTransform::Trim);
self
}
pub fn to_lowercase(mut self) -> Self {
self.transforms.push(StringTransform::ToLowerCase);
self
}
pub fn to_uppercase(mut self) -> Self {
self.transforms.push(StringTransform::ToUpperCase);
self
}
pub fn coerce(mut self) -> Self {
self.coerce = true;
self
}
pub fn ipv4(self) -> Self {
self.ipv4_msg("Invalid IPv4 address")
}
pub fn ipv4_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Ipv4(msg.into()));
self
}
pub fn ipv6(self) -> Self {
self.ipv6_msg("Invalid IPv6 address")
}
pub fn ipv6_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Ipv6(msg.into()));
self
}
pub fn cidr(self) -> Self {
self.cidr_msg("Invalid CIDR")
}
pub fn cidr_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Cidr(msg.into()));
self
}
pub fn mac(self) -> Self {
self.mac_msg("Invalid MAC address")
}
pub fn mac_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Mac(msg.into()));
self
}
pub fn hex(self) -> Self {
self.hex_msg("Invalid hexadecimal string")
}
pub fn hex_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Hex(msg.into()));
self
}
pub fn credit_card(self) -> Self {
self.credit_card_msg("Invalid credit card number")
}
pub fn credit_card_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::CreditCard(msg.into()));
self
}
pub fn phone(self) -> Self {
self.phone_msg("Invalid phone number")
}
pub fn phone_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Phone(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn phone_e164_strict(self) -> Self {
self.phone_e164_strict_msg("Invalid E.164 phone number")
}
#[cfg(feature = "string-advanced")]
pub fn phone_e164_strict_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::PhoneE164(msg.into()));
self
}
pub fn semver(self) -> Self {
self.semver_msg("Invalid semver")
}
pub fn semver_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Semver(msg.into()));
self
}
#[cfg(feature = "string-advanced")]
pub fn semver_full(self) -> Self {
self.semver_full_msg("Invalid semantic version")
}
#[cfg(feature = "string-advanced")]
pub fn semver_full_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::SemverFull(msg.into()));
self
}
pub fn jwt(self) -> Self {
self.jwt_msg("Invalid JWT")
}
pub fn jwt_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Jwt(msg.into()));
self
}
pub fn ascii(self) -> Self {
self.ascii_msg("String must contain only ASCII characters")
}
pub fn ascii_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Ascii(msg.into()));
self
}
pub fn alpha(self) -> Self {
self.alpha_msg("String must contain only alphabetic characters")
}
pub fn alpha_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Alpha(msg.into()));
self
}
pub fn alphanumeric(self) -> Self {
self.alphanumeric_msg("String must contain only alphanumeric characters")
}
pub fn alphanumeric_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Alphanumeric(msg.into()));
self
}
pub fn lowercase(self) -> Self {
self.lowercase_msg("String must be lowercase")
}
pub fn lowercase_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Lowercase(msg.into()));
self
}
pub fn uppercase(self) -> Self {
self.uppercase_msg("String must be uppercase")
}
pub fn uppercase_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Uppercase(msg.into()));
self
}
pub fn base64(self) -> Self {
self.base64_msg("Invalid Base64 string")
}
pub fn base64_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Base64(msg.into()));
self
}
pub fn iso_date(self) -> Self {
self.iso_date_msg("Invalid ISO date (expected YYYY-MM-DD)")
}
pub fn iso_date_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::IsoDate(msg.into()));
self
}
pub fn iso_datetime(self) -> Self {
self.iso_datetime_msg("Invalid ISO datetime")
}
pub fn iso_datetime_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::IsoDatetime(msg.into()));
self
}
pub fn iso_time(self) -> Self {
self.iso_time_msg("Invalid ISO time")
}
pub fn iso_time_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::IsoTime(msg.into()));
self
}
pub fn hostname(self) -> Self {
self.hostname_msg("Invalid hostname")
}
pub fn hostname_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Hostname(msg.into()));
self
}
pub fn cuid2(self) -> Self {
self.cuid2_msg("Invalid CUID2")
}
pub fn cuid2_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Cuid2(msg.into()));
self
}
pub fn ulid(self) -> Self {
self.ulid_msg("Invalid ULID")
}
pub fn ulid_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Ulid(msg.into()));
self
}
pub fn nanoid(self) -> Self {
self.nanoid_msg("Invalid Nano ID")
}
pub fn nanoid_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Nanoid(msg.into()));
self
}
pub fn emoji(self) -> Self {
self.emoji_msg("String must contain an emoji")
}
pub fn emoji_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Emoji(msg.into()));
self
}
pub fn slug(self) -> Self {
self.slug_msg("Invalid slug")
}
pub fn slug_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Slug(msg.into()));
self
}
pub fn color(self) -> Self {
self.color_msg("Invalid color")
}
pub fn color_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Color(msg.into()));
self
}
pub fn currency_code(self) -> Self {
self.currency_code_msg("Invalid currency code")
}
pub fn currency_code_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Currency(msg.into()));
self
}
pub fn country_code(self) -> Self {
self.country_code_msg("Invalid country code")
}
pub fn country_code_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::CountryCode(msg.into()));
self
}
pub fn locale(self) -> Self {
self.locale_msg("Invalid locale")
}
pub fn locale_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Locale(msg.into()));
self
}
pub fn cron(self) -> Self {
self.cron_msg("Invalid cron expression")
}
pub fn cron_msg(mut self, msg: impl Into<String>) -> Self {
self.checks.push(StringCheck::Cron(msg.into()));
self
}
#[cfg(feature = "openapi")]
pub fn to_json_schema(&self) -> serde_json::Value {
let mut schema = serde_json::json!({"type": "string"});
for check in &self.checks {
match check {
StringCheck::Min(n, _) => {
schema["minLength"] = serde_json::json!(*n);
}
StringCheck::Max(n, _) => {
schema["maxLength"] = serde_json::json!(*n);
}
StringCheck::Len(n, _) => {
schema["minLength"] = serde_json::json!(*n);
schema["maxLength"] = serde_json::json!(*n);
}
StringCheck::Email(_) => {
schema["format"] = serde_json::json!("email");
}
StringCheck::Url(_) => {
schema["format"] = serde_json::json!("uri");
}
#[cfg(feature = "string-advanced")]
StringCheck::UrlStrict(_) => {
schema["format"] = serde_json::json!("url-strict");
}
#[cfg(feature = "string-advanced")]
StringCheck::Uri(_) => {
schema["format"] = serde_json::json!("uri");
}
StringCheck::Uuid(_) => {
schema["format"] = serde_json::json!("uuid");
}
#[cfg(feature = "string-advanced")]
StringCheck::UuidV1(_) => {
schema["format"] = serde_json::json!("uuid-v1");
}
#[cfg(feature = "string-advanced")]
StringCheck::UuidV4(_) => {
schema["format"] = serde_json::json!("uuid-v4");
}
#[cfg(feature = "string-advanced")]
StringCheck::UuidV7(_) => {
schema["format"] = serde_json::json!("uuid-v7");
}
StringCheck::Ip(_) => {
schema["format"] = serde_json::json!("ip");
}
StringCheck::Slug(_) => {
schema["format"] = serde_json::json!("slug");
}
StringCheck::Color(_) => {
schema["format"] = serde_json::json!("color");
}
StringCheck::Currency(_) => {
schema["format"] = serde_json::json!("currency-code");
}
StringCheck::CountryCode(_) => {
schema["format"] = serde_json::json!("country-code");
}
StringCheck::Locale(_) => {
schema["format"] = serde_json::json!("locale");
}
StringCheck::Cron(_) => {
schema["format"] = serde_json::json!("cron");
}
StringCheck::Ipv4(_) => {
schema["format"] = serde_json::json!("ipv4");
}
StringCheck::Ipv6(_) => {
schema["format"] = serde_json::json!("ipv6");
}
StringCheck::Cidr(_) => {
schema["format"] = serde_json::json!("cidr");
}
StringCheck::Mac(_) => {
schema["format"] = serde_json::json!("mac");
}
StringCheck::Hex(_) => {
schema["format"] = serde_json::json!("hex");
}
StringCheck::CreditCard(_) => {
schema["format"] = serde_json::json!("credit-card");
}
StringCheck::Phone(_) => {
schema["format"] = serde_json::json!("phone");
}
#[cfg(feature = "string-advanced")]
StringCheck::PhoneE164(_) => {
schema["format"] = serde_json::json!("phone-e164");
}
StringCheck::Semver(_) => {
schema["format"] = serde_json::json!("semver");
}
#[cfg(feature = "string-advanced")]
StringCheck::SemverFull(_) => {
schema["format"] = serde_json::json!("semver-full");
}
StringCheck::Jwt(_) => {
schema["format"] = serde_json::json!("jwt");
}
StringCheck::Ascii(_) => {
schema["format"] = serde_json::json!("ascii");
}
StringCheck::Alpha(_) => {
schema["format"] = serde_json::json!("alpha");
}
StringCheck::Alphanumeric(_) => {
schema["format"] = serde_json::json!("alphanumeric");
}
StringCheck::Lowercase(_) => {
schema["format"] = serde_json::json!("lowercase");
}
StringCheck::Uppercase(_) => {
schema["format"] = serde_json::json!("uppercase");
}
StringCheck::IsoDate(_) => {
schema["format"] = serde_json::json!("date");
}
StringCheck::IsoDatetime(_) => {
schema["format"] = serde_json::json!("date-time");
}
StringCheck::IsoTime(_) => {
schema["format"] = serde_json::json!("time");
}
StringCheck::Hostname(_) => {
schema["format"] = serde_json::json!("hostname");
}
StringCheck::NonEmpty(_) => {
schema["minLength"] = serde_json::json!(1);
}
StringCheck::Cuid2(_) => {
schema["format"] = serde_json::json!("cuid2");
}
StringCheck::Ulid(_) => {
schema["format"] = serde_json::json!("ulid");
}
StringCheck::Nanoid(_) => {
schema["format"] = serde_json::json!("nanoid");
}
StringCheck::Emoji(_) => {
schema["format"] = serde_json::json!("emoji");
}
_ => {}
}
}
schema
}
}
impl Default for ZString {
fn default() -> Self {
Self::new()
}
}
impl VldSchema for ZString {
type Output = String;
fn parse_value(&self, value: &Value) -> Result<String, VldError> {
let type_err = |value: &Value| -> VldError {
let msg = self
.custom_type_error
.clone()
.unwrap_or_else(|| format!("Expected string, received {}", value_type_name(value)));
VldError::single_with_value(
IssueCode::InvalidType {
expected: "string".to_string(),
received: value_type_name(value),
},
msg,
value,
)
};
let mut s = if let Some(s) = value.as_str() {
s.to_string()
} else if self.coerce {
match value {
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => return Err(type_err(value)),
}
} else {
return Err(type_err(value));
};
for t in &self.transforms {
match t {
StringTransform::Trim => s = s.trim().to_string(),
StringTransform::ToLowerCase => s = s.to_lowercase(),
StringTransform::ToUpperCase => s = s.to_uppercase(),
}
}
let str_val = Value::String(s.clone());
let mut errors = VldError::new();
for check in &self.checks {
match check {
StringCheck::Min(min, msg) => {
if s.chars().count() < *min {
errors.push_with_value(
IssueCode::TooSmall {
minimum: *min as f64,
inclusive: true,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Max(max, msg) => {
if s.chars().count() > *max {
errors.push_with_value(
IssueCode::TooBig {
maximum: *max as f64,
inclusive: true,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Len(len, msg) => {
if s.chars().count() != *len {
errors.push_with_value(
IssueCode::Custom {
code: "invalid_length".to_string(),
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Email(msg) => {
if !is_valid_email(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Email,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Url(msg) => {
if !is_valid_url(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Url,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::UrlStrict(msg) => {
if !is_valid_url_strict(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::UrlStrict,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::Uri(msg) => {
if !is_valid_uri(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Uri,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Uuid(msg) => {
if !is_valid_uuid(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Uuid,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::UuidV1(msg) => {
if !is_valid_uuid_version(&s, 1) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::UuidV1,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::UuidV4(msg) => {
if !is_valid_uuid_version(&s, 4) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::UuidV4,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::UuidV7(msg) => {
if !is_valid_uuid_version(&s, 7) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::UuidV7,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Ip(msg) => {
if !is_valid_ip(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Ip,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Slug(msg) => {
if !is_valid_slug(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Slug,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Color(msg) => {
if !is_valid_color(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Color,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Currency(msg) => {
if !is_valid_currency_code(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Currency,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::CountryCode(msg) => {
if !is_valid_country_code(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::CountryCode,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Locale(msg) => {
if !is_valid_locale(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Locale,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Cron(msg) => {
if !is_valid_cron(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Cron,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "regex")]
StringCheck::Regex(re, msg) => {
if !re.is_match(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Regex,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::StartsWith(prefix, msg) => {
if !s.starts_with(prefix.as_str()) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::StartsWith,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::EndsWith(suffix, msg) => {
if !s.ends_with(suffix.as_str()) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::EndsWith,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Contains(sub, msg) => {
if !s.contains(sub.as_str()) {
errors.push_with_value(
IssueCode::Custom {
code: "invalid_string".to_string(),
},
msg.clone(),
&str_val,
);
}
}
StringCheck::NonEmpty(msg) => {
if s.is_empty() {
errors.push_with_value(
IssueCode::TooSmall {
minimum: 1.0,
inclusive: true,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Ipv4(msg) => {
if !is_valid_ipv4(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Ipv4,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Ipv6(msg) => {
if !is_valid_ipv6(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Ipv6,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Cidr(msg) => {
if !is_valid_cidr(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Cidr,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Mac(msg) => {
if !is_valid_mac(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Mac,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Hex(msg) => {
if !is_valid_hex(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Hex,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::CreditCard(msg) => {
if !is_valid_credit_card(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::CreditCard,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Phone(msg) => {
if !is_valid_phone(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Phone,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::PhoneE164(msg) => {
if !is_valid_phone_e164_strict(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::PhoneE164,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Semver(msg) => {
if !is_valid_semver(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Semver,
},
msg.clone(),
&str_val,
);
}
}
#[cfg(feature = "string-advanced")]
StringCheck::SemverFull(msg) => {
if !is_valid_semver_full(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::SemverFull,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Jwt(msg) => {
if !is_valid_jwt(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Jwt,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Ascii(msg) => {
if !s.is_ascii() {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Ascii,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Alpha(msg) => {
if !s.chars().all(|c| c.is_ascii_alphabetic()) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Alpha,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Alphanumeric(msg) => {
if !s.chars().all(|c| c.is_ascii_alphanumeric()) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Alphanumeric,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Lowercase(msg) => {
if s != s.to_lowercase() {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Lowercase,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Uppercase(msg) => {
if s != s.to_uppercase() {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Uppercase,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Base64(msg) => {
if !is_valid_base64(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Base64,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::IsoDate(msg) => {
if !is_valid_iso_date(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::IsoDate,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::IsoDatetime(msg) => {
if !is_valid_iso_datetime(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::IsoDatetime,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::IsoTime(msg) => {
if !is_valid_iso_time(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::IsoTime,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Hostname(msg) => {
if !is_valid_hostname(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Hostname,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Cuid2(msg) => {
if !is_valid_cuid2(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Cuid2,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Ulid(msg) => {
if !is_valid_ulid(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Ulid,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Nanoid(msg) => {
if !is_valid_nanoid(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Nanoid,
},
msg.clone(),
&str_val,
);
}
}
StringCheck::Emoji(msg) => {
if !is_valid_emoji(&s) {
errors.push_with_value(
IssueCode::InvalidString {
validation: StringValidation::Emoji,
},
msg.clone(),
&str_val,
);
}
}
}
}
if errors.is_empty() {
Ok(s)
} else {
Err(errors)
}
}
}