Skip to main content

icydb_model/base/validator/
len.rs

1//! Module: base::validator::len
2//!
3//! Responsibility: base validator definitions.
4//! Does not own: normalization policy, persistence, or schema mutation semantics.
5//! Boundary: reports typed visitor issues for facade schema values.
6
7use crate::{prelude::*, visitor::Validator};
8use std::{
9    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
10    hash::BuildHasher,
11};
12
13///
14/// HasLen
15///
16
17#[expect(clippy::len_without_is_empty)]
18pub trait HasLen {
19    fn len(&self) -> usize;
20}
21
22impl HasLen for Blob {
23    fn len(&self) -> usize {
24        Self::len(self)
25    }
26}
27
28impl HasLen for str {
29    fn len(&self) -> usize {
30        self.chars().count()
31    }
32}
33
34impl HasLen for String {
35    fn len(&self) -> usize {
36        self.chars().count()
37    }
38}
39
40impl<T> HasLen for [T] {
41    fn len(&self) -> usize {
42        <[T]>::len(self)
43    }
44}
45
46impl<T> HasLen for Vec<T> {
47    fn len(&self) -> usize {
48        Self::len(self)
49    }
50}
51
52impl<T, S: BuildHasher> HasLen for HashSet<T, S> {
53    fn len(&self) -> usize {
54        Self::len(self)
55    }
56}
57
58impl<K, V, S: BuildHasher> HasLen for HashMap<K, V, S> {
59    fn len(&self) -> usize {
60        Self::len(self)
61    }
62}
63
64impl<T> HasLen for BTreeSet<T> {
65    fn len(&self) -> usize {
66        Self::len(self)
67    }
68}
69
70impl<K, V> HasLen for BTreeMap<K, V> {
71    fn len(&self) -> usize {
72        Self::len(self)
73    }
74}
75
76//
77// ============================================================================
78// Equal
79// ============================================================================
80//
81
82#[validator]
83pub struct Equal {
84    target: usize,
85}
86
87impl Equal {
88    pub fn new(target: impl TryInto<usize>) -> Self {
89        Self {
90            target: target.try_into().unwrap_or_default(),
91        }
92    }
93}
94
95impl<T: HasLen + ?Sized> Validator<T> for Equal {
96    fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
97        let len = t.len();
98
99        if len != self.target {
100            ctx.issue(format!("length ({len}) is not equal to {}", self.target));
101        }
102    }
103}
104
105///
106/// Min
107///
108
109#[validator]
110pub struct Min {
111    target: usize,
112}
113
114impl Min {
115    pub fn new(target: impl TryInto<usize>) -> Self {
116        Self {
117            target: target.try_into().unwrap_or_default(),
118        }
119    }
120}
121
122impl<T: HasLen + ?Sized> Validator<T> for Min {
123    fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
124        let len = t.len();
125
126        if len < self.target {
127            ctx.issue(format!(
128                "length ({len}) is lower than minimum of {}",
129                self.target
130            ));
131        }
132    }
133}
134
135///
136/// Max
137///
138
139#[validator]
140pub struct Max {
141    target: usize,
142}
143
144impl Max {
145    pub fn new(target: impl TryInto<usize>) -> Self {
146        Self {
147            target: target.try_into().unwrap_or_default(),
148        }
149    }
150}
151
152impl<T: HasLen + ?Sized> Validator<T> for Max {
153    fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
154        let len = t.len();
155
156        if len > self.target {
157            ctx.issue(format!(
158                "length ({len}) is greater than maximum of {}",
159                self.target
160            ));
161        }
162    }
163}
164
165///
166/// Range
167///
168
169#[validator]
170pub struct Range {
171    min: usize,
172    max: usize,
173}
174
175impl Range {
176    pub fn new(min: impl TryInto<usize>, max: impl TryInto<usize>) -> Self {
177        Self {
178            min: min.try_into().unwrap_or_default(),
179            max: max.try_into().unwrap_or_default(),
180        }
181    }
182}
183
184impl<T: HasLen + ?Sized> Validator<T> for Range {
185    fn validate(&self, t: &T, ctx: &mut dyn VisitorContext) {
186        let len = t.len();
187
188        if len < self.min || len > self.max {
189            ctx.issue(format!(
190                "length ({len}) must be between {} and {} (inclusive)",
191                self.min, self.max
192            ));
193        }
194    }
195}
196
197///
198/// TESTS
199///
200
201#[cfg(test)]
202mod tests;