1use crate::core::OCRError;
7
8#[derive(Debug, Clone)]
26pub struct ScoreValidator {
27 min: f32,
28 max: f32,
29 field_name: String,
30}
31
32impl ScoreValidator {
33 pub fn new(min: f32, max: f32, field_name: impl Into<String>) -> Self {
41 Self {
42 min,
43 max,
44 field_name: field_name.into(),
45 }
46 }
47
48 pub fn new_unit_range(field_name: impl Into<String>) -> Self {
52 Self::new(0.0, 1.0, field_name)
53 }
54
55 pub fn validate_score(&self, score: f32, context: &str) -> Result<(), OCRError> {
61 if !(self.min..=self.max).contains(&score) {
62 return Err(OCRError::InvalidInput {
63 message: format!(
64 "{}: {} {} is out of valid range [{}, {}]",
65 context, self.field_name, score, self.min, self.max
66 ),
67 });
68 }
69 Ok(())
70 }
71
72 pub fn validate_scores(&self, scores: &[f32], context_prefix: &str) -> Result<(), OCRError> {
79 for (idx, &score) in scores.iter().enumerate() {
80 self.validate_score(score, &format!("{} {}", context_prefix, idx))?;
81 }
82 Ok(())
83 }
84
85 pub fn validate_scores_with<F>(&self, scores: &[f32], format_context: F) -> Result<(), OCRError>
90 where
91 F: Fn(usize) -> String,
92 {
93 for (idx, &score) in scores.iter().enumerate() {
94 self.validate_score(score, &format_context(idx))?;
95 }
96 Ok(())
97 }
98}
99
100pub fn validate_length_match(
106 actual: usize,
107 expected: usize,
108 actual_name: &str,
109 expected_name: &str,
110) -> Result<(), OCRError> {
111 if actual != expected {
112 return Err(OCRError::InvalidInput {
113 message: format!(
114 "Mismatch between {} count ({}) and {} count ({})",
115 actual_name, actual, expected_name, expected
116 ),
117 });
118 }
119 Ok(())
120}
121
122pub fn validate_max_value<T: PartialOrd + std::fmt::Display>(
128 value: T,
129 max: T,
130 field_name: &str,
131 context: &str,
132) -> Result<(), OCRError> {
133 if value > max {
134 return Err(OCRError::InvalidInput {
135 message: format!(
136 "{}: {} {} exceeds maximum {}",
137 context, field_name, value, max
138 ),
139 });
140 }
141 Ok(())
142}
143
144pub fn validate_positive_dimensions(
150 width: u32,
151 height: u32,
152 context: &str,
153) -> Result<(), OCRError> {
154 if width == 0 || height == 0 {
155 return Err(OCRError::InvalidInput {
156 message: format!(
157 "{}: invalid dimensions width={}, height={} (must be positive)",
158 context, width, height
159 ),
160 });
161 }
162 Ok(())
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_score_validator_unit_range() {
171 let validator = ScoreValidator::new_unit_range("score");
172
173 assert!(validator.validate_score(0.0, "test").is_ok());
175 assert!(validator.validate_score(0.5, "test").is_ok());
176 assert!(validator.validate_score(1.0, "test").is_ok());
177
178 assert!(validator.validate_score(-0.1, "test").is_err());
180 assert!(validator.validate_score(1.1, "test").is_err());
181 }
182
183 #[test]
184 fn test_score_validator_custom_range() {
185 let validator = ScoreValidator::new(0.5, 2.0, "custom");
186
187 assert!(validator.validate_score(0.5, "test").is_ok());
189 assert!(validator.validate_score(1.0, "test").is_ok());
190 assert!(validator.validate_score(2.0, "test").is_ok());
191
192 assert!(validator.validate_score(0.4, "test").is_err());
194 assert!(validator.validate_score(2.1, "test").is_err());
195 }
196
197 #[test]
198 fn test_validate_scores() {
199 let validator = ScoreValidator::new_unit_range("score");
200
201 assert!(validator.validate_scores(&[0.1, 0.5, 0.9], "test").is_ok());
203
204 assert!(validator.validate_scores(&[0.1, 1.5, 0.9], "test").is_err());
206 }
207
208 #[test]
209 fn test_validate_scores_with_formatter() {
210 let validator = ScoreValidator::new_unit_range("score");
211
212 let result = validator
213 .validate_scores_with(&[0.5, 1.5], |idx| format!("Image 0, detection {}", idx));
214
215 assert!(result.is_err());
216 let err_msg = format!("{:?}", result.unwrap_err());
217 assert!(err_msg.contains("detection 1"));
218 }
219
220 #[test]
221 fn test_validate_length_match() {
222 assert!(validate_length_match(3, 3, "texts", "scores").is_ok());
223 assert!(validate_length_match(3, 5, "texts", "scores").is_err());
224 }
225
226 #[test]
227 fn test_validate_max_value() {
228 assert!(validate_max_value(50, 100, "length", "text 0").is_ok());
229 assert!(validate_max_value(150, 100, "length", "text 0").is_err());
230 }
231
232 #[test]
233 fn test_validate_positive_dimensions() {
234 assert!(validate_positive_dimensions(100, 200, "image 0").is_ok());
235 assert!(validate_positive_dimensions(0, 200, "image 0").is_err());
236 assert!(validate_positive_dimensions(100, 0, "image 0").is_err());
237 }
238}