use crate::argument::{
ArgumentError,
ArgumentErrorKind,
ArgumentResult,
LengthConstraint,
LengthMetric,
sealed::Sealed,
};
#[cfg(feature = "regex")]
use crate::argument::PatternExpectation;
#[cfg(feature = "regex")]
use regex::Regex;
pub trait StringArgument: Sealed + Sized {
fn require_non_blank(self, path: &str) -> ArgumentResult<Self>;
fn require_byte_len(
self,
path: &str,
expected: usize,
) -> ArgumentResult<Self>;
fn require_byte_len_at_least(
self,
path: &str,
min: usize,
) -> ArgumentResult<Self>;
fn require_byte_len_at_most(
self,
path: &str,
max: usize,
) -> ArgumentResult<Self>;
fn require_byte_len_in(
self,
path: &str,
min: usize,
max: usize,
) -> ArgumentResult<Self>;
fn require_char_count(
self,
path: &str,
expected: usize,
) -> ArgumentResult<Self>;
fn require_char_count_at_least(
self,
path: &str,
min: usize,
) -> ArgumentResult<Self>;
fn require_char_count_at_most(
self,
path: &str,
max: usize,
) -> ArgumentResult<Self>;
fn require_char_count_in(
self,
path: &str,
min: usize,
max: usize,
) -> ArgumentResult<Self>;
#[cfg(feature = "regex")]
fn require_match(self, path: &str, pattern: &Regex)
-> ArgumentResult<Self>;
#[cfg(feature = "regex")]
fn require_not_match(
self,
path: &str,
pattern: &Regex,
) -> ArgumentResult<Self>;
}
impl StringArgument for &str {
#[inline]
fn require_non_blank(self, path: &str) -> ArgumentResult<Self> {
validate_non_blank(self, path)?;
Ok(self)
}
#[inline]
fn require_byte_len(
self,
path: &str,
expected: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::Exact(expected),
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_byte_len_at_least(
self,
path: &str,
min: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::AtLeast(min),
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_byte_len_at_most(
self,
path: &str,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::AtMost(max),
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_byte_len_in(
self,
path: &str,
min: usize,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::InRange { min, max },
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_char_count(
self,
path: &str,
expected: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::Exact(expected),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[inline]
fn require_char_count_at_least(
self,
path: &str,
min: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::AtLeast(min),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[inline]
fn require_char_count_at_most(
self,
path: &str,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::AtMost(max),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[inline]
fn require_char_count_in(
self,
path: &str,
min: usize,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::InRange { min, max },
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[cfg(feature = "regex")]
#[inline]
fn require_match(
self,
path: &str,
pattern: &Regex,
) -> ArgumentResult<Self> {
validate_pattern(self, path, pattern, PatternExpectation::Match)?;
Ok(self)
}
#[cfg(feature = "regex")]
#[inline]
fn require_not_match(
self,
path: &str,
pattern: &Regex,
) -> ArgumentResult<Self> {
validate_pattern(self, path, pattern, PatternExpectation::NoMatch)?;
Ok(self)
}
}
impl StringArgument for String {
#[inline]
fn require_non_blank(self, path: &str) -> ArgumentResult<Self> {
validate_non_blank(self.as_str(), path)?;
Ok(self)
}
#[inline]
fn require_byte_len(
self,
path: &str,
expected: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::Exact(expected),
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_byte_len_at_least(
self,
path: &str,
min: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::AtLeast(min),
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_byte_len_at_most(
self,
path: &str,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::AtMost(max),
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_byte_len_in(
self,
path: &str,
min: usize,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.len(),
LengthConstraint::InRange { min, max },
LengthMetric::Bytes,
)?;
Ok(self)
}
#[inline]
fn require_char_count(
self,
path: &str,
expected: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::Exact(expected),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[inline]
fn require_char_count_at_least(
self,
path: &str,
min: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::AtLeast(min),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[inline]
fn require_char_count_at_most(
self,
path: &str,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::AtMost(max),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[inline]
fn require_char_count_in(
self,
path: &str,
min: usize,
max: usize,
) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::InRange { min, max },
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
#[cfg(feature = "regex")]
#[inline]
fn require_match(
self,
path: &str,
pattern: &Regex,
) -> ArgumentResult<Self> {
validate_pattern(
self.as_str(),
path,
pattern,
PatternExpectation::Match,
)?;
Ok(self)
}
#[cfg(feature = "regex")]
#[inline]
fn require_not_match(
self,
path: &str,
pattern: &Regex,
) -> ArgumentResult<Self> {
validate_pattern(
self.as_str(),
path,
pattern,
PatternExpectation::NoMatch,
)?;
Ok(self)
}
}
fn validate_non_blank(value: &str, path: &str) -> ArgumentResult<()> {
if value.chars().all(char::is_whitespace) {
Err(ArgumentError::new(path, ArgumentErrorKind::Blank))
} else {
Ok(())
}
}
fn validate_length(
path: &str,
actual: usize,
constraint: LengthConstraint,
metric: LengthMetric,
) -> ArgumentResult<()> {
if let LengthConstraint::InRange { min, max } = &constraint
&& min > max
{
return Err(ArgumentError::new(
path,
ArgumentErrorKind::InvalidLengthConstraint { constraint, metric },
));
}
let is_valid = match &constraint {
LengthConstraint::Exact(expected) => actual == *expected,
LengthConstraint::AtLeast(min) => actual >= *min,
LengthConstraint::AtMost(max) => actual <= *max,
LengthConstraint::InRange { min, max } => {
actual >= *min && actual <= *max
}
};
if is_valid {
Ok(())
} else {
Err(ArgumentError::new(
path,
ArgumentErrorKind::Length {
actual,
constraint,
metric,
},
))
}
}
#[cfg(feature = "regex")]
fn validate_pattern(
value: &str,
path: &str,
pattern: &Regex,
expectation: PatternExpectation,
) -> ArgumentResult<()> {
let matches = pattern.is_match(value);
let is_valid = match expectation {
PatternExpectation::Match => matches,
PatternExpectation::NoMatch => !matches,
};
if is_valid {
Ok(())
} else {
Err(ArgumentError::new(
path,
ArgumentErrorKind::Pattern {
pattern: String::from(pattern.as_str()),
expectation,
},
))
}
}