Skip to main content

ferroui_dependency_injection/
service_descriptor.rs

1//! `ServiceDescriptor.cs`, `ServiceKeyLookupMode.cs`, and constructor call-site metadata.
2
3use crate::*;
4use ferroui_system::{threading::tasks::Task, IDisposable};
5use std::rc::Rc;
6
7/// Async disposal uses FerroUI's local task executor; no external runtime is required.
8pub trait IAsyncDisposable {
9    fn dispose_async(&self) -> Task<()>;
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum ServiceKeyLookupMode {
14    NullKey,
15    ExplicitKey,
16    InheritKey,
17}
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum DependencyKind {
20    Required,
21    Optional,
22    Enumerable,
23}
24
25/// A constructor parameter's call site, used without running user code during validation.
26#[derive(Clone, Debug)]
27pub struct Dependency {
28    pub identifier: ServiceIdentifier,
29    pub kind: DependencyKind,
30    pub lookup_mode: ServiceKeyLookupMode,
31}
32impl Dependency {
33    pub fn required<S: ?Sized + 'static>() -> Self {
34        Self {
35            identifier: ServiceIdentifier::of::<S>(),
36            kind: DependencyKind::Required,
37            lookup_mode: ServiceKeyLookupMode::NullKey,
38        }
39    }
40    pub fn keyed<S: ?Sized + 'static>(key: Option<ServiceKey>) -> Self {
41        Self {
42            lookup_mode: if key.is_some() {
43                ServiceKeyLookupMode::ExplicitKey
44            } else {
45                ServiceKeyLookupMode::NullKey
46            },
47            identifier: ServiceIdentifier::keyed::<S>(key),
48            kind: DependencyKind::Required,
49        }
50    }
51    pub fn inherited<S: ?Sized + 'static>() -> Self {
52        Self {
53            lookup_mode: ServiceKeyLookupMode::InheritKey,
54            ..Self::required::<S>()
55        }
56    }
57    pub fn optional(mut self) -> Self {
58        self.kind = DependencyKind::Optional;
59        self
60    }
61    pub fn enumerable(mut self) -> Self {
62        self.kind = DependencyKind::Enumerable;
63        self
64    }
65    pub(crate) fn identifier_for(&self, key: &Option<ServiceKey>) -> ServiceIdentifier {
66        let mut id = self.identifier.clone();
67        if self.lookup_mode == ServiceKeyLookupMode::InheritKey {
68            id.service_key = key.clone();
69        }
70        id
71    }
72}
73
74pub type ErasedFactory =
75    Rc<dyn Fn(&ServiceProvider, Option<&ServiceKey>) -> ServiceResult<ErasedService>>;
76pub(crate) type Enumerate = fn(&ErasedService) -> Vec<ErasedService>;
77pub(crate) type Dispose = Rc<dyn Fn(&ErasedService)>;
78pub(crate) type DisposeAsync = Rc<dyn Fn(&ErasedService) -> Task<()>>;
79
80/// A registration. Construct typed factories with `new` or `from_factory`; erased registration
81/// exists for adapters. Instance registrations are externally owned and never disposed.
82#[derive(Clone)]
83pub struct ServiceDescriptor {
84    pub(crate) identity: Rc<()>,
85    pub(crate) enumerable: Option<(ServiceType, Enumerate)>,
86    instance_value: Option<ErasedService>,
87    pub(crate) identifier: ServiceIdentifier,
88    pub(crate) lifetime: ServiceLifetime,
89    pub(crate) factory: ErasedFactory,
90    pub(crate) dependencies: Vec<Dependency>,
91    pub(crate) implementation_type: Option<ServiceType>,
92    pub(crate) dispose: Option<Dispose>,
93    pub(crate) dispose_async: Option<DisposeAsync>,
94    pub(crate) instance: bool,
95}
96impl ServiceDescriptor {
97    pub fn new<S: ?Sized + 'static, F>(lifetime: ServiceLifetime, factory: F) -> Self
98    where
99        F: Fn(&ServiceProvider) -> ServiceResult<Rc<S>> + 'static,
100    {
101        Self::keyed::<S, _>(lifetime, None, move |p, _| factory(p))
102    }
103    pub fn keyed<S: ?Sized + 'static, F>(
104        lifetime: ServiceLifetime,
105        key: Option<ServiceKey>,
106        factory: F,
107    ) -> Self
108    where
109        F: Fn(&ServiceProvider, Option<&ServiceKey>) -> ServiceResult<Rc<S>> + 'static,
110    {
111        Self::from_erased(ServiceIdentifier::keyed::<S>(key), lifetime, move |p, k| {
112            factory(p, k).map(erase_service)
113        })
114    }
115    pub fn from_erased(
116        identifier: ServiceIdentifier,
117        lifetime: ServiceLifetime,
118        factory: impl Fn(&ServiceProvider, Option<&ServiceKey>) -> ServiceResult<ErasedService>
119            + 'static,
120    ) -> Self {
121        Self {
122            identity: Rc::new(()),
123            enumerable: None,
124            instance_value: None,
125            identifier,
126            lifetime,
127            factory: Rc::new(factory),
128            dependencies: Vec::new(),
129            implementation_type: None,
130            dispose: None,
131            dispose_async: None,
132            instance: false,
133        }
134    }
135    pub fn instance<S: ?Sized + 'static>(value: Rc<S>) -> Self {
136        let instance = erase_service(value.clone());
137        let mut descriptor = Self::new(ServiceLifetime::Singleton, move |_| Ok(value.clone()));
138        descriptor.instance_value = Some(instance);
139        descriptor.instance = true;
140        descriptor
141    }
142    /// Explicit enumerable registration overrides the collection synthesized from individual services.
143    pub fn enumerable_instance<S: ?Sized + 'static>(values: Vec<Rc<S>>) -> Self {
144        let mut descriptor = Self::instance(Rc::new(values));
145        descriptor.enumerable = Some((ServiceType::of::<S>(), |value| {
146            unerase_service::<Vec<Rc<S>>>(value)
147                .expect("typed enumerable")
148                .iter()
149                .cloned()
150                .map(erase_service)
151                .collect()
152        }));
153        descriptor
154    }
155    pub fn from_factory<S: ?Sized + 'static>(
156        lifetime: ServiceLifetime,
157        factory: ObjectFactory<S>,
158    ) -> Self {
159        let mut descriptor = Self::keyed(lifetime, None, move |p, k| (factory.factory)(p, k));
160        descriptor.dependencies = factory.dependencies;
161        descriptor
162    }
163    pub fn with_key(mut self, key: Option<ServiceKey>) -> Self {
164        self.identifier.service_key = key;
165        self
166    }
167    pub fn with_dependencies(mut self, dependencies: Vec<Dependency>) -> Self {
168        self.dependencies = dependencies;
169        self
170    }
171    /// Identity used by TryAddEnumerable to distinguish implementations of the same service.
172    pub fn with_implementation<I: ?Sized + 'static>(mut self) -> Self {
173        self.implementation_type = Some(ServiceType::of::<I>());
174        self
175    }
176    pub fn with_disposal<S: ?Sized + IDisposable + 'static>(self) -> Self {
177        self.on_dispose::<S>(|value| value.dispose())
178    }
179    pub fn with_async_disposal<S: ?Sized + IAsyncDisposable + 'static>(self) -> Self {
180        self.on_dispose_async::<S>(|value| value.dispose_async())
181    }
182    pub fn on_dispose<S: ?Sized + 'static>(mut self, dispose: impl Fn(&S) + 'static) -> Self {
183        assert_eq!(
184            self.service_type(),
185            ServiceType::of::<S>(),
186            "disposer must match service type"
187        );
188        assert!(
189            !self.instance,
190            "externally supplied instances are not owned by the provider"
191        );
192        self.dispose = Some(Rc::new(move |v| {
193            dispose(&*unerase_service::<S>(v).expect("typed factory"))
194        }));
195        self
196    }
197    pub fn on_dispose_async<S: ?Sized + 'static>(
198        mut self,
199        dispose: impl Fn(Rc<S>) -> Task<()> + 'static,
200    ) -> Self {
201        assert_eq!(
202            self.service_type(),
203            ServiceType::of::<S>(),
204            "disposer must match service type"
205        );
206        assert!(
207            !self.instance,
208            "externally supplied instances are not owned by the provider"
209        );
210        self.dispose_async = Some(Rc::new(move |v| {
211            dispose(unerase_service::<S>(v).expect("typed factory"))
212        }));
213        self
214    }
215    pub fn implementation_type(&self) -> Option<ServiceType> {
216        self.implementation_type
217    }
218    pub fn implementation_instance(&self) -> Option<ErasedService> {
219        self.instance_value.clone()
220    }
221    pub fn implementation_factory(&self) -> Option<crate::service_descriptor::ErasedFactory> {
222        if self.instance {
223            None
224        } else {
225            Some(self.factory.clone())
226        }
227    }
228    pub fn service_type(&self) -> ServiceType {
229        self.identifier.service_type
230    }
231    pub fn service_key(&self) -> Option<&ServiceKey> {
232        self.identifier.service_key.as_ref()
233    }
234    pub fn lifetime(&self) -> ServiceLifetime {
235        self.lifetime
236    }
237    pub fn is_keyed_service(&self) -> bool {
238        self.service_key().is_some()
239    }
240    pub fn dependencies(&self) -> &[Dependency] {
241        &self.dependencies
242    }
243}
244
245impl std::fmt::Debug for ServiceDescriptor {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        f.debug_struct("ServiceDescriptor")
248            .field("identifier", &self.identifier)
249            .field("lifetime", &self.lifetime)
250            .field("implementation_type", &self.implementation_type)
251            .field("instance", &self.instance)
252            .finish()
253    }
254}
255impl std::fmt::Display for ServiceDescriptor {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        write!(f, "{self:?}")
258    }
259}