altrios_core/
validate.rs

1use crate::combo_error::ComboErrors;
2use crate::imports::*;
3use std::cmp::Ordering;
4use std::cmp::PartialOrd;
5use std::fmt::Debug;
6use uom::si::Quantity;
7use uom::ConstZero;
8
9pub type ValidationError = anyhow::Error;
10pub type ValidationErrors = ComboErrors<ValidationError>;
11pub type ValidationResults = Result<(), ValidationErrors>;
12
13/// Generate valid default-like input for use in other objects
14pub trait Valid: Sized + Default {
15    fn valid() -> Self {
16        Default::default()
17    }
18}
19
20/// Specify when an object is valid, real, and fake
21pub trait ObjState {
22    fn is_fake(&self) -> bool {
23        false
24    }
25    fn validate(&self) -> ValidationResults {
26        Ok(())
27    }
28}
29
30pub trait ObjStateConst: ObjState {
31    fn is_real(&self) -> bool;
32    fn is_valid(&self) -> bool;
33    fn real(&self) -> Option<&Self>;
34}
35
36impl<T: ObjState> ObjStateConst for T {
37    fn is_real(&self) -> bool {
38        !self.is_fake()
39    }
40    fn is_valid(&self) -> bool {
41        self.validate().is_ok()
42    }
43    fn real(&self) -> Option<&Self> {
44        if self.is_fake() {
45            return None;
46        }
47        Some(self)
48    }
49}
50
51pub fn validate_slice_real<T>(errors: &mut ValidationErrors, slice: &[T], elem_name: &str)
52where
53    T: ObjState,
54{
55    validate_slice_real_shift(errors, slice, elem_name, 0)
56}
57
58pub fn validate_slice_real_shift<T>(
59    errors: &mut ValidationErrors,
60    slice: &[T],
61    elem_name: &str,
62    idx_shift: isize,
63) where
64    T: ObjState,
65{
66    for (index, val) in slice.iter().enumerate() {
67        if val.is_fake() {
68            errors.push(anyhow!(
69                "{} at index = {} must be real!",
70                elem_name,
71                index as isize + idx_shift
72            ));
73        }
74        if let Err(mut errors_add) = val.validate() {
75            errors_add.add_context(anyhow!(
76                "{} at index = {} must be valid!",
77                elem_name,
78                index as isize + idx_shift
79            ));
80            errors.append(&mut errors_add);
81        }
82    }
83}
84
85pub fn validate_slice_fake<T>(errors: &mut ValidationErrors, slice: &[T], elem_name: &str)
86where
87    T: ObjState,
88{
89    validate_slice_fake_shift(errors, slice, elem_name, 0)
90}
91
92pub fn validate_slice_fake_shift<T>(
93    errors: &mut ValidationErrors,
94    slice: &[T],
95    elem_name: &str,
96    idx_shift: isize,
97) where
98    T: ObjState,
99{
100    for (index, val) in slice.iter().enumerate() {
101        if val.is_real() {
102            errors.push(anyhow!(
103                "{} at index = {} must be fake!",
104                elem_name,
105                index as isize + idx_shift
106            ));
107        }
108        if let Err(mut errors_add) = val.validate() {
109            errors_add.add_context(anyhow!(
110                "{} at index = {} must be valid!",
111                elem_name,
112                index as isize + idx_shift
113            ));
114            errors.append(&mut errors_add);
115        }
116    }
117}
118
119pub fn validate_field_fake<T>(errors: &mut ValidationErrors, field_val: &T, field_name: &str)
120where
121    T: ObjState + Debug,
122{
123    if !field_val.is_fake() || field_val.is_real() {
124        errors.push(anyhow!(
125            "{} = {:?} must be fake and not real!",
126            field_name,
127            field_val
128        ));
129    }
130    if let Err(mut errors_add) = field_val.validate() {
131        errors_add.add_context(anyhow!("{} must be valid!", field_name));
132        errors.append(&mut errors_add);
133    }
134}
135
136pub fn validate_field_real<T>(errors: &mut ValidationErrors, field_val: &T, field_name: &str)
137where
138    T: ObjState + Debug,
139{
140    if !field_val.is_real() || field_val.is_fake() {
141        errors.push(anyhow!(
142            "{} = {:?} must be real and not fake!",
143            field_name,
144            field_val
145        ));
146    }
147    if let Err(mut errors_add) = field_val.validate() {
148        errors_add.add_context(anyhow!("{} must be valid!", field_name));
149        errors.append(&mut errors_add);
150    }
151}
152
153/// Check if si quantity is a number (i.e. not nan)
154pub fn si_chk_num<D, U>(
155    errors: &mut ValidationErrors,
156    field_val: &Quantity<D, U, f64>,
157    field_name: &str,
158) where
159    D: uom::si::Dimension + ?Sized,
160    U: uom::si::Units<f64> + ?Sized,
161{
162    if field_val.is_nan() {
163        errors.push(anyhow!(
164            "{} = {:?} must be a number!",
165            field_name,
166            field_val
167        ));
168    }
169}
170
171/// Check if si quantity is finite
172pub fn si_chk_num_fin<D, U>(
173    errors: &mut ValidationErrors,
174    field_val: &uom::si::Quantity<D, U, f64>,
175    field_name: &str,
176) where
177    D: uom::si::Dimension + ?Sized,
178    U: uom::si::Units<f64> + ?Sized,
179{
180    if field_val.is_nan() || field_val.is_infinite() {
181        errors.push(anyhow!(
182            "{} = {:?} must be a finite number!",
183            field_name,
184            field_val
185        ));
186    }
187}
188
189/// Check that SI quantity is greater than or equal to zero
190pub fn si_chk_num_gez<T>(errors: &mut ValidationErrors, field_val: &T, field_name: &str)
191where
192    T: Debug + PartialOrd + ConstZero,
193{
194    if let None | Some(Ordering::Less) = field_val.partial_cmp(&T::ZERO) {
195        errors.push(anyhow!(
196            "{} = {:?} must be a positive number!",
197            field_name,
198            field_val
199        ));
200    }
201}
202
203/// Check if si quanity is greater than zero
204pub fn si_chk_num_gtz<T>(errors: &mut ValidationErrors, field_val: &T, field_name: &str)
205where
206    T: Debug + PartialOrd + ConstZero,
207{
208    if let None | Some(Ordering::Less) | Some(Ordering::Equal) = field_val.partial_cmp(&T::ZERO) {
209        errors.push(anyhow!(
210            "{} = {:?} must be a number larger than zero!",
211            field_name,
212            field_val
213        ));
214    }
215}
216
217/// Check if si quantity is greater than or equal zero and finite
218pub fn si_chk_num_gez_fin<D, U>(
219    errors: &mut ValidationErrors,
220    field_val: &uom::si::Quantity<D, U, f64>,
221    field_name: &str,
222) where
223    D: uom::si::Dimension + ?Sized,
224    U: uom::si::Units<f64> + ?Sized,
225{
226    if !(*field_val >= uom::si::Quantity::<D, U, f64>::ZERO && field_val.is_finite()) {
227        errors.push(anyhow!(
228            "{} = {:?} must be a finite positive number!",
229            field_name,
230            field_val
231        ));
232    }
233}
234
235/// Check if si quantity is greater than zero and finite
236pub fn si_chk_num_gtz_fin<D, U>(
237    errors: &mut ValidationErrors,
238    field_val: &uom::si::Quantity<D, U, f64>,
239    field_name: &str,
240) where
241    D: uom::si::Dimension + ?Sized,
242    U: uom::si::Units<f64> + ?Sized,
243{
244    if !(*field_val > uom::si::Quantity::<D, U, f64>::ZERO && field_val.is_finite()) {
245        errors.push(anyhow!(
246            "{} = {:?} must be a finite number larger than zero!",
247            field_name,
248            field_val
249        ));
250    }
251}
252
253/// Check if si quanity is equal to zero
254pub fn si_chk_num_eqz<T>(errors: &mut ValidationErrors, field_val: &T, field_name: &str)
255where
256    T: Debug + PartialEq + ConstZero,
257{
258    if *field_val != T::ZERO {
259        errors.push(anyhow!("{} = {:?} must equal zero!", field_name, field_val));
260    }
261}
262
263macro_rules! early_err {
264    ($errors:expr, $name:expr) => {
265        if !$errors.is_empty() {
266            $errors.push(anyhow!("{} validation unfinished!", $name));
267            return Err($errors);
268        }
269    };
270}
271
272macro_rules! early_fake_ok {
273    ($self:expr) => {
274        if $self.is_fake() {
275            return Ok(());
276        }
277    };
278}
279
280pub(crate) use {early_err, early_fake_ok};