Skip to main content

ic_dbms_api/dbms/sanitize/
clamp.rs

1use crate::prelude::{IcDbmsResult, Sanitize, Value};
2
3/// Sanitizer that clamps integer values within a specified range.
4///
5/// # Example
6///
7/// ```rust
8/// use ic_dbms_api::prelude::{ClampSanitizer, Value, Sanitize as _};
9///
10/// let value = Value::Int32(150.into());
11/// let sanitizer = ClampSanitizer { min: 0, max: 100 };
12/// let sanitized_value = sanitizer.sanitize(value).unwrap();
13/// assert_eq!(sanitized_value, Value::Int32(100.into()));
14/// ```
15pub struct ClampSanitizer {
16    pub min: i64,
17    pub max: i64,
18}
19
20impl Sanitize for ClampSanitizer {
21    fn sanitize(&self, value: Value) -> IcDbmsResult<Value> {
22        match value {
23            Value::Int32(num) => {
24                let clamped = (num.0 as i64).clamp(self.min, self.max);
25                let clamped: Result<i32, _> = clamped.try_into();
26                match clamped {
27                    Ok(clamped_i32) => Ok(Value::Int32(clamped_i32.into())),
28                    Err(_) => Err(crate::prelude::IcDbmsError::Sanitize(
29                        "Clamped value out of Int32 range".into(),
30                    )),
31                }
32            }
33            Value::Int64(num) => {
34                let clamped = num.0.clamp(self.min, self.max);
35                Ok(Value::Int64(clamped.into()))
36            }
37            other => Ok(other),
38        }
39    }
40}
41
42/// Sanitizer that clamps unsigned integer values within a specified range.
43///
44/// # Example
45///
46/// ```rust
47/// use ic_dbms_api::prelude::{ClampUnsignedSanitizer, Value, Sanitize as _};
48///
49/// let value = Value::Uint32(150.into());
50/// let sanitizer = ClampUnsignedSanitizer { min: 0, max: 100 };
51/// let sanitized_value = sanitizer.sanitize(value).unwrap();
52/// assert_eq!(sanitized_value, Value::Uint32(100.into()));
53/// ```
54pub struct ClampUnsignedSanitizer {
55    pub min: u64,
56    pub max: u64,
57}
58
59impl Sanitize for ClampUnsignedSanitizer {
60    fn sanitize(&self, value: Value) -> IcDbmsResult<Value> {
61        match value {
62            Value::Uint32(num) => {
63                let clamped = (num.0 as u64).clamp(self.min, self.max);
64                let clamped: Result<u32, _> = clamped.try_into();
65                match clamped {
66                    Ok(clamped_u32) => Ok(Value::Uint32(clamped_u32.into())),
67                    Err(_) => Err(crate::prelude::IcDbmsError::Sanitize(
68                        "Clamped value out of Uint32 range".into(),
69                    )),
70                }
71            }
72            Value::Uint64(num) => {
73                let clamped = num.0.clamp(self.min, self.max);
74                Ok(Value::Uint64(clamped.into()))
75            }
76            other => Ok(other),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_clamp_sanitizer_i32() {
87        let sanitizer = ClampSanitizer { min: 0, max: 100 };
88        let value_in_range = Value::Int32(50.into());
89        let value_below_range = Value::Int32((-10i32).into());
90        let value_above_range = Value::Int32(150.into());
91        let non_integer_value = Value::Text("Not an integer".into());
92
93        let sanitized_in_range = sanitizer.sanitize(value_in_range).unwrap();
94        let sanitized_below_range = sanitizer.sanitize(value_below_range).unwrap();
95        let sanitized_above_range = sanitizer.sanitize(value_above_range).unwrap();
96        let sanitized_non_integer = sanitizer.sanitize(non_integer_value).unwrap();
97
98        assert_eq!(sanitized_in_range, Value::Int32(50.into()));
99        assert_eq!(sanitized_below_range, Value::Int32(0.into()));
100        assert_eq!(sanitized_above_range, Value::Int32(100.into()));
101        assert_eq!(sanitized_non_integer, Value::Text("Not an integer".into()));
102    }
103
104    #[test]
105    fn test_clamp_sanitizer_i64() {
106        let sanitizer = ClampSanitizer { min: 0, max: 100 };
107        let value_in_range = Value::Int64(50.into());
108        let value_below_range = Value::Int64((-10i64).into());
109        let value_above_range = Value::Int64(150.into());
110        let non_integer_value = Value::Text("Not an integer".into());
111
112        let sanitized_in_range = sanitizer.sanitize(value_in_range).unwrap();
113        let sanitized_below_range = sanitizer.sanitize(value_below_range).unwrap();
114        let sanitized_above_range = sanitizer.sanitize(value_above_range).unwrap();
115        let sanitized_non_integer = sanitizer.sanitize(non_integer_value).unwrap();
116
117        assert_eq!(sanitized_in_range, Value::Int64(50.into()));
118        assert_eq!(sanitized_below_range, Value::Int64(0.into()));
119        assert_eq!(sanitized_above_range, Value::Int64(100.into()));
120        assert_eq!(sanitized_non_integer, Value::Text("Not an integer".into()));
121    }
122
123    #[test]
124    fn test_clamp_unsigned_sanitizer_u32() {
125        let sanitizer = ClampUnsignedSanitizer { min: 0, max: 100 };
126        let value_in_range = Value::Uint32(50.into());
127        let value_below_range = Value::Uint32(0.into()); // Unsigned can't be negative
128        let value_above_range = Value::Uint32(150.into());
129        let non_integer_value = Value::Text("Not an integer".into());
130
131        let sanitized_in_range = sanitizer.sanitize(value_in_range).unwrap();
132        let sanitized_below_range = sanitizer.sanitize(value_below_range).unwrap();
133        let sanitized_above_range = sanitizer.sanitize(value_above_range).unwrap();
134        let sanitized_non_integer = sanitizer.sanitize(non_integer_value).unwrap();
135
136        assert_eq!(sanitized_in_range, Value::Uint32(50.into()));
137        assert_eq!(sanitized_below_range, Value::Uint32(0.into()));
138        assert_eq!(sanitized_above_range, Value::Uint32(100.into()));
139        assert_eq!(sanitized_non_integer, Value::Text("Not an integer".into()));
140    }
141
142    #[test]
143    fn test_clamp_unsigned_sanitizer_u64() {
144        let sanitizer = ClampUnsignedSanitizer { min: 0, max: 100 };
145        let value_in_range = Value::Uint64(50.into());
146        let value_below_range = Value::Uint64(0.into()); // Unsigned can't be negative
147        let value_above_range = Value::Uint64(150.into());
148        let non_integer_value = Value::Text("Not an integer".into());
149
150        let sanitized_in_range = sanitizer.sanitize(value_in_range).unwrap();
151        let sanitized_below_range = sanitizer.sanitize(value_below_range).unwrap();
152        let sanitized_above_range = sanitizer.sanitize(value_above_range).unwrap();
153        let sanitized_non_integer = sanitizer.sanitize(non_integer_value).unwrap();
154
155        assert_eq!(sanitized_in_range, Value::Uint64(50.into()));
156        assert_eq!(sanitized_below_range, Value::Uint64(0.into()));
157        assert_eq!(sanitized_above_range, Value::Uint64(100.into()));
158        assert_eq!(sanitized_non_integer, Value::Text("Not an integer".into()));
159    }
160}