Skip to main content

options/prelude/builder/
validate.rs

1use super::{names_equal, Builder};
2use crate::{
3    validation::{self, Validate},
4    Ref, Value,
5};
6use cfg_if::cfg_if;
7use di::{transient_factory, Ref as Svc};
8use std::marker::PhantomData;
9
10fn message_or_default<T: AsRef<str>>(message: T) -> String {
11    let msg = message.as_ref();
12
13    if msg.is_empty() {
14        String::from("A validation error has occurred.")
15    } else {
16        String::from(msg)
17    }
18}
19
20macro_rules! validate_impl {
21    // base case: zero dependencies
22    (($($bounds:tt)+), _Validate, validate, []) => {
23        struct _Validate<TOptions, TAction> {
24            name: String,
25            action: Ref<TAction>,
26            failure_message: String,
27            _type: PhantomData<TOptions>,
28        }
29
30        impl<TOptions, TAction> Validate<TOptions> for _Validate<TOptions, TAction>
31        where
32            TOptions: Value,
33            TAction: Fn(&TOptions) -> bool + $($bounds)+,
34        {
35            fn run(&self, name: &str, options: &TOptions) -> validation::Result {
36                if names_equal(&self.name, name) {
37                    if (self.action)(options) {
38                        return validation::success();
39                    } else {
40                        return validation::fail(&self.failure_message);
41                    }
42                }
43
44                validation::skip()
45            }
46        }
47    };
48    // n-dependency case
49    (($($bounds:tt)+), $struct_name:ident, $method:ident, [$(($dep_generic:ident, $dep_field:ident)),+]) => {
50        struct $struct_name<TOptions, TAction, $($dep_generic),+> {
51            name: String,
52            action: Ref<TAction>,
53            failure_message: String,
54            $($dep_field: Svc<$dep_generic>,)+
55            _type: PhantomData<TOptions>,
56        }
57
58        impl<TOptions, TAction, $($dep_generic),+> Validate<TOptions>
59            for $struct_name<TOptions, TAction, $($dep_generic),+>
60        where
61            TOptions: Value,
62            TAction: Fn(&TOptions, $(Svc<$dep_generic>),+) -> bool + $($bounds)+,
63            $($dep_generic: Value,)+
64        {
65            fn run(&self, name: &str, options: &TOptions) -> validation::Result {
66                if names_equal(&self.name, name) {
67                    if (self.action)(options, $(self.$dep_field.clone()),+) {
68                        return validation::success();
69                    } else {
70                        return validation::fail(&self.failure_message);
71                    }
72                }
73
74                validation::skip()
75            }
76        }
77    };
78}
79
80// generates a builder method for validate with n-dependencies. each dep is passed as a separate group to avoid
81// repetition depth mismatch with bounds.
82macro_rules! validate_builder_method {
83    (($($bounds:tt)+), $method:ident, $struct_name:ident, ($d1:ident, $f1:ident)) => {
84        /// Registers an action used to validate a particular type of options.
85        ///
86        /// # Arguments
87        ///
88        /// * `action` - The validation action
89        /// * `failure_message` - The message used when validation fails
90        pub fn $method<F, M, $d1>(self, action: F, failure_message: M) -> Self
91        where
92            F: Fn(&T, Svc<$d1>) -> bool + $($bounds)+,
93            M: AsRef<str>,
94            $d1: $($bounds)+,
95        {
96            let action = Ref::new(action);
97            let name = self.name.clone();
98            let failure_message = message_or_default(failure_message);
99
100            self.services.add(transient_factory(move |sp| {
101                let validate: Ref<dyn Validate<T>> = Ref::new($struct_name {
102                    name: name.clone(),
103                    action: action.clone(),
104                    failure_message: failure_message.clone(),
105                    $f1: sp.get_required::<$d1>(),
106                    _type: PhantomData,
107                });
108                validate
109            }));
110
111            self
112        }
113    };
114    (($($bounds:tt)+), $method:ident, $struct_name:ident, ($d1:ident, $f1:ident), ($d2:ident, $f2:ident)) => {
115        /// Registers an action used to validate a particular type of options.
116        ///
117        /// # Arguments
118        ///
119        /// * `action` - The validation action
120        /// * `failure_message` - The message used when validation fails
121        pub fn $method<F, M, $d1, $d2>(self, action: F, failure_message: M) -> Self
122        where
123            F: Fn(&T, Svc<$d1>, Svc<$d2>) -> bool + $($bounds)+,
124            M: AsRef<str>,
125            $d1: $($bounds)+,
126            $d2: $($bounds)+,
127        {
128            let action = Ref::new(action);
129            let name = self.name.clone();
130            let failure_message = message_or_default(failure_message);
131
132            self.services.add(transient_factory(move |sp| {
133                let validate: Ref<dyn Validate<T>> = Ref::new($struct_name {
134                    name: name.clone(),
135                    action: action.clone(),
136                    failure_message: failure_message.clone(),
137                    $f1: sp.get_required::<$d1>(),
138                    $f2: sp.get_required::<$d2>(),
139                    _type: PhantomData,
140                });
141                validate
142            }));
143
144            self
145        }
146    };
147    (($($bounds:tt)+), $method:ident, $struct_name:ident, ($d1:ident, $f1:ident), ($d2:ident, $f2:ident), ($d3:ident, $f3:ident)) => {
148        /// Registers an action used to validate a particular type of options.
149        ///
150        /// # Arguments
151        ///
152        /// * `action` - The validation action
153        /// * `failure_message` - The message used when validation fails
154        pub fn $method<F, M, $d1, $d2, $d3>(self, action: F, failure_message: M) -> Self
155        where
156            F: Fn(&T, Svc<$d1>, Svc<$d2>, Svc<$d3>) -> bool + $($bounds)+,
157            M: AsRef<str>,
158            $d1: $($bounds)+,
159            $d2: $($bounds)+,
160            $d3: $($bounds)+,
161        {
162            let action = Ref::new(action);
163            let name = self.name.clone();
164            let failure_message = message_or_default(failure_message);
165
166            self.services.add(transient_factory(move |sp| {
167                let validate: Ref<dyn Validate<T>> = Ref::new($struct_name {
168                    name: name.clone(),
169                    action: action.clone(),
170                    failure_message: failure_message.clone(),
171                    $f1: sp.get_required::<$d1>(),
172                    $f2: sp.get_required::<$d2>(),
173                    $f3: sp.get_required::<$d3>(),
174                    _type: PhantomData,
175                });
176                validate
177            }));
178
179            self
180        }
181    };
182    (($($bounds:tt)+), $method:ident, $struct_name:ident, ($d1:ident, $f1:ident), ($d2:ident, $f2:ident), ($d3:ident, $f3:ident), ($d4:ident, $f4:ident)) => {
183        /// Registers an action used to validate a particular type of options.
184        ///
185        /// # Arguments
186        ///
187        /// * `action` - The validation action
188        /// * `failure_message` - The message used when validation fails
189        pub fn $method<F, M, $d1, $d2, $d3, $d4>(self, action: F, failure_message: M) -> Self
190        where
191            F: Fn(&T, Svc<$d1>, Svc<$d2>, Svc<$d3>, Svc<$d4>) -> bool + $($bounds)+,
192            M: AsRef<str>,
193            $d1: $($bounds)+,
194            $d2: $($bounds)+,
195            $d3: $($bounds)+,
196            $d4: $($bounds)+,
197        {
198            let action = Ref::new(action);
199            let name = self.name.clone();
200            let failure_message = message_or_default(failure_message);
201
202            self.services.add(transient_factory(move |sp| {
203                let validate: Ref<dyn Validate<T>> = Ref::new($struct_name {
204                    name: name.clone(),
205                    action: action.clone(),
206                    failure_message: failure_message.clone(),
207                    $f1: sp.get_required::<$d1>(),
208                    $f2: sp.get_required::<$d2>(),
209                    $f3: sp.get_required::<$d3>(),
210                    $f4: sp.get_required::<$d4>(),
211                    _type: PhantomData,
212                });
213                validate
214            }));
215
216            self
217        }
218    };
219    (($($bounds:tt)+), $method:ident, $struct_name:ident, ($d1:ident, $f1:ident), ($d2:ident, $f2:ident), ($d3:ident, $f3:ident), ($d4:ident, $f4:ident), ($d5:ident, $f5:ident)) => {
220        /// Registers an action used to validate a particular type of options.
221        ///
222        /// # Arguments
223        ///
224        /// * `action` - The validation action
225        /// * `failure_message` - The message used when validation fails
226        pub fn $method<F, M, $d1, $d2, $d3, $d4, $d5>(self, action: F, failure_message: M) -> Self
227        where
228            F: Fn(&T, Svc<$d1>, Svc<$d2>, Svc<$d3>, Svc<$d4>, Svc<$d5>) -> bool + $($bounds)+,
229            M: AsRef<str>,
230            $d1: $($bounds)+,
231            $d2: $($bounds)+,
232            $d3: $($bounds)+,
233            $d4: $($bounds)+,
234            $d5: $($bounds)+,
235        {
236            let action = Ref::new(action);
237            let name = self.name.clone();
238            let failure_message = message_or_default(failure_message);
239
240            self.services.add(transient_factory(move |sp| {
241                let validate: Ref<dyn Validate<T>> = Ref::new($struct_name {
242                    name: name.clone(),
243                    action: action.clone(),
244                    failure_message: failure_message.clone(),
245                    $f1: sp.get_required::<$d1>(),
246                    $f2: sp.get_required::<$d2>(),
247                    $f3: sp.get_required::<$d3>(),
248                    $f4: sp.get_required::<$d4>(),
249                    $f5: sp.get_required::<$d5>(),
250                    _type: PhantomData,
251                });
252                validate
253            }));
254
255            self
256        }
257    };
258}
259
260macro_rules! validate_methods {
261    (($($bounds:tt)+)) => {
262        impl<'a, T: $($bounds)+> Builder<'a, T> {
263            /// Registers an action used to validate a particular type of options.
264            ///
265            /// # Arguments
266            ///
267            /// * `action` - The validation action
268            /// * `failure_message` - The message used when validation fails
269            pub fn validate<F, M>(self, action: F, failure_message: M) -> Self
270            where
271                F: Fn(&T) -> bool + $($bounds)+,
272                M: AsRef<str>,
273            {
274                let action = Ref::new(action);
275                let name = self.name.clone();
276                let failure_message = message_or_default(failure_message);
277
278                self.services.add(transient_factory(move |_| {
279                    let validate: Ref<dyn Validate<T>> = Ref::new(_Validate {
280                        name: name.clone(),
281                        action: action.clone(),
282                        failure_message: failure_message.clone(),
283                        _type: PhantomData,
284                    });
285                    validate
286                }));
287
288                self
289            }
290
291            validate_builder_method!(($($bounds)+), validate1, _Validate1, (D, dependency));
292            validate_builder_method!(($($bounds)+), validate2, _Validate2, (D1, dependency1), (D2, dependency2));
293            validate_builder_method!(($($bounds)+), validate3, _Validate3, (D1, dependency1), (D2, dependency2), (D3, dependency3));
294            validate_builder_method!(($($bounds)+), validate4, _Validate4, (D1, dependency1), (D2, dependency2), (D3, dependency3), (D4, dependency4));
295            validate_builder_method!(($($bounds)+), validate5, _Validate5, (D1, dependency1), (D2, dependency2), (D3, dependency3), (D4, dependency4), (D5, dependency5));
296        }
297    };
298}
299
300cfg_if! {
301    if #[cfg(feature = "async")] {
302        validate_impl!((Send + Sync + 'static), _Validate, validate, []);
303        validate_impl!((Send + Sync + 'static), _Validate1, validate1, [(TDep1, dependency)]);
304        validate_impl!((Send + Sync + 'static), _Validate2, validate2, [(TDep1, dependency1), (TDep2, dependency2)]);
305        validate_impl!((Send + Sync + 'static), _Validate3, validate3, [(TDep1, dependency1), (TDep2, dependency2), (TDep3, dependency3)]);
306        validate_impl!((Send + Sync + 'static), _Validate4, validate4, [(TDep1, dependency1), (TDep2, dependency2), (TDep3, dependency3), (TDep4, dependency4)]);
307        validate_impl!((Send + Sync + 'static), _Validate5, validate5, [(TDep1, dependency1), (TDep2, dependency2), (TDep3, dependency3), (TDep4, dependency4), (TDep5, dependency5)]);
308        validate_methods!((Send + Sync + 'static));
309    } else {
310        validate_impl!(('static), _Validate, validate, []);
311        validate_impl!(('static), _Validate1, validate1, [(TDep1, dependency)]);
312        validate_impl!(('static), _Validate2, validate2, [(TDep1, dependency1), (TDep2, dependency2)]);
313        validate_impl!(('static), _Validate3, validate3, [(TDep1, dependency1), (TDep2, dependency2), (TDep3, dependency3)]);
314        validate_impl!(('static), _Validate4, validate4, [(TDep1, dependency1), (TDep2, dependency2), (TDep3, dependency3), (TDep4, dependency4)]);
315        validate_impl!(('static), _Validate5, validate5, [(TDep1, dependency1), (TDep2, dependency2), (TDep3, dependency3), (TDep4, dependency4), (TDep5, dependency5)]);
316        validate_methods!(('static));
317    }
318}