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)(®istry);
365
366 #[cfg(feature = "multithread")]
367 (register.0)(®istry);
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)(®istry);
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(®istry);
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(®istry), 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)(®istry).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}