icydb/base/validator/
len.rs1use crate::{design::prelude::*, traits::Validator};
2use std::{
3 collections::{HashMap, HashSet},
4 hash::BuildHasher,
5};
6
7#[allow(clippy::len_without_is_empty)]
12pub trait HasLen {
13 fn len(&self) -> usize;
14}
15
16impl HasLen for Blob {
17 fn len(&self) -> usize {
18 Self::len(self)
19 }
20}
21
22impl HasLen for str {
23 fn len(&self) -> usize {
24 Self::len(self)
25 }
26}
27
28impl HasLen for String {
29 fn len(&self) -> usize {
30 Self::len(self)
31 }
32}
33
34impl<T> HasLen for [T] {
35 fn len(&self) -> usize {
36 <[T]>::len(self)
37 }
38}
39
40impl<T> HasLen for Vec<T> {
41 fn len(&self) -> usize {
42 Self::len(self)
43 }
44}
45
46impl<T, S: BuildHasher> HasLen for HashSet<T, S> {
47 fn len(&self) -> usize {
48 Self::len(self)
49 }
50}
51
52impl<K, V, S: BuildHasher> HasLen for HashMap<K, V, S> {
53 fn len(&self) -> usize {
54 Self::len(self)
55 }
56}
57
58#[validator]
65pub struct Equal {
66 target: usize,
67}
68
69impl Equal {
70 pub fn new(target: impl TryInto<usize>) -> Self {
71 Self {
72 target: target.try_into().unwrap_or_default(),
73 }
74 }
75}
76
77impl<T: HasLen + ?Sized> Validator<T> for Equal {
78 fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
79 let len = t.len();
80
81 if len != self.target {
82 ctx.issue(format!("length ({len}) is not equal to {}", self.target));
83 }
84 }
85}
86
87#[validator]
92pub struct Min {
93 target: usize,
94}
95
96impl Min {
97 pub fn new(target: impl TryInto<usize>) -> Self {
98 Self {
99 target: target.try_into().unwrap_or_default(),
100 }
101 }
102}
103
104impl<T: HasLen + ?Sized> Validator<T> for Min {
105 fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
106 let len = t.len();
107
108 if len < self.target {
109 ctx.issue(format!(
110 "length ({len}) is lower than minimum of {}",
111 self.target
112 ));
113 }
114 }
115}
116
117#[validator]
122pub struct Max {
123 target: usize,
124}
125
126impl Max {
127 pub fn new(target: impl TryInto<usize>) -> Self {
128 Self {
129 target: target.try_into().unwrap_or_default(),
130 }
131 }
132}
133
134impl<T: HasLen + ?Sized> Validator<T> for Max {
135 fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
136 let len = t.len();
137
138 if len > self.target {
139 ctx.issue(format!(
140 "length ({len}) is greater than maximum of {}",
141 self.target
142 ));
143 }
144 }
145}
146
147#[validator]
152pub struct Range {
153 min: usize,
154 max: usize,
155}
156
157impl Range {
158 pub fn new(min: impl TryInto<usize>, max: impl TryInto<usize>) -> Self {
159 Self {
160 min: min.try_into().unwrap_or_default(),
161 max: max.try_into().unwrap_or_default(),
162 }
163 }
164}
165
166impl<T: HasLen + ?Sized> Validator<T> for Range {
167 fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
168 let len = t.len();
169
170 if len < self.min || len > self.max {
171 ctx.issue(format!(
172 "length ({len}) must be between {} and {} (inclusive)",
173 self.min, self.max
174 ));
175 }
176 }
177}
178
179#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::visitor::{Issue, PathSegment, VisitorContext, VisitorIssues};
187
188 struct TestCtx {
189 issues: VisitorIssues,
190 }
191
192 impl TestCtx {
193 fn new() -> Self {
194 Self {
195 issues: VisitorIssues::new(),
196 }
197 }
198 }
199
200 impl VisitorContext for TestCtx {
201 fn add_issue(&mut self, issue: Issue) {
202 self.issues
203 .entry(String::new())
204 .or_default()
205 .push(issue.message);
206 }
207
208 fn add_issue_at(&mut self, _: PathSegment, issue: Issue) {
209 self.add_issue(issue);
210 }
211 }
212
213 #[test]
214 fn equal_reports_mismatch() {
215 let v = Equal::new(3);
216 let mut ctx = TestCtx::new();
217
218 v.validate("abcd", &mut ctx);
219
220 assert_eq!(ctx.issues[""][0], "length (4) is not equal to 3");
221 }
222
223 #[test]
224 fn range_accepts_in_bounds() {
225 let v = Range::new(2, 4);
226 let mut ctx = TestCtx::new();
227
228 v.validate("abc", &mut ctx);
229
230 assert!(ctx.issues.is_empty());
231 }
232
233 #[test]
234 fn range_reports_out_of_bounds() {
235 let v = Range::new(2, 4);
236 let mut ctx = TestCtx::new();
237
238 v.validate("a", &mut ctx);
239
240 assert_eq!(
241 ctx.issues[""][0],
242 "length (1) must be between 2 and 4 (inclusive)"
243 );
244 }
245}