#[cfg(feature = "regex")]
use regex::Regex;
use crate::argument::ArgumentError;
use crate::argument::ArgumentErrorKind;
use crate::argument::ArgumentResult;
use crate::argument::LengthConstraint;
use crate::argument::LengthMetric;
#[cfg(feature = "regex")]
use crate::argument::PatternExpectation;
use crate::argument::internal::Sealed;
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 {
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)
}
fn require_char_count(self, path: &str, expected: usize) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::Exact(expected),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
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)
}
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)
}
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")]
fn require_match(self, path: &str, pattern: &Regex) -> ArgumentResult<Self> {
validate_pattern(self, path, pattern, PatternExpectation::Match)?;
Ok(self)
}
#[cfg(feature = "regex")]
fn require_not_match(self, path: &str, pattern: &Regex) -> ArgumentResult<Self> {
validate_pattern(self, path, pattern, PatternExpectation::NoMatch)?;
Ok(self)
}
}
impl StringArgument for String {
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)
}
fn require_char_count(self, path: &str, expected: usize) -> ArgumentResult<Self> {
validate_length(
path,
self.chars().count(),
LengthConstraint::Exact(expected),
LengthMetric::UnicodeScalars,
)?;
Ok(self)
}
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)
}
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)
}
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")]
fn require_match(self, path: &str, pattern: &Regex) -> ArgumentResult<Self> {
validate_pattern(self.as_str(), path, pattern, PatternExpectation::Match)?;
Ok(self)
}
#[cfg(feature = "regex")]
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,
},
))
}
}