validation2 example now working and displaying the full potential of the feature2 being implemented

This commit is contained in:
Priec
2025-08-05 21:11:31 +02:00
parent d62cc2add6
commit c9b4841f67
3 changed files with 784 additions and 323 deletions

View File

@@ -2,6 +2,7 @@
//! Position-based pattern filtering for validation
use serde::{Deserialize, Serialize};
use std::sync::Arc;
/// A filter that applies to specific character positions in a field
#[derive(Debug, Clone)]
@@ -26,7 +27,6 @@ pub enum PositionRange {
}
/// Types of character filters that can be applied
#[derive(Debug, Clone)]
pub enum CharacterFilter {
/// Allow only alphabetic characters (a-z, A-Z)
Alphabetic,
@@ -39,7 +39,34 @@ pub enum CharacterFilter {
/// Allow any character from the provided set
OneOf(Vec<char>),
/// Custom user-defined filter function
Custom(Box<dyn Fn(char) -> bool + Send + Sync>),
Custom(Arc<dyn Fn(char) -> bool + Send + Sync>),
}
// Manual implementations for Debug and Clone
impl std::fmt::Debug for CharacterFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CharacterFilter::Alphabetic => write!(f, "Alphabetic"),
CharacterFilter::Numeric => write!(f, "Numeric"),
CharacterFilter::Alphanumeric => write!(f, "Alphanumeric"),
CharacterFilter::Exact(ch) => write!(f, "Exact('{}')", ch),
CharacterFilter::OneOf(chars) => write!(f, "OneOf({:?})", chars),
CharacterFilter::Custom(_) => write!(f, "Custom(<function>)"),
}
}
}
impl Clone for CharacterFilter {
fn clone(&self) -> Self {
match self {
CharacterFilter::Alphabetic => CharacterFilter::Alphabetic,
CharacterFilter::Numeric => CharacterFilter::Numeric,
CharacterFilter::Alphanumeric => CharacterFilter::Alphanumeric,
CharacterFilter::Exact(ch) => CharacterFilter::Exact(*ch),
CharacterFilter::OneOf(chars) => CharacterFilter::OneOf(chars.clone()),
CharacterFilter::Custom(func) => CharacterFilter::Custom(Arc::clone(func)),
}
}
}
impl PositionRange {
@@ -52,7 +79,7 @@ impl PositionRange {
PositionRange::Multiple(positions) => positions.contains(&position),
}
}
/// Get all positions up to a given length that this range covers
pub fn positions_up_to(&self, max_length: usize) -> Vec<usize> {
match self {
@@ -96,7 +123,7 @@ impl CharacterFilter {
CharacterFilter::Custom(func) => func(ch),
}
}
/// Get a human-readable description of this filter
pub fn description(&self) -> String {
match self {
@@ -118,7 +145,7 @@ impl PositionFilter {
pub fn new(positions: PositionRange, filter: CharacterFilter) -> Self {
Self { positions, filter }
}
/// Validate a character at a specific position
pub fn validate_position(&self, position: usize, character: char) -> bool {
if self.positions.contains(position) {
@@ -127,7 +154,7 @@ impl PositionFilter {
true // Position not covered by this filter, allow any character
}
}
/// Get error message for invalid character at position
pub fn error_message(&self, position: usize, character: char) -> Option<String> {
if self.positions.contains(position) && !self.filter.accepts(character) {
@@ -154,19 +181,19 @@ impl PatternFilters {
pub fn new() -> Self {
Self::default()
}
/// Add a position filter
pub fn add_filter(mut self, filter: PositionFilter) -> Self {
self.filters.push(filter);
self
}
/// Add multiple filters
pub fn add_filters(mut self, filters: Vec<PositionFilter>) -> Self {
self.filters.extend(filters);
self
}
/// Validate a character at a specific position against all applicable filters
pub fn validate_char_at_position(&self, position: usize, character: char) -> Result<(), String> {
for filter in &self.filters {
@@ -176,7 +203,7 @@ impl PatternFilters {
}
Ok(())
}
/// Validate entire text against all filters
pub fn validate_text(&self, text: &str) -> Result<(), String> {
for (position, character) in text.char_indices() {
@@ -186,12 +213,12 @@ impl PatternFilters {
}
Ok(())
}
/// Check if any filters are configured
pub fn has_filters(&self) -> bool {
!self.filters.is_empty()
}
/// Get all configured filters
pub fn filters(&self) -> &[PositionFilter] {
&self.filters
@@ -206,63 +233,63 @@ mod tests {
fn test_position_range_contains() {
assert!(PositionRange::Single(3).contains(3));
assert!(!PositionRange::Single(3).contains(2));
assert!(PositionRange::Range(1, 4).contains(3));
assert!(!PositionRange::Range(1, 4).contains(5));
assert!(PositionRange::From(2).contains(5));
assert!(!PositionRange::From(2).contains(1));
assert!(PositionRange::Multiple(vec![0, 2, 5]).contains(2));
assert!(!PositionRange::Multiple(vec![0, 2, 5]).contains(3));
}
#[test]
fn test_position_range_positions_up_to() {
assert_eq!(PositionRange::Single(3).positions_up_to(5), vec![3]);
assert_eq!(PositionRange::Single(5).positions_up_to(3), vec![]);
assert_eq!(PositionRange::Range(1, 3).positions_up_to(5), vec![1, 2, 3]);
assert_eq!(PositionRange::Range(1, 5).positions_up_to(3), vec![1, 2]);
assert_eq!(PositionRange::From(2).positions_up_to(5), vec![2, 3, 4]);
assert_eq!(PositionRange::Multiple(vec![0, 2, 5]).positions_up_to(4), vec![0, 2]);
}
#[test]
fn test_character_filter_accepts() {
assert!(CharacterFilter::Alphabetic.accepts('a'));
assert!(CharacterFilter::Alphabetic.accepts('Z'));
assert!(!CharacterFilter::Alphabetic.accepts('1'));
assert!(CharacterFilter::Numeric.accepts('5'));
assert!(!CharacterFilter::Numeric.accepts('a'));
assert!(CharacterFilter::Alphanumeric.accepts('a'));
assert!(CharacterFilter::Alphanumeric.accepts('5'));
assert!(!CharacterFilter::Alphanumeric.accepts('-'));
assert!(CharacterFilter::Exact('x').accepts('x'));
assert!(!CharacterFilter::Exact('x').accepts('y'));
assert!(CharacterFilter::OneOf(vec!['a', 'b', 'c']).accepts('b'));
assert!(!CharacterFilter::OneOf(vec!['a', 'b', 'c']).accepts('d'));
}
#[test]
fn test_position_filter_validation() {
let filter = PositionFilter::new(
PositionRange::Range(0, 1),
CharacterFilter::Alphabetic,
);
assert!(filter.validate_position(0, 'A'));
assert!(filter.validate_position(1, 'b'));
assert!(!filter.validate_position(0, '1'));
assert!(filter.validate_position(2, '1')); // Position 2 not covered, allow anything
}
#[test]
fn test_pattern_filters_validation() {
let patterns = PatternFilters::new()
@@ -274,25 +301,25 @@ mod tests {
PositionRange::Range(2, 4),
CharacterFilter::Numeric,
));
// Valid pattern: AB123
assert!(patterns.validate_text("AB123").is_ok());
// Invalid: number in alphabetic position
assert!(patterns.validate_text("A1123").is_err());
// Invalid: letter in numeric position
assert!(patterns.validate_text("AB1A3").is_err());
}
#[test]
fn test_custom_filter() {
let pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::From(0),
CharacterFilter::Custom(Box::new(|c| c.is_lowercase())),
CharacterFilter::Custom(Arc::new(|c| c.is_lowercase())),
));
assert!(pattern.validate_text("hello").is_ok());
assert!(pattern.validate_text("Hello").is_err()); // Uppercase not allowed
}