1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Module with constraint configs to apply to inputs
//! 
//! This module provides various constraints that can be applied to user inputs,
//! including constraints for strings and numbers. These constraints help ensure that
//! input data meets specific criteria before it is accepted.
//! 
//! # Example
//! 
//! ```
//! use constrained_inputs::constraints::{StringConstraint, NumberConstraint, Constraint};
//! 
//! fn main() {
//!     let string_constraint = StringConstraint {
//!         max_len: Some(10),
//!         min_len: Some(5),
//!         blacklist_chars: vec!['a', 'e', 'i', 'o', 'u'],
//!     };
//! 
//!     let result = string_constraint.validate(&"hello");
//!     assert_eq!(result, ConstraintResult::Err(ConstraintError::BlacklistedChar));
//! 
//!     let number_constraint = NumberConstraint {
//!         max: Some(100),
//!         min: Some(10),
//!     };
//! 
//!     let result = number_constraint.validate(&50);
//!     assert_eq!(result, ConstraintResult::Valid);
//! }
//! ```

/// Result type for constraints
#[derive(Debug, PartialEq)]
pub enum ConstraintResult {
    Valid,
    Err(ConstraintError),
}

/// Error types for constraints
#[derive(Debug, PartialEq)]
pub enum ConstraintError {
    InvalidConstraint,
    TooLarge,
    TooSmall,
    BlacklistedChar,
    TooLong,
    TooShort,
}

/// Constraint trait
pub trait Constraint<T> {
    /// Validates data to make sure it follows constraints
    fn validate(&self, data: &T) -> ConstraintResult;
}

/// String constraint config for applying constraints to what a string can be
pub struct StringConstraint {
    pub max_len: Option<usize>,
    pub min_len: Option<usize>,
    pub blacklist_chars: Vec<char>,
}

/// Implementing method to apply constraints on strings
impl<T> Constraint<T> for StringConstraint
where
    T: AsRef<str>,
{
    fn validate(&self, data: &T) -> ConstraintResult {
        let data = data.as_ref();

        if let Some(max_len) = self.max_len {
            if data.len() > max_len {
                return ConstraintResult::Err(ConstraintError::TooLong);
            }
        }
        if let Some(min_len) = self.min_len {
            if data.len() < min_len {
                return ConstraintResult::Err(ConstraintError::TooShort);
            }
        }
        for ch in data.chars() {
            if self.blacklist_chars.contains(&ch) {
                return ConstraintResult::Err(ConstraintError::BlacklistedChar);
            }
        }

        ConstraintResult::Valid
    }
}

/// Number constraint config for applying constraints onto some number
pub struct NumberConstraint {
    pub max: Option<i32>,
    pub min: Option<i32>,
}

/// Implementing method to apply constraints on numbers
impl<T> Constraint<T> for NumberConstraint
where
    T: Into<i32> + Clone + PartialOrd,
{
    fn validate(&self, data: &T) -> ConstraintResult {
        let res = i32::try_from(data.clone()).map_err(|_| ConstraintError::InvalidConstraint);
        if let Err(err) = res {
            return ConstraintResult::Err(err);
        }
        let data = res.unwrap();

        if let Some(max) = self.max {
            if data > max {
                return ConstraintResult::Err(ConstraintError::TooLarge);
            }
        }
        if let Some(min) = self.min {
            if data < min {
                return ConstraintResult::Err(ConstraintError::TooSmall);
            }
        }

        ConstraintResult::Valid
    }
}