checked_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    num,
5    ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Rem, Sub},
6};
7
8use clamp::ValueRangeInclusive;
9pub mod clamp;
10pub mod guard;
11pub mod view;
12
13mod reexports {
14    #[doc(hidden)]
15    pub use anyhow::{anyhow, bail, ensure, format_err, Chain, Context, Error, Result};
16    #[doc(hidden)]
17    pub use serde;
18}
19
20pub mod prelude {
21    pub use crate::reexports::*;
22
23    pub use crate::{
24        clamp::*, commit_or_bail, view::*, Behavior, InherentBehavior, InherentLimits,
25        OpBehaviorParams,
26    };
27
28    pub use checked_rs_macros::clamped;
29}
30
31#[derive(Debug, Clone)]
32pub enum OpBehaviorParams<T: 'static + Copy + Eq + Ord + InherentLimits<T>> {
33    Simple {
34        min: T,
35        max: T,
36    },
37    ExactsOnly(&'static [T]),
38    RangesOnly(&'static [ValueRangeInclusive<T>]),
39    ExactsAndRanges {
40        exacts: &'static [T],
41        ranges: &'static [ValueRangeInclusive<T>],
42    },
43}
44
45pub trait Behavior: Copy + 'static {
46    fn add<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
47        lhs: T,
48        rhs: T,
49        params: OpBehaviorParams<T>,
50    ) -> T
51    where
52        T: Add<Output = T>,
53        T::Output: Eq + Ord + Into<T>,
54        num::Saturating<T>: Add<Output = num::Saturating<T>>,
55        <num::Saturating<T> as Add>::Output: Eq + Ord + Into<num::Saturating<T>>;
56    fn sub<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
57        lhs: T,
58        rhs: T,
59        params: OpBehaviorParams<T>,
60    ) -> T
61    where
62        T: Sub<Output = T>,
63        T::Output: Eq + Ord + Into<T>,
64        num::Saturating<T>: Sub<Output = num::Saturating<T>>,
65        <num::Saturating<T> as Sub>::Output: Eq + Ord + Into<num::Saturating<T>>;
66    fn mul<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
67        lhs: T,
68        rhs: T,
69        params: OpBehaviorParams<T>,
70    ) -> T
71    where
72        T: Mul<Output = T>,
73        T::Output: Eq + Ord + Into<T>,
74        num::Saturating<T>: Mul<Output = num::Saturating<T>>,
75        <num::Saturating<T> as Mul>::Output: Eq + Ord + Into<num::Saturating<T>>;
76    fn div<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
77        lhs: T,
78        rhs: T,
79        params: OpBehaviorParams<T>,
80    ) -> T
81    where
82        T: Div<Output = T>,
83        T::Output: Eq + Ord + Into<T>,
84        num::Saturating<T>: Div<Output = num::Saturating<T>>,
85        <num::Saturating<T> as Div>::Output: Eq + Ord + Into<num::Saturating<T>>;
86    fn rem<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
87        lhs: T,
88        rhs: T,
89        params: OpBehaviorParams<T>,
90    ) -> T
91    where
92        T: Rem<Output = T> + Sub<Output = T>,
93        <T as Rem>::Output: Eq + Ord + Into<T>,
94        <T as Sub>::Output: Eq + Ord + Into<T>,
95        num::Saturating<T>: Rem<Output = num::Saturating<T>>,
96        <num::Saturating<T> as Rem>::Output: Eq + Ord + Into<num::Saturating<T>>;
97    fn bitand<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
98        lhs: T,
99        rhs: T,
100        params: OpBehaviorParams<T>,
101    ) -> T
102    where
103        T: BitAnd<Output = T> + Sub<Output = T>,
104        <T as BitAnd>::Output: Eq + Ord + Into<T>,
105        <T as Sub>::Output: Eq + Ord + Into<T>,
106        num::Saturating<T>: BitAnd<Output = num::Saturating<T>>,
107        <num::Saturating<T> as BitAnd>::Output: Eq + Ord + Into<num::Saturating<T>>;
108    fn bitor<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
109        lhs: T,
110        rhs: T,
111        params: OpBehaviorParams<T>,
112    ) -> T
113    where
114        T: BitOr<Output = T> + Sub<Output = T>,
115        <T as BitOr>::Output: Eq + Ord + Into<T>,
116        <T as Sub>::Output: Eq + Ord + Into<T>,
117        num::Saturating<T>: BitOr<Output = num::Saturating<T>>,
118        <num::Saturating<T> as BitOr>::Output: Eq + Ord + Into<num::Saturating<T>>;
119    fn bitxor<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
120        lhs: T,
121        rhs: T,
122        params: OpBehaviorParams<T>,
123    ) -> T
124    where
125        T: BitXor<Output = T> + Sub<Output = T>,
126        <T as BitXor>::Output: Eq + Ord + Into<T>,
127        <T as Sub>::Output: Eq + Ord + Into<T>,
128        num::Saturating<T>: BitXor<Output = num::Saturating<T>>,
129        <num::Saturating<T> as BitXor>::Output: Eq + Ord + Into<num::Saturating<T>>;
130    fn neg<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
131        val: T,
132        params: OpBehaviorParams<T>,
133    ) -> T
134    where
135        T: Neg<Output = T> + Sub<Output = T>,
136        <T as Neg>::Output: Eq + Ord + Into<T>,
137        <T as Sub>::Output: Eq + Ord + Into<T>,
138        num::Saturating<T>: Neg<Output = num::Saturating<T>>,
139        <num::Saturating<T> as Neg>::Output: Eq + Ord + Into<num::Saturating<T>>;
140    fn not<T: 'static + Copy + Eq + Ord + InherentLimits<T>>(
141        val: T,
142        params: OpBehaviorParams<T>,
143    ) -> T
144    where
145        T: Not<Output = T> + Sub<Output = T>,
146        <T as Not>::Output: Eq + Ord + Into<T>,
147        <T as Sub>::Output: Eq + Ord + Into<T>,
148        num::Saturating<T>: Not<Output = num::Saturating<T>>,
149        <num::Saturating<T> as Not>::Output: Eq + Ord + Into<num::Saturating<T>>;
150}
151
152pub trait InherentLimits<T>: 'static {
153    const MIN: Self;
154    const MAX: Self;
155    const MIN_INT: T;
156    const MAX_INT: T;
157
158    fn is_zero(&self) -> bool;
159    fn is_negative(&self) -> bool;
160    fn is_positive(&self) -> bool;
161}
162
163pub trait InherentBehavior: 'static {
164    type Behavior: Behavior;
165}
166
167#[cfg(test)]
168mod tests {
169    use crate::prelude::*;
170
171    clamped! {
172        #[usize; derive(Debug)]
173        enum DoubleSentinel {
174            Zero(0),
175            Valid(..),
176            Invalid(usize::MAX),
177        }
178    }
179
180    #[test]
181    fn test_enum_simple() {
182        let value = DoubleSentinel::new(0);
183
184        assert!(value.is_some());
185
186        let mut value = value.unwrap();
187
188        value += 1;
189
190        assert_eq!(value, 1);
191        assert!(value.is_valid());
192
193        value -= 1;
194        assert!(value.is_zero());
195
196        value += usize::MAX;
197        assert!(value.is_invalid());
198    }
199
200    clamped! {
201        #[isize; derive(Debug)]
202        enum SignedNumbers {
203            Min(isize::MIN),
204            Neg(..0),
205            Zero(0),
206            Pos(0..),
207            Max(isize::MAX),
208        }
209    }
210
211    // #[test]
212    // fn test_enum_non_comprehensive() {
213    //     clamped! {
214    //         #[usize]
215    //         enum TenTwentyThirty {
216    //             Ten(10),
217    //             Twenty(20),
218    //             Thirty(30),
219    //         }
220    //     }
221    // }
222
223    // #[test]
224    // fn test_enum_multiple_exacts() {
225    //     clamped! {
226    //         #[usize]
227    //         enum SpecificValues {
228    //             OneTwoOrSeven(1, 2, 7),
229    //             AnythingElse(..),
230    //         }
231    //     }
232    // }
233
234    // #[test]
235    // fn test_enum_multiple_ranges() {
236    //     clamped! {
237    //         #[usize]
238    //         enum HundredToThousand {
239    //             Valid(..),
240    //             Invalid(..100, 1000..)
241    //         }
242    //     }
243    // }
244
245    clamped! {
246        #[usize]
247        enum ResponseCode {
248            Success[200..300] {
249                Okay(200),
250                Created(201),
251                Accepted(202),
252                Unknown(..),
253            },
254            Error {
255                Client[400..500] {
256                    BadRequest(400),
257                    Unauthorized(401),
258                    PaymentRequired(402),
259                    Forbidden(403),
260                    NotFound(404),
261                    Unknown(..)
262                },
263                Server[500..600] {
264                    Internal(500),
265                    NotImplemented(501),
266                    BadGateway(502),
267                    ServiceUnavailable(503),
268                    GatewayTimeout(504),
269                    Unknown(..)
270                }
271            }
272        }
273    }
274
275    #[test]
276    fn test_enum_nested() {}
277
278    // #[test]
279    // fn test_struct_soft() {
280    //     clamped! {
281    //         #[usize as Soft]
282    //         struct TenOrLess(..=10);
283    //     }
284    // }
285
286    clamped! {
287        #[usize as Hard; derive(Debug)]
288        struct TenOrMore(10..);
289    }
290
291    #[test]
292    fn test_struct_hard() {
293        let value = TenOrMore::new(10);
294
295        assert!(value.is_some());
296
297        let mut value = value.unwrap();
298
299        value += 1;
300
301        assert_eq!(value, 11);
302    }
303
304    #[test]
305    #[should_panic]
306    fn test_struct_hard_overflow() {
307        let value = TenOrMore::new(10);
308
309        assert!(value.is_some());
310
311        let mut value = value.unwrap();
312
313        value -= 1;
314    }
315
316    clamped! {
317        #[usize as Hard; derive(Debug)]
318        struct LessThanTenOrBetween999And2000(..10, 1000..2000);
319    }
320
321    #[test]
322    fn test_struct_multiple_ranges() {
323        let value = LessThanTenOrBetween999And2000::new(5);
324
325        assert!(value.is_some());
326
327        let mut value = value.unwrap();
328
329        value += 3;
330
331        assert_eq!(value, 8);
332
333        value += 1000;
334
335        assert_eq!(value, 1008);
336    }
337}