Skip to main content

options/
di_ext.rs

1use crate::*;
2use cfg_if::cfg_if;
3use di::{
4    exactly_one, scoped, singleton, singleton_as_self, transient, transient_factory, zero_or_more, ServiceCollection,
5    ServiceDescriptor, ServiceProvider,
6};
7
8macro_rules! opts_ext {
9    (($($bounds:tt)+)) => {
10        /// Defines extension methods for the [`ServiceCollection`](di::ServiceCollection) struct.
11        pub trait OptionsServiceExtensions {
12            /// Registers an options type that will have all of its associated services registered.
13            fn add_options<T: Value + Default + 'static>(&mut self) -> OptionsBuilder<'_, T>;
14
15            /// Registers an options type that will have all of its associated services registered.
16            ///
17            /// # Arguments
18            ///
19            /// * `name` - The name associated with the options
20            fn add_named_options<T: Value + Default + 'static>(
21                &mut self,
22                name: impl AsRef<str>,
23            ) -> OptionsBuilder<'_, T>;
24
25            /// Registers an options type that will have all of its associated services registered.
26            ///
27            /// # Arguments
28            ///
29            /// * `factory` - The function used to create the associated options factory
30            fn add_options_with<T, F>(&mut self, factory: F) -> OptionsBuilder<'_, T>
31            where
32                T: Value,
33                F: Fn(&ServiceProvider) -> Ref<dyn OptionsFactory<T>> + $($bounds)+;
34
35            /// Registers an options type that will have all of its associated services registered.
36            ///
37            /// # Arguments
38            ///
39            /// * `name` - The name associated with the options
40            /// * `factory` - The function used to create the associated options factory
41            fn add_named_options_with<T, F>(
42                &mut self,
43                name: impl AsRef<str>,
44                factory: F,
45            ) -> OptionsBuilder<'_, T>
46            where
47                T: Value,
48                F: Fn(&ServiceProvider) -> Ref<dyn OptionsFactory<T>> + $($bounds)+;
49
50            /// Registers an action used to initialize a particular type of configuration options.
51            ///
52            /// # Arguments
53            ///
54            /// * `setup` - The setup action used to configure options.
55            fn configure_options<T, F>(&mut self, setup: F) -> &mut Self
56            where
57                T: Value + Default + 'static,
58                F: Fn(&mut T) + $($bounds)+;
59
60            /// Registers an action used to initialize a particular type of configuration options.
61            ///
62            /// # Arguments
63            ///
64            /// * `name` - The name associated with the options
65            /// * `setup` - The setup action used to configure options
66            fn configure_named_options<T, F>(
67                &mut self,
68                name: impl AsRef<str>,
69                setup: F,
70            ) -> &mut Self
71            where
72                T: Value + Default + 'static,
73                F: Fn(&mut T) + $($bounds)+;
74
75            /// Registers an action used to initialize a particular type of configuration options.
76            ///
77            /// # Arguments
78            ///
79            /// * `setup` - The setup action used to configure options
80            fn post_configure_options<T, F>(&mut self, setup: F) -> &mut Self
81            where
82                T: Value + Default + 'static,
83                F: Fn(&mut T) + $($bounds)+;
84
85            /// Registers an action used to initialize a particular type of configuration options.
86            ///
87            /// # Arguments
88            ///
89            /// * `name` - The name associated with the options
90            /// * `setup` - The setup action used to configure options
91            fn post_configure_named_options<T, F>(
92                &mut self,
93                name: impl AsRef<str>,
94                setup: F,
95            ) -> &mut Self
96            where
97                T: Value + Default + 'static,
98                F: Fn(&mut T) + $($bounds)+;
99        }
100    };
101}
102
103fn _add_options<'a, T: Value>(
104    services: &'a mut ServiceCollection,
105    name: Option<&str>,
106    descriptor: ServiceDescriptor,
107) -> OptionsBuilder<'a, T> {
108    services
109        .try_add(
110            singleton_as_self::<OptionsManager<T>>()
111                .depends_on(exactly_one::<dyn OptionsFactory<T>>())
112                .from(|sp| Ref::new(OptionsManager::new(sp.get_required::<dyn OptionsFactory<T>>()))),
113        )
114        .try_add(
115            singleton::<dyn Options<T>, OptionsManager<T>>()
116                .depends_on(exactly_one::<OptionsManager<T>>())
117                .from(|sp| sp.get_required::<OptionsManager<T>>()),
118        )
119        .try_add(
120            scoped::<dyn OptionsSnapshot<T>, OptionsManager<T>>()
121                .depends_on(exactly_one::<OptionsManager<T>>())
122                .from(|sp| sp.get_required::<OptionsManager<T>>()),
123        )
124        .try_add(
125            singleton::<dyn OptionsMonitor<T>, DefaultOptionsMonitor<T>>()
126                .depends_on(exactly_one::<dyn OptionsMonitorCache<T>>())
127                .depends_on(zero_or_more::<dyn OptionsChangeTokenSource<T>>())
128                .depends_on(exactly_one::<dyn OptionsFactory<T>>())
129                .from(|sp| {
130                    Ref::new(DefaultOptionsMonitor::new(
131                        sp.get_required::<dyn OptionsMonitorCache<T>>(),
132                        sp.get_all::<dyn OptionsChangeTokenSource<T>>().collect(),
133                        sp.get_required::<dyn OptionsFactory<T>>(),
134                    ))
135                }),
136        )
137        .try_add(descriptor)
138        .try_add(
139            singleton::<dyn OptionsMonitorCache<T>, OptionsCache<T>>().from(|_| Ref::new(OptionsCache::default())),
140        );
141
142    OptionsBuilder::new(services, name)
143}
144
145macro_rules! opts_ext_impl {
146    (($($bounds:tt)+)) => {
147        impl OptionsServiceExtensions for ServiceCollection {
148            fn add_options<T: Value + Default + 'static>(&mut self) -> OptionsBuilder<'_, T> {
149                let descriptor = transient::<dyn OptionsFactory<T>, DefaultOptionsFactory<T>>()
150                    .depends_on(zero_or_more::<dyn ConfigureOptions<T>>())
151                    .depends_on(zero_or_more::<dyn PostConfigureOptions<T>>())
152                    .depends_on(zero_or_more::<dyn ValidateOptions<T>>())
153                    .from(|sp| {
154                        Ref::new(DefaultOptionsFactory::new(
155                            sp.get_all::<dyn ConfigureOptions<T>>().collect(),
156                            sp.get_all::<dyn PostConfigureOptions<T>>().collect(),
157                            sp.get_all::<dyn ValidateOptions<T>>().collect(),
158                        ))
159                    });
160
161                _add_options(self, None, descriptor)
162            }
163
164            fn add_named_options<T: Value + Default + 'static>(
165                &mut self,
166                name: impl AsRef<str>,
167            ) -> OptionsBuilder<'_, T> {
168                let descriptor = transient::<dyn OptionsFactory<T>, DefaultOptionsFactory<T>>()
169                    .depends_on(zero_or_more::<dyn ConfigureOptions<T>>())
170                    .depends_on(zero_or_more::<dyn PostConfigureOptions<T>>())
171                    .depends_on(zero_or_more::<dyn ValidateOptions<T>>())
172                    .from(|sp| {
173                        Ref::new(DefaultOptionsFactory::new(
174                            sp.get_all::<dyn ConfigureOptions<T>>().collect(),
175                            sp.get_all::<dyn PostConfigureOptions<T>>().collect(),
176                            sp.get_all::<dyn ValidateOptions<T>>().collect(),
177                        ))
178                    });
179
180                _add_options(self, Some(name.as_ref()), descriptor)
181            }
182
183            #[inline]
184            fn add_options_with<T, F>(&mut self, factory: F) -> OptionsBuilder<'_, T>
185            where
186                T: Value,
187                F: Fn(&ServiceProvider) -> Ref<dyn OptionsFactory<T>> + $($bounds)+,
188            {
189                _add_options(self, None, transient_factory(factory))
190            }
191
192            #[inline]
193            fn add_named_options_with<T, F>(
194                &mut self,
195                name: impl AsRef<str>,
196                factory: F,
197            ) -> OptionsBuilder<'_, T>
198            where
199                T: Value,
200                F: Fn(&ServiceProvider) -> Ref<dyn OptionsFactory<T>> + $($bounds)+,
201            {
202                _add_options(self, Some(name.as_ref()), transient_factory(factory))
203            }
204
205            #[inline]
206            fn configure_options<T, F>(&mut self, setup: F) -> &mut Self
207            where
208                T: Value + Default + 'static,
209                F: Fn(&mut T) + $($bounds)+,
210            {
211                self.add_options().configure(setup).into()
212            }
213
214            #[inline]
215            fn configure_named_options<T, F>(
216                &mut self,
217                name: impl AsRef<str>,
218                setup: F,
219            ) -> &mut Self
220            where
221                T: Value + Default + 'static,
222                F: Fn(&mut T) + $($bounds)+,
223            {
224                self.add_named_options(name).configure(setup).into()
225            }
226
227            #[inline]
228            fn post_configure_options<T, F>(&mut self, setup: F) -> &mut Self
229            where
230                T: Value + Default + 'static,
231                F: Fn(&mut T) + $($bounds)+,
232            {
233                self.add_options().post_configure(setup).into()
234            }
235
236            #[inline]
237            fn post_configure_named_options<T, F>(
238                &mut self,
239                name: impl AsRef<str>,
240                setup: F,
241            ) -> &mut Self
242            where
243                T: Value + Default + 'static,
244                F: Fn(&mut T) + $($bounds)+,
245            {
246                self.add_named_options(name).configure(setup).into()
247            }
248        }
249    };
250}
251
252cfg_if! {
253    if #[cfg(feature = "async")] {
254        opts_ext!((Send + Sync + 'static));
255        opts_ext_impl!((Send + Sync + 'static));
256    } else {
257        opts_ext!(('static));
258        opts_ext_impl!(('static));
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use di::{existing_as_self, transient};
266    use std::sync::RwLock;
267
268    #[derive(Default, Debug, PartialEq, Eq)]
269    struct TestOptions {
270        enabled: bool,
271        setting: usize,
272    }
273
274    #[derive(Default)]
275    struct TestValidation;
276
277    impl ValidateOptions<TestOptions> for TestValidation {
278        fn validate(&self, _name: Option<&str>, options: &TestOptions) -> ValidateOptionsResult {
279            if !options.enabled && options.setting > 0 {
280                ValidateOptionsResult::fail("Setting must be zero when disabled")
281            } else {
282                ValidateOptionsResult::success()
283            }
284        }
285    }
286
287    struct TestService {
288        value: RwLock<usize>,
289    }
290
291    impl TestService {
292        fn next(&self) -> usize {
293            let mut value = self.value.write().unwrap();
294            let current = *value;
295            *value += 1;
296            current
297        }
298
299        fn calls(&self) -> usize {
300            *self.value.read().unwrap() - 1
301        }
302    }
303
304    impl Default for TestService {
305        fn default() -> Self {
306            Self { value: RwLock::new(1) }
307        }
308    }
309
310    #[test]
311    fn get_should_resolve_service() {
312        // arrange
313        let provider = ServiceCollection::new()
314            .add_options::<TestOptions>()
315            .build_provider()
316            .unwrap();
317
318        // act
319        let result = provider.get::<dyn Options<TestOptions>>();
320
321        // assert
322        assert!(result.is_some());
323    }
324
325    #[test]
326    fn get_required_should_configure_options() {
327        // arrange
328        let provider = ServiceCollection::new()
329            .configure_options(|o: &mut TestOptions| o.setting = 1)
330            .build_provider()
331            .unwrap();
332
333        // act
334        let options = provider.get_required::<dyn Options<TestOptions>>();
335
336        // assert
337        assert_eq!(options.value().setting, 1);
338    }
339
340    #[test]
341    fn get_required_should_post_configure_options() {
342        // arrange
343        let provider = ServiceCollection::new()
344            .post_configure_options(|o: &mut TestOptions| o.setting = 1)
345            .build_provider()
346            .unwrap();
347
348        // act
349        let options = provider.get_required::<dyn Options<TestOptions>>();
350
351        // assert
352        assert_eq!(options.value().setting, 1);
353    }
354
355    #[test]
356    fn get_required_should_apply_all_configurations() {
357        // arrange
358        let provider = ServiceCollection::new()
359            .configure_options(|o: &mut TestOptions| o.setting = 1)
360            .configure_options(|o: &mut TestOptions| o.enabled = true)
361            .post_configure_options(|o: &mut TestOptions| o.setting = 2)
362            .build_provider()
363            .unwrap();
364
365        // act
366        let result = provider.get_required::<dyn Options<TestOptions>>();
367        let options = result.value();
368
369        // assert
370        assert!(options.enabled);
371        assert_eq!(options.setting, 2);
372    }
373
374    #[test]
375    fn get_required_should_not_panic_when_configured_options_are_valid() {
376        // arrange
377        let provider = ServiceCollection::new()
378            .configure_options(|o: &mut TestOptions| {
379                o.enabled = true;
380                o.setting = 1;
381            })
382            .add(
383                transient::<dyn ValidateOptions<TestOptions>, TestValidation>()
384                    .from(|_| Ref::new(TestValidation::default())),
385            )
386            .build_provider()
387            .unwrap();
388
389        // act
390        let options = provider.get_required::<dyn Options<TestOptions>>();
391
392        // assert
393        let _ = options.value();
394    }
395
396    #[test]
397    #[should_panic(expected = "Setting must be zero when disabled")]
398    fn get_required_should_panic_when_configured_options_are_invalid() {
399        // arrange
400        let provider = ServiceCollection::new()
401            .configure_options(|o: &mut TestOptions| {
402                o.enabled = false;
403                o.setting = 1;
404            })
405            .add(
406                transient::<dyn ValidateOptions<TestOptions>, TestValidation>()
407                    .from(|_| Ref::new(TestValidation::default())),
408            )
409            .build_provider()
410            .unwrap();
411
412        // act
413        let options = provider.get_required::<dyn Options<TestOptions>>();
414
415        // assert
416        let _ = options.value();
417    }
418
419    #[test]
420    fn get_required_should_configure_options_with_1_dependency() {
421        // arrange
422        let provider = ServiceCollection::new()
423            .add_options::<TestOptions>()
424            .configure1(|o, d1: Ref<TestService>| o.setting = d1.next())
425            .add(existing_as_self(TestService::default()))
426            .build_provider()
427            .unwrap();
428
429        // act
430        let options = provider.get_required::<dyn Options<TestOptions>>();
431
432        // assert
433        assert_eq!(options.value().setting, 1);
434    }
435
436    #[test]
437    fn get_required_should_configure_options_with_2_dependencies() {
438        // arrange
439        let provider = ServiceCollection::new()
440            .add_options::<TestOptions>()
441            .configure2(|o, d1: Ref<TestService>, d2: Ref<TestService>| o.setting = d1.next() + d2.next())
442            .add(existing_as_self(TestService::default()))
443            .build_provider()
444            .unwrap();
445
446        // act
447        let options = provider.get_required::<dyn Options<TestOptions>>();
448
449        // assert
450        assert_eq!(options.value().setting, 3);
451    }
452
453    #[test]
454    fn get_required_should_configure_options_with_3_dependencies() {
455        // arrange
456        let provider = ServiceCollection::new()
457            .add_options::<TestOptions>()
458            .configure3(|o, d1: Ref<TestService>, d2: Ref<TestService>, d3: Ref<TestService>| {
459                o.setting = d1.next() + d2.next() + d3.next()
460            })
461            .add(existing_as_self(TestService::default()))
462            .build_provider()
463            .unwrap();
464
465        // act
466        let options = provider.get_required::<dyn Options<TestOptions>>();
467
468        // assert
469        assert_eq!(options.value().setting, 6);
470    }
471
472    #[test]
473    fn get_required_should_configure_options_with_4_dependencies() {
474        // arrange
475        let provider = ServiceCollection::new()
476            .add_options::<TestOptions>()
477            .configure4(
478                |o, d1: Ref<TestService>, d2: Ref<TestService>, d3: Ref<TestService>, d4: Ref<TestService>| {
479                    o.setting = d1.next() + d2.next() + d3.next() + d4.next()
480                },
481            )
482            .add(existing_as_self(TestService::default()))
483            .build_provider()
484            .unwrap();
485
486        // act
487        let options = provider.get_required::<dyn Options<TestOptions>>();
488
489        // assert
490        assert_eq!(options.value().setting, 10);
491    }
492
493    #[test]
494    fn get_required_should_configure_options_with_5_dependencies() {
495        // arrange
496        let provider = ServiceCollection::new()
497            .add_options::<TestOptions>()
498            .configure5(
499                |o,
500                 d1: Ref<TestService>,
501                 d2: Ref<TestService>,
502                 d3: Ref<TestService>,
503                 d4: Ref<TestService>,
504                 d5: Ref<TestService>| {
505                    o.setting = d1.next() + d2.next() + d3.next() + d4.next() + d5.next()
506                },
507            )
508            .add(existing_as_self(TestService::default()))
509            .build_provider()
510            .unwrap();
511
512        // act
513        let options = provider.get_required::<dyn Options<TestOptions>>();
514
515        // assert
516        assert_eq!(options.value().setting, 15);
517    }
518
519    #[test]
520    fn get_required_should_post_configure_options_with_1_dependency() {
521        // arrange
522        let provider = ServiceCollection::new()
523            .add_options::<TestOptions>()
524            .post_configure1(|o, d1: Ref<TestService>| o.setting = d1.next())
525            .add(existing_as_self(TestService::default()))
526            .build_provider()
527            .unwrap();
528
529        // act
530        let options = provider.get_required::<dyn Options<TestOptions>>();
531
532        // assert
533        assert_eq!(options.value().setting, 1);
534    }
535
536    #[test]
537    fn get_required_should_post_configure_options_with_2_dependencies() {
538        // arrange
539        let provider = ServiceCollection::new()
540            .add_options::<TestOptions>()
541            .post_configure2(|o, d1: Ref<TestService>, d2: Ref<TestService>| o.setting = d1.next() + d2.next())
542            .add(existing_as_self(TestService::default()))
543            .build_provider()
544            .unwrap();
545
546        // act
547        let options = provider.get_required::<dyn Options<TestOptions>>();
548
549        // assert
550        assert_eq!(options.value().setting, 3);
551    }
552
553    #[test]
554    fn get_required_should_post_configure_options_with_3_dependencies() {
555        // arrange
556        let provider = ServiceCollection::new()
557            .add_options::<TestOptions>()
558            .post_configure3(|o, d1: Ref<TestService>, d2: Ref<TestService>, d3: Ref<TestService>| {
559                o.setting = d1.next() + d2.next() + d3.next()
560            })
561            .add(existing_as_self(TestService::default()))
562            .build_provider()
563            .unwrap();
564
565        // act
566        let options = provider.get_required::<dyn Options<TestOptions>>();
567
568        // assert
569        assert_eq!(options.value().setting, 6);
570    }
571
572    #[test]
573    fn get_required_should_post_configure_options_with_4_dependencies() {
574        // arrange
575        let provider = ServiceCollection::new()
576            .add_options::<TestOptions>()
577            .post_configure4(
578                |o, d1: Ref<TestService>, d2: Ref<TestService>, d3: Ref<TestService>, d4: Ref<TestService>| {
579                    o.setting = d1.next() + d2.next() + d3.next() + d4.next()
580                },
581            )
582            .add(existing_as_self(TestService::default()))
583            .build_provider()
584            .unwrap();
585
586        // act
587        let options = provider.get_required::<dyn Options<TestOptions>>();
588
589        // assert
590        assert_eq!(options.value().setting, 10);
591    }
592
593    #[test]
594    fn get_required_should_post_configure_options_with_5_dependencies() {
595        // arrange
596        let provider = ServiceCollection::new()
597            .add_options::<TestOptions>()
598            .post_configure5(
599                |o,
600                 d1: Ref<TestService>,
601                 d2: Ref<TestService>,
602                 d3: Ref<TestService>,
603                 d4: Ref<TestService>,
604                 d5: Ref<TestService>| {
605                    o.setting = d1.next() + d2.next() + d3.next() + d4.next() + d5.next()
606                },
607            )
608            .add(existing_as_self(TestService::default()))
609            .build_provider()
610            .unwrap();
611
612        // act
613        let options = provider.get_required::<dyn Options<TestOptions>>();
614
615        // assert
616        assert_eq!(options.value().setting, 15);
617    }
618
619    #[test]
620    fn get_required_should_validate_options_with_1_dependency() {
621        // arrange
622        let provider = ServiceCollection::new()
623            .add_options::<TestOptions>()
624            .configure(|o| o.enabled = true)
625            .validate1(
626                |o, d1: Ref<TestService>| {
627                    let _ = d1.next();
628                    o.enabled
629                },
630                "Not enabled!",
631            )
632            .add(existing_as_self(TestService::default()))
633            .build_provider()
634            .unwrap();
635
636        // act
637        let options = provider.get_required::<dyn Options<TestOptions>>();
638        let service = provider.get_required::<TestService>();
639
640        // assert
641        assert_eq!(options.value().enabled, true);
642        assert_eq!(service.calls(), 1);
643    }
644
645    #[test]
646    fn get_required_should_validate_options_with_2_dependencies() {
647        // arrange
648        let provider = ServiceCollection::new()
649            .add_options::<TestOptions>()
650            .configure(|o| o.enabled = true)
651            .validate2(
652                |o, d1: Ref<TestService>, d2: Ref<TestService>| {
653                    let _ = d1.next() + d2.next();
654                    o.enabled
655                },
656                "Not enabled!",
657            )
658            .add(existing_as_self(TestService::default()))
659            .build_provider()
660            .unwrap();
661
662        // act
663        let options = provider.get_required::<dyn Options<TestOptions>>();
664        let service = provider.get_required::<TestService>();
665
666        // assert
667        assert_eq!(options.value().enabled, true);
668        assert_eq!(service.calls(), 2);
669    }
670
671    #[test]
672    fn get_required_should_validate_options_with_3_dependencies() {
673        // arrange
674        let provider = ServiceCollection::new()
675            .add_options::<TestOptions>()
676            .configure(|o| o.enabled = true)
677            .validate3(
678                |o, d1: Ref<TestService>, d2: Ref<TestService>, d3: Ref<TestService>| {
679                    let _ = d1.next() + d2.next() + d3.next();
680                    o.enabled
681                },
682                "Not enabled!",
683            )
684            .add(existing_as_self(TestService::default()))
685            .build_provider()
686            .unwrap();
687
688        // act
689        let options = provider.get_required::<dyn Options<TestOptions>>();
690        let service = provider.get_required::<TestService>();
691
692        // assert
693        assert_eq!(options.value().enabled, true);
694        assert_eq!(service.calls(), 3);
695    }
696
697    #[test]
698    fn get_required_should_validate_options_with_4_dependencies() {
699        // arrange
700        let provider = ServiceCollection::new()
701            .add_options::<TestOptions>()
702            .configure(|o| o.enabled = true)
703            .validate4(
704                |o, d1: Ref<TestService>, d2: Ref<TestService>, d3: Ref<TestService>, d4: Ref<TestService>| {
705                    let _ = d1.next() + d2.next() + d3.next() + d4.next();
706                    o.enabled
707                },
708                "Not enabled!",
709            )
710            .add(existing_as_self(TestService::default()))
711            .build_provider()
712            .unwrap();
713
714        // act
715        let options = provider.get_required::<dyn Options<TestOptions>>();
716        let service = provider.get_required::<TestService>();
717
718        // assert
719        assert_eq!(options.value().enabled, true);
720        assert_eq!(service.calls(), 4);
721    }
722
723    #[test]
724    fn get_required_should_validate_options_with_5_dependencies() {
725        // arrange
726        let provider = ServiceCollection::new()
727            .add_options::<TestOptions>()
728            .configure(|o| o.enabled = true)
729            .validate5(
730                |o,
731                 d1: Ref<TestService>,
732                 d2: Ref<TestService>,
733                 d3: Ref<TestService>,
734                 d4: Ref<TestService>,
735                 d5: Ref<TestService>| {
736                    let _ = d1.next() + d2.next() + d3.next() + d4.next() + d5.next();
737                    o.enabled
738                },
739                "Not enabled!",
740            )
741            .add(existing_as_self(TestService::default()))
742            .build_provider()
743            .unwrap();
744
745        // act
746        let options = provider.get_required::<dyn Options<TestOptions>>();
747        let service = provider.get_required::<TestService>();
748
749        // assert
750        assert_eq!(options.value().enabled, true);
751        assert_eq!(service.calls(), 5);
752    }
753}