swift_check/
require.rs

1//! Ensure each requirement was met
2//!
3//! **Note**:
4//! This module is experimental, other parts of this crate for the most part should have a stable
5//! api, this is an exception.
6//!
7//! The idea behind this part of the crate is you can define requirements once and reuse them
8//! across your project. A requirement is a higher-level concept than a condition in the context
9//! of this crate. Unlike the condition combinatorics `swift-check` exposes, requirements must have
10//! been fulfilled at least once in the input, or they raise the error associated with them. This is
11//! valuable for things like specifying password rules in a highly maintainable fashion.
12//!
13//! # Example
14//!
15//! ```
16//! use swift_check::{require::{Requirement, check}, requirement, requirements, eq};
17//!
18//! enum MyError {
19//!     Space,
20//!     Seven
21//! }
22//!
23//! requirement!(
24//!     /// The input must include a space
25//!     pub space => eq(b' ') =>! MyError::Space
26//! );
27//! requirement!(
28//!     /// The input must include a seven
29//!     pub seven => eq(b'7') =>! MyError::Seven
30//! );
31//!
32//! // (you can use any condition exposed by swift-check, not just eq)
33//!
34//! // now check if all the requirements were fulfilled, and that each byte in the input fell into
35//! // at least one requirement (for validation)
36//! let (valid, res) = check(
37//!     b"example input 7",
38//!     // as long as each requirement's error implements `Into<MyError>` you can use it.
39//!     requirements!(MyError, [space, seven])
40//! ).result(); // fails fast, there's also `results()` which allows you to iterate over the results
41//!
42//! // not all the example input was 7 or space, so valid is false
43//! assert!(!valid);
44//!
45//! // there was a space and 7 in the input so the res is Ok
46//! assert!(res.is_ok())
47//! ```
48//!
49//! # Performance
50//!
51//! This is the slowest part of the api due to the added complexity to facilitate the desired
52//! functionality. It is not slow, but it is not swift, there's a lot of room for optimization so if
53//! `require` remains a part of the api it will overtime become more and more performant.
54
55use crate::arch;
56use crate::arch::{Vector, MoveMask};
57
58/// The default error type meant for when you're feeling lazy.
59#[repr(transparent)]
60pub struct ErrMsg {
61    pub msg: &'static str
62}
63
64impl ErrMsg {
65    #[inline] #[must_use]
66    pub const fn new(msg: &'static str) -> Self {
67        Self { msg }
68    }
69}
70
71impl From<&'static str> for ErrMsg {
72    #[inline]
73    fn from(value: &'static str) -> Self {
74        Self::new(value)
75    }
76}
77
78impl core::fmt::Display for ErrMsg {
79    #[inline]
80    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
81        f.write_str(self.msg)
82    }
83}
84
85impl core::fmt::Debug for ErrMsg {
86    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
87        write!(f, "Unsatisfied Requirement: {}", self.msg)
88    }
89}
90
91impl core::ops::Deref for ErrMsg {
92    type Target = str;
93
94    #[inline]
95    fn deref(&self) -> &Self::Target {
96        self.msg
97    }
98}
99
100#[cfg(feature = "std")]
101impl std::error::Error for ErrMsg {}
102
103/// A `Condition` is used internally by the [`Requirement`] trait. `requirement!` will expand into
104/// a constant function which returns a type that implements this trait.
105pub trait Condition {
106    /// The error type associated with the requirement. When used in `requirements` all the error
107    /// types must implement `Into` to a common type.
108    type Error;
109    /// Used internally by the `requirements!` macro.
110    #[must_use]
111    fn check(&mut self, vector: Vector) -> MoveMask;
112    /// Check that the condition was met at least once, used internally by the `requirements!` macro
113    fn ok(self) -> Result<(), Self::Error>;
114}
115
116/// A trait representing a collection of conditions which must be met at least once.
117///
118/// # Methods
119///
120/// * [`result`] - Check if all requirements were met, fails fast
121/// * [`results`] - Iterate over each requirement's result
122///
123
124/// [`result`]: Requirement::result
125/// [`results`]: Requirement::results
126pub trait Requirement {
127    /// The common error of each `requirement!`, each error associated with the requirement should
128    /// either be this error type or implement `Into` for this error type.
129    type Error;
130    /// Used internally by the [`check`] function
131    fn check(&mut self, vector: Vector);
132    /// Used internally by the [`check`] function
133    ///
134    /// This operates similarly to `Requirement::check` but for handling partial loads.
135    ///
136    /// The reason this is necessary is that partial loads pad the register with zeroes to ensure
137    /// safety, if these zeroes to not meet any requirement then the validator would consider there
138    /// to be illegal input and flag it as such.
139    ///
140    /// # Arguments
141    ///
142    /// * `vector` - The vector to check.
143    /// * `len`    - The length of the data so that the validator knows what to check.
144    fn check_partial(&mut self, vector: Vector, len: u32);
145    /// # Result
146    ///
147    /// Get the result of the requirement check. This will return the first error caught in order
148    /// of the requirements. If you need to know each unfulfilled requirement see [`results`]
149    ///
150    /// # Returns
151    ///
152    /// 0. A `bool` denoting if all bytes met at least one of the requirements.
153    /// 1. `Ok(())` if all requirements were met, or the first error (in order of requirements)
154    ///    caught.
155    ///
156
157    /// [`results`]: Requirement::results
158    fn result(self) -> (bool, Result<(), Self::Error>);
159    /// # Results
160    ///
161    /// Get an iterator over each requirement result. If you only need to know if the
162    /// requirements were fulfilled see [`result`].
163    ///
164    /// # Returns
165    ///
166    /// 0. A `bool` denoting if all bytes met at least one of the requirements.
167    /// 1. An iterator over results, in order of the provided requirements.
168    ///
169
170    /// [`result`]: Requirement::result
171    fn results(self) -> (bool, impl Iterator<Item = Result<(), Self::Error>>);
172}
173
174/// `Requires` is the final representation of a requirement, usable in the `requirements!` macro.
175///
176/// When you use the `requirement!` macro it will expand into a constant function which returns
177/// a `Requires` instance.
178///
179/// # Generics
180///
181/// - `C`: The required condition
182/// - `Raise`: If the requirement was not fulfilled this is invoked to raise the corresponding `Err`
183/// - `Err`: The error to `Raise` if the requirement was not fulfilled
184pub struct Requires<C, Raise, Err>
185    where
186        C: Fn(Vector) -> Vector,
187        Raise: FnOnce() -> Err
188{
189    /// The required condition
190    pub cond: C,
191    /// Raise the error if the requirement was not met
192    raise: Raise,
193    /// Track if the requirement has been met
194    seen: bool
195}
196
197impl<C, Raise, Err> Requires<C, Raise, Err>
198    where
199        C: Fn(Vector) -> Vector,
200        Raise: FnOnce() -> Err
201{
202    /// Create a new `Requires` instance
203    #[inline] #[must_use]
204    pub const fn new(cond: C, raise: Raise) -> Self {
205        Self { cond, raise, seen: false }
206    }
207}
208
209impl<C, Raise, Err> Condition for Requires<C, Raise, Err>
210    where
211        C: Fn(Vector) -> Vector,
212        Raise: FnOnce() -> Err
213{
214    type Error = Err;
215
216    /// Compute the condition over the vector, if any bit was set update seen to true.
217    ///
218    /// # Returns
219    ///
220    /// The `MoveMask` used to extract the condition result, used to check validity of the input,
221    /// ensuring each byte fulfilled at least one condition.
222    #[inline] #[must_use]
223    fn check(&mut self, vector: Vector) -> MoveMask {
224        let mask = unsafe { MoveMask::new((self.cond)(vector)) };
225        self.seen |= mask.any_bit_set();
226        mask
227    }
228
229    #[inline(always)]
230    fn ok(self) -> Result<(), Self::Error> {
231        if self.seen { Ok(()) } else { Err((self.raise)()) }
232    }
233}
234
235/// Define a Requirement
236///
237/// Requirements can easily be composed in the `requirements!` macro, allowing you to create
238/// highly maintainable and robust validators with decent performance.
239///
240/// # Example
241///
242/// ```
243/// use swift_check::{
244///     require::{Requirement, check},
245///     requirement, requirements,
246///     range, eq
247/// };
248///
249/// enum Errors {
250///     Space,
251///     F,
252///     Number,
253/// }
254///
255/// requirement!(
256///     /// The input must include a space
257///     space => eq(b' ') =>! Errors::Space
258/// );
259///
260/// requirement!(
261///     /// The input must include `F`
262///     f => eq(b'f') =>! Errors::F
263/// );
264///
265/// requirement!(
266///     /// The input must include a number
267///     number => range!(b'0'..=b'9') =>! Errors::Number
268/// );
269///
270/// // now we can use each requirement together in the requirements! macro, as long as the errors
271/// // of each requirement impl Into<requirements! error type> then you're good to go
272///
273/// let (valid, res_iter) = check(
274///     b"example input",
275///     requirements!(Errors, [space, f, number])
276/// ).results();
277///
278/// // valid denotes each byte met at least one of the requirements, in this case it should be
279/// // false
280/// assert!(!valid);
281///
282/// for result in res_iter {
283///     match result {
284///         Err(Errors::F) => {/* There was not an F in the input */},
285///         Err(Errors::Number) => {/* There was not a number in the input */},
286///         Err(Errors::Space) => unreachable!("There was a space in the input"),
287///         _ => {}
288///     }
289/// }
290/// ```
291///
292/// Or, if you're feeling lazy you can use literals as your error, these use the [`ErrMsg`] type.
293///
294/// ```
295/// # use swift_check::{
296/// #     require::{Requirement, check},
297/// #     requirement, requirements,
298/// #     range, eq
299/// # };
300/// #
301/// requirement!(pub space => eq(b' ') =>! "There needs to be a space!");
302/// ```
303///
304/// # Syntax
305///
306/// ```txt
307/// #[attributes]
308/// visibility identifier => condition =>! error
309/// ```
310///
311/// **Syntax Limitation**: Right now you cannot use errors within a module, so you must import them.
312#[macro_export]
313macro_rules! requirement {
314    // when implemented as an accumulator it didn't work well with rust analyzer / rust rover
315    (
316        $(#[$attr:meta])*
317        $vis:vis $req_name:ident => $cond:expr =>! $error_message:literal
318    ) => {
319        $(#[$attr])*
320        #[must_use]
321        $vis const fn $req_name () -> impl $crate::require::Condition<Error = $crate::require::ErrMsg> {
322            let res = $crate::require::Requires::new($cond, || { $crate::require::ErrMsg::new($error_message) });
323            res
324        }
325    };
326    (
327        $(#[$attr:meta])*
328        $vis:vis $req_name:ident => $cond:expr =>! $create_err:expr => $err_ty:ty
329    ) => {
330        $(#[$attr])*
331        #[must_use]
332        $vis const fn $req_name () -> impl $crate::require::Condition<Error = $err_ty> {
333            let res = $crate::require::Requires::new($cond, || { $create_err });
334            res
335        }
336    };
337    (
338        $(#[$attr:meta])*
339        $vis:vis $req_name:ident => $cond:expr =>! $err:ident ($($args:expr),* $(,)?)
340    ) => {
341        $(#[$attr])*
342        #[must_use]
343        $vis const fn $req_name () -> impl $crate::require::Condition<Error = $err> {
344            let res = $crate::require::Requires::new($cond, || { $err ($($args),*) });
345            res
346        }
347    };
348    (
349        $(#[$attr:meta])*
350        $vis:vis $req_name:ident => $cond:expr =>! $err:ident :: $func:ident ($($args:expr),* $(,)?)
351    ) => {
352        $(#[$attr])*
353        #[must_use]
354        $vis const fn $req_name () -> impl $crate::require::Condition<Error = $err> {
355            let res = $crate::require::Requires::new($cond, || { $err :: $func ($($args),*) });
356            res
357        }
358    };
359    (
360        $(#[$attr:meta])*
361        $vis:vis $req_name:ident => $cond:expr =>! $err:ident :: $variant:ident
362    ) => {
363        $(#[$attr])*
364        #[must_use]
365        $vis const fn $req_name () -> impl $crate::require::Condition<Error = $err> {
366            let res = $crate::require::Requires::new($cond, || { $err :: $variant });
367            res
368        }
369    };
370}
371
372/// Check multiple `requirement!`s
373///
374/// # Example
375///
376/// ```
377/// use swift_check::{
378///     require::{Requirement, check},
379///     requirement, requirements,
380///     eq
381/// };
382///
383/// requirement!(pub space => eq(b' ') =>! "There needs to be a space!");
384/// requirement!(pub seven => eq(b'7') =>! "There needs to be a seven!");
385///
386/// let (valid, res) = check(
387///     b"example input 7",
388///     requirements!([space, seven])
389/// ).results();
390///
391/// // The first element of result denotes if all bytes met at least one of the conditions, in this
392/// // case this is false.
393/// assert!(!valid);
394///
395/// // the res is an iterator over each requirement! result. You also can use `result` instead of
396/// // `results` if you only need to know all requirements were met and care less about granular
397/// // error reporting.
398/// ```
399///
400/// # Error Handling
401///
402/// The individual requirements do not need to share the same error type, but they do all need to
403/// implement either `From` or `Into` the `requirements!` error type.
404///
405/// # Syntax
406///
407/// ```txt
408/// Error Type, [Requirements, ...]
409/// ```
410///
411/// or for when you're feeling lazy, the default error is `ErrMsg`, the above example uses this.
412///
413/// ```txt
414/// [Requirements, ...]
415/// ```
416#[macro_export]
417macro_rules! requirements {
418    ([$($requirement:ident),* $(,)?] $(,)?) => {
419        $crate::requirements!($crate::require::ErrMsg, [$($requirement),*])
420    };
421    ($error:ty, [$($requirement:ident),* $(,)?] $(,)?) => {{
422        #[allow(non_camel_case_types)]
423        struct Requirements<$($requirement: $crate::require::Condition),*> {
424            __valid: bool,
425            $($requirement: $requirement),*
426        }
427        #[allow(non_camel_case_types)]
428        impl<$($requirement),*> $crate::require::Requirement for Requirements<$($requirement),*>
429            where
430                $($requirement: $crate::require::Condition,
431                <$requirement as $crate::require::Condition>::Error: Into<$error>),*
432        {
433            type Error = $error;
434            #[inline]
435            fn check(&mut self, vector: $crate::arch::Vector) {
436                #[allow(unused_imports)]
437                use $crate::require::Condition as _;
438                self.__valid &= ($(self.$requirement.check(vector) )|*).all_bits_set();
439            }
440            #[inline]
441            fn check_partial(&mut self, vector: $crate::arch::Vector, len: u32) {
442                #[allow(unused_imports)]
443                use $crate::require::Condition as _;
444                self.__valid &= ($(self.$requirement.check(vector) )|*)
445                    .trailing_ones() >= len;
446            }
447            #[inline]
448            fn result(self) -> (bool, Result<(), Self::Error>) {
449                $(
450                    if let Err(err) = self.$requirement.ok() {
451                        return (self.__valid, Err(err.into()));
452                    };
453                )*
454                (self.__valid, Ok(()))
455            }
456            #[inline]
457            fn results(self) -> (bool, impl Iterator<Item=Result<(), Self::Error>>) {
458                (
459                    self.__valid,
460                    [$(self.$requirement.ok().map_err(|err| -> Self::Error {err.into()})),*]
461                        .into_iter()
462                )
463            }
464        }
465
466        Requirements {
467            __valid: true,
468            $($requirement: $requirement ()),*
469        }
470    }};
471}
472
473/// Check that all `requirement!`s are fulfilled
474///
475/// # Arguments
476///
477/// * `data` - The data to validate
478/// * `req` - The requirements to check against
479///
480/// # Result
481///
482/// An implementor of [`Requirement`] at the final state
483///
484/// # Example
485///
486/// ```
487/// use swift_check::{
488///     require::{Requirement, check},
489///     requirement, requirements,
490///     eq
491/// };
492///
493/// requirement!(pub space => eq(b' ') =>! "There needs to be a space!");
494/// requirement!(pub seven => eq(b'7') =>! "There needs to be a seven!");
495///
496/// let (valid, res) = check(
497///     b"example input 7",
498///     requirements!([space, seven])
499/// ).results();
500///
501/// // The first element of result denotes if all bytes met at least one of the conditions, in this
502/// // case this is false.
503/// assert!(!valid);
504/// ```
505#[inline]
506pub fn check<R: Requirement>(data: &[u8], mut req: R) -> R {
507    if data.len() >= arch::WIDTH {
508        unsafe { arch::scan::ensure_requirements(data, req) }
509    } else {
510        let len = data.len();
511        req.check_partial(unsafe { arch::load_partial(data, len) }, len as u32);
512        req
513    }
514}
515
516#[cfg(test)]
517#[test]
518fn test() {
519    use crate::{eq, range};
520    struct SpecialError(&'static str);
521    impl From<SpecialError> for ErrMsg {
522        fn from(value: SpecialError) -> Self {
523            ErrMsg::new(value.0)
524        }
525    }
526    impl SpecialError {
527        fn new(msg: &'static str) -> Self {
528            Self ( msg )
529        }
530    }
531
532    // no std, no alloc, requirement checks / validator with SIMD on aarch64, x86, and WASM32
533
534    // if you're lazy you can use literals as your error
535    requirement!(
536        /// There should be at least one uppercase letter
537        #[inline] pub uppercase => range!(b'A'..=b'Z') =>! "needs uppercase!"
538    );
539    requirement!(
540        /// There should be at least one lowercase letter
541        #[inline] pub lowercase => range!(b'a'..=b'z') =>! "needs lowercase!"
542    );
543    // you can use multiple different error types as long as they impl From to the requirements! err
544    requirement!(
545        /// There needs to be at least one number
546        #[inline] pub numeric => range!(b'0'..=b'9') =>! SpecialError("needs number!")
547    );
548    requirement!(
549        /// Question marks are a requirement
550        #[inline] pub question_mark => eq(b'?') =>! SpecialError::new("needs a question mark!")
551    );
552
553    let res = check(
554        b"hello world 12345678910",
555        requirements!([uppercase, lowercase, numeric, question_mark])
556    );
557
558    // you can iterate through the errors
559    for res in res.results().1 {
560        if let Err(err) = res {
561            println!("{err:?}");
562        }
563    }
564
565    let res = check(
566        b"hello world 12345678910",
567        requirements!([uppercase, lowercase, numeric, question_mark])
568    );
569
570    // or if you just want to know if an err took place you can use ok
571    println!("{:?}", res.result().1.unwrap_err());
572}