ferrunix_core/
registry.rs

1//! Holds all registered types that can be injected or constructed.
2#![allow(clippy::multiple_inherent_impl)]
3
4use std::any::TypeId;
5use std::marker::PhantomData;
6
7use crate::cycle_detection::{
8    DependencyValidator, FullValidationError, ValidationError,
9};
10use crate::dependency_builder::DepBuilder;
11use crate::error::{ImplErrors, ResolveError};
12use crate::object_builder::Object;
13use crate::types::{BoxErr, RefWeak, Registerable, RegisterableSingleton};
14#[cfg(not(feature = "tokio"))]
15use crate::types::{
16    SingletonCtor, SingletonCtorDeps, SingletonCtorFallible,
17    SingletonCtorFallibleDeps, TransientCtor, TransientCtorDeps,
18    TransientCtorFallible, TransientCtorFallibleDeps,
19};
20use crate::{
21    registration::RegistrationFunc, registration::DEFAULT_REGISTRY,
22    types::HashMap, types::Ref, types::RwLock,
23};
24
25/// Registry for all types that can be constructed or otherwise injected.
26pub struct Registry {
27    /// Parent registry. None if it's the root registry.
28    parent: Option<RefWeak<Registry>>,
29    /// Internal hashtable of all registered objects.
30    objects: RwLock<HashMap<TypeId, Object>>,
31    /// Validation.
32    validator: DependencyValidator,
33}
34
35#[allow(clippy::multiple_inherent_impl)]
36impl Registry {
37    /// Create a new, empty, registry. This registry contains no pre-registered
38    /// types.
39    ///
40    /// Types that are auto-registered are also not included in this registry.
41    ///
42    /// To get access to the auto-registered types (types that are annotated by
43    /// the derive macro), the global registry [`Registry::global`] needs to
44    /// be used.
45    #[must_use]
46    pub fn empty() -> Self {
47        Self {
48            parent: None,
49            objects: RwLock::new(HashMap::new()),
50            validator: DependencyValidator::new(),
51        }
52    }
53
54    /// Register a new transient or singleton with dependencies.
55    #[cfg_attr(feature = "tracing", tracing::instrument)]
56    pub fn with_deps<T, Deps>(&self) -> Builder<'_, T, Deps>
57    where
58        Deps: DepBuilder<T>,
59    {
60        Builder {
61            registry: self,
62            _marker: PhantomData,
63            _marker1: PhantomData,
64        }
65    }
66
67    /// Check whether all registered types have the required dependencies.
68    ///
69    /// This is a potentially expensive call since it needs to go through the
70    /// entire dependency tree for each registered type.
71    ///
72    /// Nontheless, it's recommended to call this before using the [`Registry`].
73    ///
74    /// # Errors
75    /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or
76    /// has cycles.
77    #[cfg_attr(feature = "tracing", tracing::instrument)]
78    pub fn validate_all(&self) -> Result<(), ValidationError> {
79        self.validator.validate_all()
80    }
81
82    /// Check whether all registered types have the required dependencies and returns a
83    /// detailed error about what's missing or where a cycle was detected.
84    ///
85    /// This is a potentially expensive call since it needs to go through the
86    /// entire dependency tree for each registered type.
87    ///
88    /// # Errors
89    /// Returns a [`FullValidationError`] when the dependency graph is missing dependencies or has
90    /// cycles.
91    #[cfg_attr(feature = "tracing", tracing::instrument)]
92    pub fn validate_all_full(&self) -> Result<(), FullValidationError> {
93        self.validator.validate_all_full()
94    }
95
96    /// Check whether the type `T` is registered in this registry, and all
97    /// dependencies of the type `T` are also registered.
98    ///
99    /// # Errors
100    /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or has cycles.
101    #[cfg_attr(feature = "tracing", tracing::instrument)]
102    pub fn validate<T>(&self) -> Result<(), ValidationError>
103    where
104        T: Registerable,
105    {
106        self.validator.validate::<T>()
107    }
108
109    /// Return a string of the dependency graph visualized using graphviz's `dot` language.
110    ///
111    /// # Errors
112    /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or has cycles.
113    #[cfg_attr(feature = "tracing", tracing::instrument)]
114    pub fn dotgraph(&self) -> Result<String, ValidationError> {
115        self.validator.dotgraph()
116    }
117
118    /// Access the global registry.
119    ///
120    /// This registry contains the types that are marked for auto-registration
121    /// via the derive macro.
122    #[cfg(all(not(feature = "tokio"), not(feature = "multithread")))]
123    #[cfg_attr(feature = "tracing", tracing::instrument)]
124    pub fn global() -> std::rc::Rc<Self> {
125        DEFAULT_REGISTRY.with(|val| {
126            let ret =
127                val.get_or_init(|| std::rc::Rc::new(Self::autoregistered()));
128            std::rc::Rc::clone(ret)
129        })
130    }
131
132    /// Access the global registry.
133    ///
134    /// This registry contains the types that are marked for auto-registration
135    /// via the derive macro.
136    #[cfg(all(feature = "multithread", not(feature = "tokio")))]
137    #[cfg_attr(feature = "tracing", tracing::instrument)]
138    pub fn global() -> Ref<Self> {
139        let ret = DEFAULT_REGISTRY.get_or_init(|| {
140            let new = Self::autoregistered();
141            Ref::new(new)
142        });
143        Ref::clone(ret)
144    }
145
146    /// Create a new child registry.
147    ///
148    /// When this registry is used to register and retrieve objects, the objects
149    /// directly registered on the returned registry are preferred to the objects
150    /// that have been previously registered with the parent registry.
151    ///
152    /// This allows for sub-registries to override objects from their parent.
153    pub fn child(self: &Ref<Self>) -> Ref<Self> {
154        Ref::new(Self {
155            parent: Some(Ref::downgrade(self)),
156            objects: RwLock::new(HashMap::new()),
157            validator: DependencyValidator::new(),
158        })
159    }
160}
161
162#[cfg(not(feature = "tokio"))]
163impl Registry {
164    /// Register a new transient object, without dependencies.
165    ///
166    /// To register a type with dependencies, use the builder returned from
167    /// [`Registry::with_deps`].
168    ///
169    /// # Parameters
170    ///   * `ctor`: A constructor function returning the newly constructed `T`.
171    ///     This constructor will be called for every `T` that is requested.
172    ///
173    /// # Panics
174    /// When the type has been registered already.
175    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
176    pub fn register_transient<T, C>(&self, ctor: C)
177    where
178        T: Registerable,
179        C: TransientCtor<T> + Copy + 'static,
180    {
181        self.try_register_transient::<T, _>(move || -> Result<T, BoxErr> {
182            Ok((ctor)())
183        });
184    }
185
186    /// Register a new transient object, without dependencies.
187    ///
188    /// To register a type with dependencies, use the builder returned from
189    /// [`Registry::with_deps`].
190    ///
191    /// # Parameters
192    ///   * `ctor`: A constructor function returning a `Result<T, E>`.
193    ///     This constructor will be called for every `T` that is requested.
194    ///
195    /// # Panics
196    /// When the type has been registered already.
197    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
198    pub fn try_register_transient<T, C>(&self, ctor: C)
199    where
200        T: Registerable,
201        C: TransientCtorFallible<T> + Copy + 'static,
202    {
203        use crate::object_builder::TransientBuilderImplNoDeps;
204
205        #[cfg(feature = "tracing")]
206        tracing::info!(
207            "registering transient ({})",
208            std::any::type_name::<T>()
209        );
210
211        let closure = Box::new(move || -> Result<T, BoxErr> { (ctor)() });
212        let transient = Object::Transient(Box::new(
213            TransientBuilderImplNoDeps::new(closure),
214        ));
215
216        self.insert_or_panic::<T>(transient);
217        self.validator.add_transient_no_deps::<T>();
218    }
219
220    /// Register a new singleton object, without dependencies.
221    ///
222    /// To register a type with dependencies, use the builder returned from
223    /// [`Registry::with_deps`].
224    ///
225    /// # Parameters
226    ///   * `ctor`: A constructor function returning the newly constructed `T`.
227    ///     This constructor will be called once, lazily, when the first
228    ///     instance of `T` is requested.
229    ///
230    /// # Panics
231    /// When the type has been registered already.
232    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
233    pub fn register_singleton<T, F>(&self, ctor: F)
234    where
235        T: RegisterableSingleton,
236        F: SingletonCtor<T>,
237    {
238        self.try_register_singleton(move || -> Result<T, BoxErr> {
239            Ok((ctor)())
240        });
241    }
242
243    /// Register a new singleton object, without dependencies.
244    ///
245    /// To register a type with dependencies, use the builder returned from
246    /// [`Registry::with_deps`].
247    ///
248    /// # Parameters
249    ///   * `ctor`: A constructor function returning a `Result<T, E>`.
250    ///     This constructor will be called once, lazily, when the first
251    ///     instance of `T` is requested.
252    ///
253    /// # Panics
254    /// When the type has been registered already.
255    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
256    pub fn try_register_singleton<T, F>(&self, ctor: F)
257    where
258        T: RegisterableSingleton,
259        F: SingletonCtorFallible<T>,
260    {
261        use crate::object_builder::SingletonGetterNoDeps;
262
263        #[cfg(feature = "tracing")]
264        tracing::info!(
265            "registering singleton ({})",
266            std::any::type_name::<T>()
267        );
268
269        let singleton =
270            Object::Singleton(Box::new(SingletonGetterNoDeps::new(ctor)));
271
272        self.insert_or_panic::<T>(singleton);
273        self.validator.add_singleton_no_deps::<T>();
274    }
275
276    /// Retrieves a newly constructed `T` from this registry.
277    ///
278    /// # Errors
279    /// Returns an error if the registered constructor fails, or the type fails
280    /// to resolve (i.e is not registered).
281    #[cfg_attr(feature = "tracing", tracing::instrument)]
282    pub fn transient<T>(&self) -> Result<T, ResolveError>
283    where
284        T: Registerable,
285    {
286        let lock = self.objects.read();
287        if let Some(Object::Transient(transient)) = lock.get(&TypeId::of::<T>())
288        {
289            let resolved = transient.make_transient(self)?;
290            drop(lock);
291            let ret = resolved.downcast::<T>().map_err(|_self| {
292                ResolveError::Impl(ImplErrors::TypeMismatch)
293            })?;
294            return Ok(*ret);
295        }
296
297        #[allow(clippy::redundant_closure_for_method_calls)]
298        self.parent
299            .as_ref()
300            .and_then(|weakref| weakref.upgrade())
301            .map_or_else(
302                || {
303                    Err(ResolveError::TypeMissing {
304                        typename: std::any::type_name::<T>(),
305                    })
306                },
307                |parent| parent.transient::<T>(),
308            )
309    }
310
311    /// Retrieves the singleton `T` from this registry.
312    ///
313    /// The return singleton is a ref-counted pointer object (either `Arc` or `Rc`).
314    ///
315    /// # Errors
316    /// Returns an error if the registered constructor fails, or the type fails
317    /// to resolve (i.e is not registered).
318    #[cfg_attr(feature = "tracing", tracing::instrument)]
319    pub fn singleton<T>(&self) -> Result<Ref<T>, ResolveError>
320    where
321        T: RegisterableSingleton,
322    {
323        let lock = self.objects.read();
324        if let Some(Object::Singleton(singleton)) = lock.get(&TypeId::of::<T>())
325        {
326            let resolved = singleton.get_singleton(self)?;
327            drop(lock);
328            let obj = resolved.downcast::<T>().map_err(|_self| {
329                ResolveError::Impl(ImplErrors::TypeMismatch)
330            })?;
331            return Ok(obj);
332        }
333
334        #[allow(clippy::redundant_closure_for_method_calls)]
335        self.parent
336            .as_ref()
337            .and_then(|weakref| weakref.upgrade())
338            .map_or_else(
339                || {
340                    Err(ResolveError::TypeMissing {
341                        typename: std::any::type_name::<T>(),
342                    })
343                },
344                |parent| parent.singleton::<T>(),
345            )
346    }
347
348    /// Reset the global registry, removing all previously registered types, and
349    /// re-running the auto-registration routines.
350    ///
351    /// # Safety
352    /// Ensure that no other thread is currently using [`Registry::global()`].
353    #[allow(unsafe_code)]
354    #[cfg_attr(feature = "tracing", tracing::instrument)]
355    pub unsafe fn reset_global() {
356        let registry = Self::global();
357        {
358            let mut lock = registry.objects.write();
359            lock.clear();
360        }
361
362        for register in inventory::iter::<RegistrationFunc> {
363            #[cfg(not(feature = "multithread"))]
364            (register.0)(&registry);
365
366            #[cfg(feature = "multithread")]
367            (register.0)(&registry);
368        }
369    }
370
371    /// Create an empty registry, and add all autoregistered types into it.
372    ///
373    /// This is the constructor for the global registry that can be acquired
374    /// with [`Registry::global`].
375    #[must_use]
376    #[cfg_attr(feature = "tracing", tracing::instrument)]
377    pub fn autoregistered() -> Self {
378        let registry = Self::empty();
379        for register in inventory::iter::<RegistrationFunc> {
380            (register.0)(&registry);
381        }
382
383        registry
384    }
385
386    /// Inserts a new object into the objecs hashtable.
387    ///
388    /// This acquires an exclusive lock on `self.objects`.
389    ///
390    /// # Panics
391    /// If the key already exists (=> the type was previously registered).
392    #[inline]
393    fn insert_or_panic<T: 'static>(&self, value: Object) {
394        let mut lock = self.objects.write();
395        let entry = lock.entry(TypeId::of::<T>());
396        match entry {
397            #[allow(clippy::panic)]
398            hashbrown::hash_map::Entry::Occupied(_) => panic!(
399                "Type '{}' ({:?}) is already registered",
400                std::any::type_name::<T>(),
401                TypeId::of::<T>()
402            ),
403            hashbrown::hash_map::Entry::Vacant(view) => {
404                view.insert(value);
405            }
406        }
407    }
408}
409
410#[cfg(feature = "tokio")]
411impl Registry {
412    /// Create an empty registry, and add all autoregistered types into it.
413    ///
414    /// This is the constructor for the global registry that can be acquired
415    /// with [`Registry::global`].
416    ///
417    /// # Panics
418    /// If any of the constructors panic.
419    #[must_use]
420    #[cfg_attr(feature = "tracing", tracing::instrument)]
421    pub async fn autoregistered() -> Self {
422        use std::sync::Arc;
423
424        let registry = Arc::new(Self::empty());
425
426        let mut set = tokio::task::JoinSet::new();
427        for register in inventory::iter::<RegistrationFunc> {
428            let registry = Arc::clone(&registry);
429            set.spawn(async move {
430                let inner_registry = registry;
431                (register.0)(&inner_registry).await;
432            });
433        }
434
435        #[allow(clippy::panic)]
436        while let Some(res) = set.join_next().await {
437            match res {
438                Ok(_) => {}
439                Err(err) if err.is_panic() => {
440                    std::panic::resume_unwind(err.into_panic())
441                }
442                Err(err) => panic!("{err}"),
443            }
444        }
445
446        assert_eq!(
447            Arc::strong_count(&registry), 1,
448            "all of the tasks in the `JoinSet` should've joined, dropping their \
449            Arc's. some task is still holding an Arc");
450        Arc::try_unwrap(registry).expect("all tasks above are joined")
451    }
452
453    /// Register a new singleton object, without dependencies.
454    ///
455    /// To register a type with dependencies, use the builder returned from
456    /// [`Registry::with_deps`].
457    ///
458    /// # Parameters
459    ///   * `ctor`: A constructor function returning the newly constructed `T`. This constructor
460    ///     will be called once, lazily, when the first instance of `T` is requested.
461    ///
462    /// # Panics
463    /// When the type has been registered already.
464    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
465    pub async fn register_singleton<T, F>(&self, ctor: F)
466    where
467        T: RegisterableSingleton,
468        F: crate::types::SingletonCtor<T> + Copy,
469    {
470        let wrapped = move || {
471            Box::pin(async move {
472                let obj = (ctor)().await;
473                Ok::<_, BoxErr>(obj)
474            })
475                as crate::types::BoxFuture<'static, Result<T, BoxErr>>
476        };
477
478        self.try_register_singleton::<T, _>(wrapped).await;
479    }
480
481    /// Try registering a new singleton object, without dependencies.
482    ///
483    /// To register a type with dependencies, use the builder returned from
484    /// [`Registry::with_deps`].
485    ///
486    /// # Parameters
487    ///   * `ctor`: A constructor function returning the newly constructed `Result<T>`. This
488    ///     constructor will be called once, lazily, when the first instance of `T` is requested.
489    ///
490    /// # Panics
491    /// When the type has been registered already.
492    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
493    pub async fn try_register_singleton<T, F>(&self, ctor: F)
494    where
495        T: RegisterableSingleton,
496        F: crate::types::SingletonCtorFallible<T>,
497    {
498        use crate::object_builder::AsyncSingletonNoDeps;
499
500        #[cfg(feature = "tracing")]
501        tracing::info!(
502            "registering singleton ({})",
503            std::any::type_name::<T>()
504        );
505
506        let singleton =
507            Object::AsyncSingleton(Box::new(AsyncSingletonNoDeps::new(ctor)));
508
509        self.insert_or_panic::<T>(singleton).await;
510        self.validator.add_singleton_no_deps::<T>();
511    }
512
513    /// Register a new transient object, without dependencies.
514    ///
515    /// To register a type with dependencies, use the builder returned from
516    /// [`Registry::with_deps`].
517    ///
518    /// # Parameters
519    ///   * `ctor`: A constructor function returning the newly constructed `T`. This constructor
520    ///     will be called for every `T` that is requested.
521    ///
522    /// # Panics
523    /// When the type has been registered already.
524    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
525    pub async fn register_transient<T, F>(&self, ctor: F)
526    where
527        T: Registerable,
528        F: crate::types::TransientCtor<T> + Copy,
529    {
530        let wrapped = move || {
531            Box::pin(async move {
532                let obj = (ctor)().await;
533                Ok::<T, BoxErr>(obj)
534            })
535        }
536            as crate::types::BoxFuture<'static, Result<T, BoxErr>>;
537
538        self.try_register_transient::<T, _>(wrapped).await;
539    }
540
541    /// Try registering a new transient object, without dependencies.
542    ///
543    /// To register a type with dependencies, use the builder returned from
544    /// [`Registry::with_deps`].
545    ///
546    /// # Parameters
547    ///   * `ctor`: A constructor function returning the newly constructed `Result<T>`. This
548    ///     constructor will be called for every `T` that is requested.
549    ///
550    /// # Panics
551    /// When the type has been registered already.
552    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
553    pub async fn try_register_transient<T, F>(&self, ctor: F)
554    where
555        T: Registerable,
556        F: crate::types::TransientCtorFallible<T>,
557    {
558        use crate::object_builder::AsyncTransientBuilderImplNoDeps;
559
560        #[cfg(feature = "tracing")]
561        tracing::info!(
562            "registering transient ({})",
563            std::any::type_name::<T>()
564        );
565
566        let transient = Object::AsyncTransient(Box::new(
567            AsyncTransientBuilderImplNoDeps::new(Box::new(ctor)
568                as Box<
569                    dyn crate::types::TransientCtorFallible<T> + Send + Sync,
570                >),
571        ));
572
573        self.insert_or_panic::<T>(transient).await;
574        self.validator.add_transient_no_deps::<T>();
575    }
576
577    /// Retrieves a newly constructed `T` from this registry.
578    ///
579    /// Returns `None` if `T` wasn't registered or failed to construct.
580    #[cfg_attr(feature = "tracing", tracing::instrument)]
581    pub async fn transient<T>(&self) -> Result<T, ResolveError>
582    where
583        T: Registerable,
584    {
585        let lock = self.objects.read().await;
586        if let Some(Object::AsyncTransient(ctor)) = lock.get(&TypeId::of::<T>())
587        {
588            let boxed = ctor.make_transient(self).await?;
589            drop(lock);
590            let obj = boxed.downcast::<T>().map_err(|_self| {
591                ResolveError::Impl(ImplErrors::TypeMismatch)
592            })?;
593            return Ok(*obj);
594        }
595
596        #[allow(clippy::redundant_closure_for_method_calls)]
597        if let Some(parent) =
598            self.parent.as_ref().and_then(|weakref| weakref.upgrade())
599        {
600            Box::pin(async move { parent.transient::<T>().await }).await
601        } else {
602            Err(ResolveError::TypeMissing {
603                typename: std::any::type_name::<T>(),
604            })
605        }
606    }
607
608    /// Retrieves the singleton `T` from this registry.
609    ///
610    /// Returns `None` if `T` wasn't registered or failed to construct. The
611    /// singleton is a ref-counted pointer object (either `Arc` or `Rc`).
612    ///
613    /// # Errors
614    /// Returns an error if the singleton fails to construct.
615    #[cfg_attr(feature = "tracing", tracing::instrument)]
616    pub async fn singleton<T>(&self) -> Result<Ref<T>, ResolveError>
617    where
618        T: RegisterableSingleton,
619    {
620        let lock = self.objects.read().await;
621        if let Some(Object::AsyncSingleton(singleton)) =
622            lock.get(&TypeId::of::<T>())
623        {
624            let resolved = singleton.get_singleton(self).await?;
625            drop(lock);
626            let obj = resolved.downcast::<T>().map_err(|_self| {
627                ResolveError::Impl(ImplErrors::TypeMismatch)
628            })?;
629            return Ok(obj);
630        }
631
632        #[allow(clippy::redundant_closure_for_method_calls)]
633        if let Some(parent) =
634            self.parent.as_ref().and_then(|weakref| weakref.upgrade())
635        {
636            Box::pin(async move { parent.singleton::<T>().await }).await
637        } else {
638            Err(ResolveError::TypeMissing {
639                typename: std::any::type_name::<T>(),
640            })
641        }
642    }
643
644    /// Access the global registry.
645    ///
646    /// This registry contains the types that are marked for auto-registration
647    /// via the derive macro.
648    #[cfg_attr(feature = "tracing", tracing::instrument)]
649    pub async fn global() -> Ref<Self> {
650        let ret = DEFAULT_REGISTRY
651            .get_or_init(|| async move {
652                let new = Self::autoregistered().await;
653                Ref::new(new)
654            })
655            .await;
656        Ref::clone(ret)
657    }
658
659    /// Reset the global registry, removing all previously registered types, and
660    /// re-running the auto-registration routines.
661    ///
662    /// # Safety
663    /// Ensure that no other thread is currently using [`Registry::global()`].
664    #[allow(unsafe_code)]
665    pub async unsafe fn reset_global() {
666        // Purposefully not annotated with `tracing::instrument` because it mangles the order of
667        // `async` and `unsafe`, resulting in a compiler error.
668        let registry = Self::global().await;
669        {
670            let mut lock = registry.objects.write().await;
671            lock.clear();
672        }
673
674        for register in inventory::iter::<RegistrationFunc> {
675            (register.0)(&registry).await;
676        }
677    }
678
679    /// Inserts a new object into the objecs hashtable.
680    ///
681    /// This acquires an exclusive lock on `self.objects`.
682    ///
683    /// # Panics
684    /// If the key already exists (=> the type was previously registered).
685    #[inline]
686    async fn insert_or_panic<T: 'static>(&self, value: Object) {
687        let mut lock = self.objects.write().await;
688        let entry = lock.entry(TypeId::of::<T>());
689        match entry {
690            #[allow(clippy::panic)]
691            hashbrown::hash_map::Entry::Occupied(_) => panic!(
692                "Type '{}' ({:?}) is already registered",
693                std::any::type_name::<T>(),
694                TypeId::of::<T>()
695            ),
696            hashbrown::hash_map::Entry::Vacant(view) => {
697                view.insert(value);
698            }
699        }
700    }
701}
702
703impl std::fmt::Debug for Registry {
704    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
705        fmt.debug_struct("Registry").finish()
706    }
707}
708
709/// A builder for objects with dependencies. This can be created by using
710/// [`Registry::with_deps`].
711#[allow(clippy::single_char_lifetime_names)]
712pub struct Builder<'reg, T, Deps> {
713    /// Reference to parent registry.
714    registry: &'reg Registry,
715    /// Marker for `T`.
716    _marker: PhantomData<T>,
717    /// Marker for `Deps`.
718    _marker1: PhantomData<Deps>,
719}
720
721impl<
722        T,
723        #[cfg(not(feature = "tokio"))] Deps: DepBuilder<T> + 'static,
724        #[cfg(feature = "tokio")] Deps: DepBuilder<T> + Send + Sync + 'static,
725    > Builder<'_, T, Deps>
726where
727    T: Registerable,
728{
729    /// Register a new transient object, with dependencies specified in [`Registry::with_deps`].
730    ///
731    /// The `ctor` parameter is a constructor function returning the newly constructed `T`. The
732    /// constructor accepts a single argument `Deps` (a tuple implementing
733    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
734    /// each dependency separately. This constructor will be called for every `T` that is
735    /// requested.
736    ///
737    /// # Example
738    /// ```rust,no_run
739    /// # use ferrunix_core::{Registry, Singleton, Transient};
740    /// # let registry = Registry::empty();
741    /// # struct Template {
742    /// #     template: &'static str,
743    /// # }
744    /// registry
745    ///     .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
746    ///     .register_transient(|(num, template)| {
747    ///         // access `num` and `template` here.
748    ///         u16::from(*num)
749    ///     });
750    /// ```
751    ///
752    /// For single dependencies, the destructured tuple needs to end with a
753    /// comma: `(dep,)`.
754    ///
755    /// # Panics
756    /// When the type has been registered already.
757    #[cfg(not(feature = "tokio"))]
758    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
759    pub fn register_transient<C>(&self, ctor: C)
760    where
761        C: TransientCtorDeps<T, Deps> + Copy + 'static,
762    {
763        self.try_register_transient::<_>(move |deps| -> Result<T, BoxErr> {
764            Ok((ctor)(deps))
765        });
766    }
767
768    /// Register a new transient object, with dependencies specified in [`Registry::with_deps`].
769    ///
770    /// The `ctor` parameter is a constructor function returning a `Result<T, E>`. The
771    /// constructor accepts a single argument `Deps` (a tuple implementing
772    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
773    /// each dependency separately. This constructor will be called for every `T` that is
774    /// requested.
775    ///
776    /// # Example
777    /// ```rust,no_run
778    /// # use ferrunix_core::{Registry, Singleton, Transient};
779    /// # let registry = Registry::empty();
780    /// # struct Template {
781    /// #     template: &'static str,
782    /// # }
783    /// registry
784    ///     .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
785    ///     .register_transient(|(num, template)| {
786    ///         // access `num` and `template` here.
787    ///         u16::from(*num)
788    ///     });
789    /// ```
790    ///
791    /// For single dependencies, the destructured tuple needs to end with a
792    /// comma: `(dep,)`.
793    ///
794    /// # Panics
795    /// When the type has been registered already.
796    #[cfg(not(feature = "tokio"))]
797    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
798    pub fn try_register_transient<C>(&self, ctor: C)
799    where
800        C: TransientCtorFallibleDeps<T, Deps> + Copy + 'static,
801    {
802        use crate::object_builder::TransientBuilderImplWithDeps;
803
804        #[cfg(feature = "tracing")]
805        tracing::info!(
806            "registering transient (with dependencies) ({})",
807            std::any::type_name::<T>()
808        );
809
810        let closure =
811            Box::new(move |deps| -> Result<T, BoxErr> { (ctor)(deps) });
812        let transient = Object::Transient(Box::new(
813            TransientBuilderImplWithDeps::new(closure),
814        ));
815
816        self.registry.insert_or_panic::<T>(transient);
817        self.registry.validator.add_transient_deps::<T, Deps>();
818    }
819
820    /// Register a new transient object, with dependencies specified in
821    /// `.with_deps`.
822    ///
823    /// The `ctor` parameter is a constructor function returning the newly
824    /// constructed `T`. The constructor accepts a single argument `Deps` (a
825    /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
826    /// best to destructure the tuple to accept each dependency separately.
827    /// This constructor will be called for every `T` that is requested.
828    ///
829    /// The `ctor` is an `async` closure.
830    ///
831    /// # Panics
832    /// When the type has been registered already.
833    #[cfg(feature = "tokio")]
834    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
835    pub async fn register_transient<F>(&self, ctor: F)
836    where
837        F: crate::types::TransientCtorDeps<T, Deps> + Copy,
838    {
839        self.try_register_transient(move |deps| {
840            Box::pin(async move {
841                let obj = (ctor)(deps).await;
842                Ok::<T, BoxErr>(obj)
843            })
844        })
845        .await;
846    }
847
848    /// Try registering a new transient object, with dependencies specified in
849    /// `.with_deps`.
850    ///
851    /// The `ctor` parameter is a constructor function returning a `Result<T>` of the newly
852    /// constructed `T`. The constructor accepts a single argument `Deps` (a tuple implementing
853    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
854    /// each dependency separately. This constructor will be called for every `T` that is
855    /// requested.
856    ///
857    /// The `ctor` is an `async` closure.
858    ///
859    /// # Panics
860    /// When the type has been registered already.
861    #[cfg(feature = "tokio")]
862    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
863    pub async fn try_register_transient<F>(&self, ctor: F)
864    where
865        F: crate::types::TransientCtorFallibleDeps<T, Deps>,
866    {
867        use crate::object_builder::AsyncTransientBuilderImplWithDeps;
868
869        #[cfg(feature = "tracing")]
870        tracing::info!(
871            "registering transient (with dependencies) ({})",
872            std::any::type_name::<T>()
873        );
874
875        let closure = Box::new(ctor)
876            as Box<
877                dyn crate::types::TransientCtorFallibleDeps<T, Deps>
878                    + Send
879                    + Sync,
880            >;
881
882        let transient = Object::AsyncTransient(Box::new(
883            AsyncTransientBuilderImplWithDeps::new(closure),
884        ));
885
886        self.registry.insert_or_panic::<T>(transient).await;
887        self.registry.validator.add_transient_deps::<T, Deps>();
888    }
889}
890
891impl<
892        T,
893        #[cfg(not(feature = "tokio"))] Deps: DepBuilder<T> + 'static,
894        #[cfg(feature = "tokio")] Deps: DepBuilder<T> + Send + Sync + 'static,
895    > Builder<'_, T, Deps>
896where
897    T: RegisterableSingleton,
898{
899    /// Register a new singleton object, with dependencies specified by
900    /// [`Registry::with_deps`].
901    ///
902    /// The `ctor` parameter is a constructor function returning the newly constructed `T`. The
903    /// constructor accepts a single argument `Deps` (a tuple implementing
904    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
905    /// each dependency separately. This constructor will be called once, lazily, when the first
906    /// instance of `T` is requested.
907    ///
908    /// # Example
909    /// ```rust,no_run
910    /// # use ferrunix_core::{Registry, Singleton, Transient};
911    /// # let registry = Registry::empty();
912    /// # struct Template {
913    /// #     template: &'static str,
914    /// # }
915    /// registry
916    ///     .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
917    ///     .register_transient(|(num, template)| {
918    ///         // access `num` and `template` here.
919    ///         u16::from(*num)
920    ///     });
921    /// ```
922    ///
923    /// For single dependencies, the destructured tuple needs to end with a
924    /// comma: `(dep,)`.
925    #[cfg(not(feature = "tokio"))]
926    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
927    pub fn register_singleton<F>(&self, ctor: F)
928    where
929        F: SingletonCtorDeps<T, Deps>,
930    {
931        self.try_register_singleton::<_>(move |deps| -> Result<T, BoxErr> {
932            Ok((ctor)(deps))
933        });
934    }
935
936    /// Register a new singleton object, with dependencies specified by
937    /// [`Registry::with_deps`].
938    ///
939    /// The `ctor` parameter is a constructor function returning a `Result<T, E>`. The constructor
940    /// accepts a single argument `Deps` (a tuple implementing
941    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
942    /// each dependency separately. This constructor will be called once, lazily, when the first
943    /// instance of `T` is requested.
944    ///
945    /// # Example
946    /// ```rust,no_run
947    /// # use ferrunix_core::{Registry, Singleton, Transient};
948    /// # let registry = Registry::empty();
949    /// # struct Template {
950    /// #     template: &'static str,
951    /// # }
952    /// registry
953    ///     .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
954    ///     .register_transient(|(num, template)| {
955    ///         // access `num` and `template` here.
956    ///         u16::from(*num)
957    ///     });
958    /// ```
959    ///
960    /// For single dependencies, the destructured tuple needs to end with a comma: `(dep,)`.
961    #[cfg(not(feature = "tokio"))]
962    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
963    pub fn try_register_singleton<F>(&self, ctor: F)
964    where
965        F: SingletonCtorFallibleDeps<T, Deps>,
966    {
967        use crate::object_builder::SingletonGetterWithDeps;
968
969        #[cfg(feature = "tracing")]
970        tracing::info!(
971            "registering singleton (with dependencies) ({})",
972            std::any::type_name::<T>()
973        );
974
975        let singleton =
976            Object::Singleton(Box::new(SingletonGetterWithDeps::new(ctor)));
977
978        self.registry.insert_or_panic::<T>(singleton);
979        self.registry.validator.add_singleton_deps::<T, Deps>();
980    }
981
982    /// Register a new singleton object, with dependencies specified in
983    /// [`Registry::with_deps`].
984    ///
985    /// The `ctor` parameter is a constructor function returning the newly constructed `T`. The
986    /// constructor accepts a single argument `Deps` (a tuple implementing
987    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
988    /// each dependency separately. This constructor will be called once, lazily, when the first
989    /// instance of `T` is requested.
990    ///
991    /// The `ctor` is an `async` closure.
992    #[cfg(feature = "tokio")]
993    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
994    pub async fn register_singleton<F>(&self, ctor: F)
995    where
996        F: crate::types::SingletonCtorDeps<T, Deps>,
997    {
998        self.try_register_singleton(move |deps| {
999            Box::pin(async move {
1000                let obj = (ctor)(deps).await;
1001                Ok::<_, BoxErr>(obj)
1002            })
1003        })
1004        .await;
1005    }
1006
1007    /// Register a new singleton object, with dependencies specified in
1008    /// [`Registry::with_deps`].
1009    ///
1010    /// The `ctor` parameter is a constructor function returning a `Result<T>` with the newly
1011    /// constructed `T`. The constructor accepts a single argument `Deps` (a tuple implementing
1012    /// [`crate::dependency_builder::DepBuilder`]). It's best to destructure the tuple to accept
1013    /// each dependency separately. This constructor will be called once, lazily, when the first
1014    /// instance of `T` is requested.
1015    ///
1016    /// The `ctor` is an `async` closure.
1017    #[cfg(feature = "tokio")]
1018    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
1019    pub async fn try_register_singleton<F>(&self, ctor: F)
1020    where
1021        F: crate::types::SingletonCtorFallibleDeps<T, Deps>,
1022    {
1023        use crate::object_builder::AsyncSingletonWithDeps;
1024
1025        #[cfg(feature = "tracing")]
1026        tracing::info!(
1027            "registering singleton (with dependencies) ({})",
1028            std::any::type_name::<T>()
1029        );
1030
1031        let closure = Box::new(ctor)
1032            as Box<
1033                dyn crate::types::SingletonCtorFallibleDeps<T, Deps>
1034                    + Send
1035                    + Sync,
1036            >;
1037
1038        let singleton = Object::AsyncSingleton(Box::new(
1039            AsyncSingletonWithDeps::new(closure),
1040        ));
1041
1042        self.registry.insert_or_panic::<T>(singleton).await;
1043        self.registry.validator.add_singleton_deps::<T, Deps>();
1044    }
1045}
1046
1047impl<T, Dep> std::fmt::Debug for Builder<'_, T, Dep> {
1048    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1049        fmt.debug_struct("Builder").finish()
1050    }
1051}