options/
di_ext.rs

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