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::object_builder::Object;
12use crate::types::{
13    Registerable, RegisterableSingleton, SingletonCtor, SingletonCtorDeps,
14};
15use crate::{
16    registration::RegistrationFunc, registration::DEFAULT_REGISTRY,
17    types::HashMap, types::Ref, types::RwLock,
18};
19
20/// Registry for all types that can be constructed or otherwise injected.
21pub struct Registry {
22    /// Internal hashtable of all registered objects.
23    objects: RwLock<HashMap<TypeId, Object>>,
24    /// Validation.
25    validator: DependencyValidator,
26}
27
28#[allow(clippy::multiple_inherent_impl)]
29impl Registry {
30    /// Create a new, empty, registry. This registry contains no pre-registered
31    /// types.
32    ///
33    /// Types that are auto-registered are also not included in this registry.
34    ///
35    /// To get access to the auto-registered types (types that are annotated by
36    /// the derive macro), the global registry [`Registry::global`] needs to
37    /// be used.
38    #[must_use]
39    pub fn empty() -> Self {
40        Self {
41            objects: RwLock::new(HashMap::new()),
42            validator: DependencyValidator::new(),
43        }
44    }
45
46    /// Register a new transient or singleton with dependencies.
47    #[cfg_attr(feature = "tracing", tracing::instrument)]
48    pub fn with_deps<T, Deps>(&self) -> Builder<'_, T, Deps>
49    where
50        Deps: DepBuilder<T>,
51    {
52        Builder {
53            registry: self,
54            _marker: PhantomData,
55            _marker1: PhantomData,
56        }
57    }
58
59    /// Check whether all registered types have the required dependencies.
60    ///
61    /// This is a potentially expensive call since it needs to go through the
62    /// entire dependency tree for each registered type.
63    ///
64    /// Nontheless, it's recommended to call this before using the [`Registry`].
65    ///
66    /// # Errors
67    /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or
68    /// has cycles.
69    #[cfg_attr(feature = "tracing", tracing::instrument)]
70    pub fn validate_all(&self) -> Result<(), ValidationError> {
71        self.validator.validate_all()
72    }
73
74    /// Check whether all registered types have the required dependencies and returns a
75    /// detailed error about what's missing or where a cycle was detected.
76    ///
77    /// This is a potentially expensive call since it needs to go through the
78    /// entire dependency tree for each registered type.
79    ///
80    /// # Errors
81    /// Returns a [`FullValidationError`] when the dependency graph is missing dependencies or has
82    /// cycles.
83    #[cfg_attr(feature = "tracing", tracing::instrument)]
84    pub fn validate_all_full(&self) -> Result<(), FullValidationError> {
85        self.validator.validate_all_full()
86    }
87
88    /// Check whether the type `T` is registered in this registry, and all
89    /// dependencies of the type `T` are also registered.
90    ///
91    /// # Errors
92    /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or has cycles.
93    #[cfg_attr(feature = "tracing", tracing::instrument)]
94    pub fn validate<T>(&self) -> Result<(), ValidationError>
95    where
96        T: Registerable,
97    {
98        self.validator.validate::<T>()
99    }
100
101    /// Return a string of the dependency graph visualized using graphviz's `dot` language.
102    ///
103    /// # Errors
104    /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or has cycles.
105    #[cfg_attr(feature = "tracing", tracing::instrument)]
106    pub fn dotgraph(&self) -> Result<String, ValidationError> {
107        self.validator.dotgraph()
108    }
109
110    /// Access the global registry.
111    ///
112    /// This registry contains the types that are marked for auto-registration
113    /// via the derive macro.
114    #[cfg(all(not(feature = "tokio"), not(feature = "multithread")))]
115    #[cfg_attr(feature = "tracing", tracing::instrument)]
116    pub fn global() -> std::rc::Rc<Self> {
117        DEFAULT_REGISTRY.with(|val| {
118            let ret =
119                val.get_or_init(|| std::rc::Rc::new(Self::autoregistered()));
120            std::rc::Rc::clone(ret)
121        })
122    }
123}
124
125#[cfg(all(feature = "multithread", not(feature = "tokio")))]
126impl Registry {
127    /// Access the global registry.
128    ///
129    /// This registry contains the types that are marked for auto-registration
130    /// via the derive macro.
131    #[cfg_attr(feature = "tracing", tracing::instrument)]
132    pub fn global() -> &'static Self {
133        DEFAULT_REGISTRY.get_or_init(Self::autoregistered)
134    }
135}
136
137#[cfg(not(feature = "tokio"))]
138impl Registry {
139    /// Register a new transient object, without dependencies.
140    ///
141    /// To register a type with dependencies, use the builder returned from
142    /// [`Registry::with_deps`].
143    ///
144    /// # Parameters
145    ///   * `ctor`: A constructor function returning the newly constructed `T`.
146    ///     This constructor will be called for every `T` that is requested.
147    ///
148    /// # Panics
149    /// When the type has been registered already.
150    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
151    pub fn transient<T>(&self, ctor: fn() -> T)
152    where
153        T: Registerable,
154    {
155        use crate::object_builder::TransientBuilderImplNoDeps;
156
157        #[cfg(feature = "tracing")]
158        tracing::info!(
159            "registering transient ({})",
160            std::any::type_name::<T>()
161        );
162
163        let transient =
164            Object::Transient(Box::new(TransientBuilderImplNoDeps::new(ctor)));
165
166        self.insert_or_panic::<T>(transient);
167        self.validator.add_transient_no_deps::<T>();
168    }
169
170    /// Register a new singleton object, without dependencies.
171    ///
172    /// To register a type with dependencies, use the builder returned from
173    /// [`Registry::with_deps`].
174    ///
175    /// # Parameters
176    ///   * `ctor`: A constructor function returning the newly constructed `T`.
177    ///     This constructor will be called once, lazily, when the first
178    ///     instance of `T` is requested.
179    ///
180    /// # Panics
181    /// When the type has been registered already.
182    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
183    pub fn singleton<T, F>(&self, ctor: F)
184    where
185        T: RegisterableSingleton,
186        F: SingletonCtor<T>,
187    {
188        use crate::object_builder::SingletonGetterNoDeps;
189
190        #[cfg(feature = "tracing")]
191        tracing::info!(
192            "registering singleton ({})",
193            std::any::type_name::<T>()
194        );
195
196        let singleton =
197            Object::Singleton(Box::new(SingletonGetterNoDeps::new(ctor)));
198
199        self.insert_or_panic::<T>(singleton);
200        self.validator.add_singleton_no_deps::<T>();
201    }
202
203    /// Retrieves a newly constructed `T` from this registry.
204    ///
205    /// Returns `None` if `T` wasn't registered or failed to construct.
206    #[must_use]
207    #[cfg_attr(feature = "tracing", tracing::instrument)]
208    pub fn get_transient<T>(&self) -> Option<T>
209    where
210        T: Registerable,
211    {
212        let lock = self.objects.read();
213        if let Some(Object::Transient(transient)) = lock.get(&TypeId::of::<T>())
214        {
215            let resolved = transient.make_transient(self)?;
216            drop(lock);
217            if let Ok(obj) = resolved.downcast::<T>() {
218                return Some(*obj);
219            }
220        }
221
222        None
223    }
224
225    /// Retrieves the singleton `T` from this registry.
226    ///
227    /// Returns `None` if `T` wasn't registered or failed to construct. The
228    /// singleton is a ref-counted pointer object (either `Arc` or `Rc`).
229    #[must_use]
230    #[cfg_attr(feature = "tracing", tracing::instrument)]
231    pub fn get_singleton<T>(&self) -> Option<Ref<T>>
232    where
233        T: RegisterableSingleton,
234    {
235        let lock = self.objects.read();
236        if let Some(Object::Singleton(singleton)) = lock.get(&TypeId::of::<T>())
237        {
238            let resolved = singleton.get_singleton(self)?;
239            drop(lock);
240            if let Ok(obj) = resolved.downcast::<T>() {
241                return Some(obj);
242            }
243        }
244
245        None
246    }
247
248    /// Reset the global registry, removing all previously registered types, and
249    /// re-running the auto-registration routines.
250    ///
251    /// # Safety
252    /// Ensure that no other thread is currently using [`Registry::global()`].
253    #[allow(unsafe_code)]
254    #[cfg_attr(feature = "tracing", tracing::instrument)]
255    pub unsafe fn reset_global() {
256        let registry = Self::global();
257        {
258            let mut lock = registry.objects.write();
259            lock.clear();
260        }
261
262        for register in inventory::iter::<RegistrationFunc> {
263            #[cfg(not(feature = "multithread"))]
264            (register.0)(&registry);
265
266            #[cfg(feature = "multithread")]
267            (register.0)(registry);
268        }
269    }
270
271    /// Create an empty registry, and add all autoregistered types into it.
272    ///
273    /// This is the constructor for the global registry that can be acquired
274    /// with [`Registry::global`].
275    #[must_use]
276    #[cfg_attr(feature = "tracing", tracing::instrument)]
277    pub fn autoregistered() -> Self {
278        let registry = Self::empty();
279        for register in inventory::iter::<RegistrationFunc> {
280            (register.0)(&registry);
281        }
282
283        registry
284    }
285
286    /// Inserts a new object into the objecs hashtable.
287    ///
288    /// This acquires an exclusive lock on `self.objects`.
289    ///
290    /// # Panics
291    /// If the key already exists (=> the type was previously registered).
292    #[inline]
293    fn insert_or_panic<T: 'static>(&self, value: Object) {
294        let mut lock = self.objects.write();
295        let entry = lock.entry(TypeId::of::<T>());
296        match entry {
297            #[allow(clippy::panic)]
298            hashbrown::hash_map::Entry::Occupied(_) => panic!(
299                "Type '{}' ({:?}) is already registered",
300                std::any::type_name::<T>(),
301                TypeId::of::<T>()
302            ),
303            hashbrown::hash_map::Entry::Vacant(view) => {
304                view.insert(value);
305            }
306        }
307    }
308}
309
310#[cfg(feature = "tokio")]
311impl Registry {
312    /// Create an empty registry, and add all autoregistered types into it.
313    ///
314    /// This is the constructor for the global registry that can be acquired
315    /// with [`Registry::global`].
316    ///
317    /// # Panics
318    /// If any of the constructors panic.
319    #[must_use]
320    #[cfg_attr(feature = "tracing", tracing::instrument)]
321    pub async fn autoregistered() -> Self {
322        use std::sync::Arc;
323
324        let registry = Arc::new(Self::empty());
325
326        let mut set = tokio::task::JoinSet::new();
327        for register in inventory::iter::<RegistrationFunc> {
328            let registry = Arc::clone(&registry);
329            set.spawn(async move {
330                let inner_registry = registry;
331                (register.0)(&inner_registry).await;
332            });
333        }
334
335        #[allow(clippy::panic)]
336        while let Some(res) = set.join_next().await {
337            match res {
338                Ok(_) => continue,
339                Err(err) if err.is_panic() => {
340                    std::panic::resume_unwind(err.into_panic())
341                }
342                Err(err) => panic!("{err}"),
343            }
344        }
345
346        assert_eq!(
347            Arc::strong_count(&registry), 1,
348            "all of the tasks in the `JoinSet` should've joined, dropping their \
349            Arc's. some task is still holding an Arc");
350        Arc::try_unwrap(registry).expect("all tasks above are joined")
351    }
352
353    /// Register a new singleton object, without dependencies.
354    ///
355    /// To register a type with dependencies, use the builder returned from
356    /// [`Registry::with_deps`].
357    ///
358    /// # Parameters
359    ///   * `ctor`: A constructor function returning the newly constructed `T`.
360    ///     This constructor will be called once, lazily, when the first
361    ///     instance of `T` is requested.
362    ///
363    /// # Panics
364    /// When the type has been registered already.
365    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
366    pub async fn singleton<T, F>(&self, ctor: F)
367    where
368        T: RegisterableSingleton,
369        F: SingletonCtor<T>,
370    {
371        use crate::object_builder::AsyncSingletonNoDeps;
372
373        #[cfg(feature = "tracing")]
374        tracing::info!(
375            "registering singleton ({})",
376            std::any::type_name::<T>()
377        );
378
379        let singleton =
380            Object::AsyncSingleton(Box::new(AsyncSingletonNoDeps::new(ctor)));
381
382        self.insert_or_panic::<T>(singleton).await;
383        self.validator.add_singleton_no_deps::<T>();
384    }
385
386    /// Register a new transient object, without dependencies.
387    ///
388    /// To register a type with dependencies, use the builder returned from
389    /// [`Registry::with_deps`].
390    ///
391    /// # Parameters
392    ///   * `ctor`: A constructor function returning the newly constructed `T`.
393    ///     This constructor will be called for every `T` that is requested.
394    ///
395    /// # Panics
396    /// When the type has been registered already.
397    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
398    pub async fn transient<T>(
399        &self,
400        ctor: fn() -> std::pin::Pin<
401            Box<dyn std::future::Future<Output = T> + Send>,
402        >,
403    ) where
404        T: Registerable,
405    {
406        use crate::object_builder::AsyncTransientBuilderImplNoDeps;
407
408        #[cfg(feature = "tracing")]
409        tracing::info!(
410            "registering transient ({})",
411            std::any::type_name::<T>()
412        );
413
414        let transient = Object::AsyncTransient(Box::new(
415            AsyncTransientBuilderImplNoDeps::new(ctor),
416        ));
417
418        self.insert_or_panic::<T>(transient).await;
419        self.validator.add_transient_no_deps::<T>();
420    }
421
422    /// Retrieves a newly constructed `T` from this registry.
423    ///
424    /// Returns `None` if `T` wasn't registered or failed to construct.
425    #[must_use]
426    #[cfg_attr(feature = "tracing", tracing::instrument)]
427    pub async fn get_transient<T>(&self) -> Option<T>
428    where
429        T: Registerable,
430    {
431        let lock = self.objects.read().await;
432        if let Some(Object::AsyncTransient(ctor)) = lock.get(&TypeId::of::<T>())
433        {
434            let boxed = ctor.make_transient(self).await?;
435            drop(lock);
436            if let Ok(obj) = boxed.downcast::<T>() {
437                return Some(*obj);
438            }
439        }
440
441        None
442    }
443
444    /// Retrieves the singleton `T` from this registry.
445    ///
446    /// Returns `None` if `T` wasn't registered or failed to construct. The
447    /// singleton is a ref-counted pointer object (either `Arc` or `Rc`).
448    #[must_use]
449    #[cfg_attr(feature = "tracing", tracing::instrument)]
450    pub async fn get_singleton<T>(&self) -> Option<Ref<T>>
451    where
452        T: RegisterableSingleton,
453    {
454        let lock = self.objects.read().await;
455        if let Some(Object::AsyncSingleton(singleton)) =
456            lock.get(&TypeId::of::<T>())
457        {
458            let resolved = singleton.get_singleton(self).await?;
459            drop(lock);
460            if let Ok(obj) = resolved.downcast::<T>() {
461                return Some(obj);
462            }
463        }
464
465        None
466    }
467
468    /// Access the global registry.
469    ///
470    /// This registry contains the types that are marked for auto-registration
471    /// via the derive macro.
472    #[cfg_attr(feature = "tracing", tracing::instrument)]
473    pub async fn global() -> &'static Self {
474        DEFAULT_REGISTRY.get_or_init(Self::autoregistered).await
475    }
476
477    /// Reset the global registry, removing all previously registered types, and
478    /// re-running the auto-registration routines.
479    ///
480    /// # Safety
481    /// Ensure that no other thread is currently using [`Registry::global()`].
482    #[allow(unsafe_code)]
483    pub async unsafe fn reset_global() {
484        // Purposefully not annotated with `tracing::instrument` because it mangles the order of
485        // `async` and `unsafe`, resulting in a compiler error.
486        let registry = Self::global().await;
487        {
488            let mut lock = registry.objects.write().await;
489            lock.clear();
490        }
491
492        for register in inventory::iter::<RegistrationFunc> {
493            (register.0)(registry).await;
494        }
495    }
496
497    /// Inserts a new object into the objecs hashtable.
498    ///
499    /// This acquires an exclusive lock on `self.objects`.
500    ///
501    /// # Panics
502    /// If the key already exists (=> the type was previously registered).
503    #[inline]
504    async fn insert_or_panic<T: 'static>(&self, value: Object) {
505        let mut lock = self.objects.write().await;
506        let entry = lock.entry(TypeId::of::<T>());
507        match entry {
508            #[allow(clippy::panic)]
509            hashbrown::hash_map::Entry::Occupied(_) => panic!(
510                "Type '{}' ({:?}) is already registered",
511                std::any::type_name::<T>(),
512                TypeId::of::<T>()
513            ),
514            hashbrown::hash_map::Entry::Vacant(view) => {
515                view.insert(value);
516            }
517        }
518    }
519}
520
521impl std::fmt::Debug for Registry {
522    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523        fmt.debug_struct("Registry").finish()
524    }
525}
526
527/// A builder for objects with dependencies. This can be created by using
528/// [`Registry::with_deps`].
529#[allow(clippy::single_char_lifetime_names)]
530pub struct Builder<'reg, T, Deps> {
531    /// Reference to parent registry.
532    registry: &'reg Registry,
533    /// Marker for `T`.
534    _marker: PhantomData<T>,
535    /// Marker for `Deps`.
536    _marker1: PhantomData<Deps>,
537}
538
539impl<
540        T,
541        #[cfg(not(feature = "tokio"))] Deps: DepBuilder<T> + 'static,
542        #[cfg(feature = "tokio")] Deps: DepBuilder<T> + Sync + 'static,
543    > Builder<'_, T, Deps>
544where
545    T: Registerable,
546{
547    /// Register a new transient object, with dependencies specified in
548    /// `.with_deps`.
549    ///
550    /// The `ctor` parameter is a constructor function returning the newly
551    /// constructed `T`. The constructor accepts a single argument `Deps` (a
552    /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
553    /// best to destructure the tuple to accept each dependency separately.
554    /// This constructor will be called for every `T` that is requested.
555    ///
556    /// # Example
557    /// ```rust,no_run
558    /// # use ferrunix_core::{Registry, Singleton, Transient};
559    /// # let registry = Registry::empty();
560    /// # struct Template {
561    /// #     template: &'static str,
562    /// # }
563    /// registry
564    ///     .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
565    ///     .transient(|(num, template)| {
566    ///         // access `num` and `template` here.
567    ///         u16::from(*num)
568    ///     });
569    /// ```
570    ///
571    /// For single dependencies, the destructured tuple needs to end with a
572    /// comma: `(dep,)`.
573    ///
574    /// # Panics
575    /// When the type has been registered already.
576    #[cfg(not(feature = "tokio"))]
577    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
578    pub fn transient(&self, ctor: fn(Deps) -> T) {
579        use crate::object_builder::TransientBuilderImplWithDeps;
580
581        #[cfg(feature = "tracing")]
582        tracing::info!(
583            "registering transient (with dependencies) ({})",
584            std::any::type_name::<T>()
585        );
586
587        let transient = Object::Transient(Box::new(
588            TransientBuilderImplWithDeps::new(ctor),
589        ));
590
591        self.registry.insert_or_panic::<T>(transient);
592        self.registry.validator.add_transient_deps::<T, Deps>();
593    }
594
595    /// Register a new transient object, with dependencies specified in
596    /// `.with_deps`.
597    ///
598    /// The `ctor` parameter is a constructor function returning the newly
599    /// constructed `T`. The constructor accepts a single argument `Deps` (a
600    /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
601    /// best to destructure the tuple to accept each dependency separately.
602    /// This constructor will be called for every `T` that is requested.
603    ///
604    /// The `ctor` must return a boxed `dyn Future`.
605    ///
606    /// # Panics
607    /// When the type has been registered already.
608    #[cfg(feature = "tokio")]
609    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
610    pub async fn transient(
611        &self,
612        ctor: fn(
613            Deps,
614        ) -> std::pin::Pin<
615            Box<dyn std::future::Future<Output = T> + Send>,
616        >,
617    ) {
618        use crate::object_builder::AsyncTransientBuilderImplWithDeps;
619
620        #[cfg(feature = "tracing")]
621        tracing::info!(
622            "registering transient (with dependencies) ({})",
623            std::any::type_name::<T>()
624        );
625
626        let transient = Object::AsyncTransient(Box::new(
627            AsyncTransientBuilderImplWithDeps::new(ctor),
628        ));
629
630        self.registry.insert_or_panic::<T>(transient).await;
631        self.registry.validator.add_transient_deps::<T, Deps>();
632    }
633}
634
635impl<
636        T,
637        #[cfg(not(feature = "tokio"))] Deps: DepBuilder<T> + 'static,
638        #[cfg(feature = "tokio")] Deps: DepBuilder<T> + Sync + 'static,
639    > Builder<'_, T, Deps>
640where
641    T: RegisterableSingleton,
642{
643    /// Register a new singleton object, with dependencies specified in
644    /// `.with_deps`.
645    ///
646    /// The `ctor` parameter is a constructor function returning the newly
647    /// constructed `T`. The constructor accepts a single argument `Deps` (a
648    /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
649    /// best to destructure the tuple to accept each dependency separately.
650    /// This constructor will be called once, lazily, when the first
651    /// instance of `T` is requested.
652    ///
653    /// # Example
654    /// ```rust,no_run
655    /// # use ferrunix_core::{Registry, Singleton, Transient};
656    /// # let registry = Registry::empty();
657    /// # struct Template {
658    /// #     template: &'static str,
659    /// # }
660    /// registry
661    ///     .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
662    ///     .transient(|(num, template)| {
663    ///         // access `num` and `template` here.
664    ///         u16::from(*num)
665    ///     });
666    /// ```
667    ///
668    /// For single dependencies, the destructured tuple needs to end with a
669    /// comma: `(dep,)`.
670    #[cfg(not(feature = "tokio"))]
671    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
672    pub fn singleton<F>(&self, ctor: F)
673    where
674        F: SingletonCtorDeps<T, Deps>,
675    {
676        use crate::object_builder::SingletonGetterWithDeps;
677
678        #[cfg(feature = "tracing")]
679        tracing::info!(
680            "registering singleton (with dependencies) ({})",
681            std::any::type_name::<T>()
682        );
683
684        let singleton =
685            Object::Singleton(Box::new(SingletonGetterWithDeps::new(ctor)));
686
687        self.registry.insert_or_panic::<T>(singleton);
688        self.registry.validator.add_singleton_deps::<T, Deps>();
689    }
690
691    /// Register a new singleton object, with dependencies specified in
692    /// `.with_deps`.
693    ///
694    /// The `ctor` parameter is a constructor function returning the newly
695    /// constructed `T`. The constructor accepts a single argument `Deps` (a
696    /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
697    /// best to destructure the tuple to accept each dependency separately.
698    /// This constructor will be called once, lazily, when the first
699    /// instance of `T` is requested.
700    ///
701    /// The `ctor` must return a boxed `dyn Future`.
702    #[cfg(feature = "tokio")]
703    #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
704    pub async fn singleton<F>(&self, ctor: F)
705    where
706        F: SingletonCtorDeps<T, Deps>,
707    {
708        use crate::object_builder::AsyncSingletonWithDeps;
709
710        #[cfg(feature = "tracing")]
711        tracing::info!(
712            "registering singleton (with dependencies) ({})",
713            std::any::type_name::<T>()
714        );
715
716        let singleton =
717            Object::AsyncSingleton(Box::new(AsyncSingletonWithDeps::new(ctor)));
718
719        self.registry.insert_or_panic::<T>(singleton).await;
720        self.registry.validator.add_singleton_deps::<T, Deps>();
721    }
722}
723
724impl<T, Dep> std::fmt::Debug for Builder<'_, T, Dep> {
725    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
726        fmt.debug_struct("Builder").finish()
727    }
728}