elif_core/providers/
provider.rs

1use crate::container::{Container, ContainerBuilder};
2use crate::errors::CoreError;
3
4/// Provider error type
5#[derive(Debug, thiserror::Error)]
6pub enum ProviderError {
7    #[error("Circular dependency detected in provider: {provider}")]
8    CircularDependency { provider: String },
9
10    #[error("Missing dependency '{dependency}' for provider '{provider}'")]
11    MissingDependency {
12        provider: String,
13        dependency: String,
14    },
15
16    #[error("Provider registration failed: {message}")]
17    RegistrationFailed { message: String },
18
19    #[error("Provider boot failed: {message}")]
20    BootFailed { message: String },
21
22    #[error("Container error: {0}")]
23    Container(#[from] CoreError),
24}
25
26/// Service provider trait for registering services and managing lifecycle
27pub trait ServiceProvider: Send + Sync {
28    /// Provider name for identification and dependency resolution
29    fn name(&self) -> &'static str;
30
31    /// Register services in the container builder
32    /// This is called during the registration phase
33    fn register(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ProviderError>;
34
35    /// Boot the provider after all services are registered
36    /// This is called during the boot phase with access to the built container
37    fn boot(&self, container: &Container) -> Result<(), ProviderError> {
38        // Default implementation does nothing
39        let _ = container; // Suppress unused parameter warning
40        Ok(())
41    }
42
43    /// Provider dependencies (other providers that must be registered first)
44    fn dependencies(&self) -> Vec<&'static str> {
45        vec![]
46    }
47
48    /// Defer boot phase (useful for providers that need other providers to be booted first)
49    fn defer_boot(&self) -> bool {
50        false
51    }
52
53    /// Provider version for compatibility checking
54    fn version(&self) -> Option<&'static str> {
55        None
56    }
57
58    /// Provider description
59    fn description(&self) -> Option<&'static str> {
60        None
61    }
62
63    /// Check if this provider is optional
64    fn is_optional(&self) -> bool {
65        true
66    }
67}
68
69/// Provider metadata for introspection
70#[derive(Debug, Clone)]
71pub struct ProviderMetadata {
72    pub name: String,
73    pub version: Option<String>,
74    pub description: Option<String>,
75    pub dependencies: Vec<String>,
76    pub defer_boot: bool,
77    pub is_optional: bool,
78}
79
80impl ProviderMetadata {
81    /// Create metadata from a provider
82    pub fn from_provider<P: ServiceProvider + ?Sized>(provider: &P) -> Self {
83        Self {
84            name: provider.name().to_string(),
85            version: provider.version().map(|v| v.to_string()),
86            description: provider.description().map(|d| d.to_string()),
87            dependencies: provider
88                .dependencies()
89                .iter()
90                .map(|d| d.to_string())
91                .collect(),
92            defer_boot: provider.defer_boot(),
93            is_optional: provider.is_optional(),
94        }
95    }
96}
97
98/// Base provider implementation for common functionality
99#[derive(Debug)]
100pub struct BaseProvider {
101    name: &'static str,
102    version: Option<&'static str>,
103    description: Option<&'static str>,
104    dependencies: Vec<&'static str>,
105    defer_boot: bool,
106    is_optional: bool,
107}
108
109impl BaseProvider {
110    /// Create a new base provider
111    pub fn new(name: &'static str) -> Self {
112        Self {
113            name,
114            version: None,
115            description: None,
116            dependencies: Vec::new(),
117            defer_boot: false,
118            is_optional: true,
119        }
120    }
121
122    /// Set provider version
123    pub fn with_version(mut self, version: &'static str) -> Self {
124        self.version = Some(version);
125        self
126    }
127
128    /// Set provider description
129    pub fn with_description(mut self, description: &'static str) -> Self {
130        self.description = Some(description);
131        self
132    }
133
134    /// Set provider dependencies
135    pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
136        self.dependencies = dependencies;
137        self
138    }
139
140    /// Set defer boot flag
141    pub fn with_defer_boot(mut self, defer_boot: bool) -> Self {
142        self.defer_boot = defer_boot;
143        self
144    }
145
146    /// Set if provider is optional
147    pub fn with_optional(mut self, is_optional: bool) -> Self {
148        self.is_optional = is_optional;
149        self
150    }
151}
152
153impl ServiceProvider for BaseProvider {
154    fn name(&self) -> &'static str {
155        self.name
156    }
157
158    fn register(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ProviderError> {
159        // Base provider doesn't register anything by default
160        Ok(builder)
161    }
162
163    fn dependencies(&self) -> Vec<&'static str> {
164        self.dependencies.clone()
165    }
166
167    fn defer_boot(&self) -> bool {
168        self.defer_boot
169    }
170
171    fn version(&self) -> Option<&'static str> {
172        self.version
173    }
174
175    fn description(&self) -> Option<&'static str> {
176        self.description
177    }
178
179    fn is_optional(&self) -> bool {
180        self.is_optional
181    }
182}
183
184/// Macro to simplify provider creation
185#[macro_export]
186macro_rules! provider {
187    (
188        name: $name:expr,
189        $(version: $version:expr,)?
190        $(description: $description:expr,)?
191        $(dependencies: [$($dep:expr),* $(,)?],)?
192        $(defer_boot: $defer:expr,)?
193        $(optional: $optional:expr,)?
194        register: |$builder:ident| $register:block
195        $(, boot: |$container:ident| $boot:block)?
196    ) => {
197        {
198            struct CustomProvider;
199
200            impl $crate::providers::ServiceProvider for CustomProvider {
201                fn name(&self) -> &'static str {
202                    $name
203                }
204
205                $(fn version(&self) -> Option<&'static str> {
206                    Some($version)
207                })?
208
209                $(fn description(&self) -> Option<&'static str> {
210                    Some($description)
211                })?
212
213                $(fn dependencies(&self) -> Vec<&'static str> {
214                    vec![$($dep),*]
215                })?
216
217                $(fn defer_boot(&self) -> bool {
218                    $defer
219                })?
220
221                $(fn is_optional(&self) -> bool {
222                    $optional
223                })?
224
225                fn register(&self, $builder: $crate::container::ContainerBuilder)
226                    -> Result<$crate::container::ContainerBuilder, $crate::providers::ProviderError>
227                {
228                    $register
229                }
230
231                $(fn boot(&self, $container: &$crate::container::Container)
232                    -> Result<(), $crate::providers::ProviderError>
233                {
234                    $boot
235                })?
236            }
237
238            CustomProvider
239        }
240    };
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_provider_metadata() {
249        let base_provider = BaseProvider::new("test_provider")
250            .with_version("1.0.0")
251            .with_description("A test provider")
252            .with_dependencies(vec!["dependency1", "dependency2"])
253            .with_defer_boot(true)
254            .with_optional(false);
255
256        let metadata = ProviderMetadata::from_provider(&base_provider);
257
258        assert_eq!(metadata.name, "test_provider");
259        assert_eq!(metadata.version, Some("1.0.0".to_string()));
260        assert_eq!(metadata.description, Some("A test provider".to_string()));
261        assert_eq!(metadata.dependencies, vec!["dependency1", "dependency2"]);
262        assert!(metadata.defer_boot);
263        assert!(!metadata.is_optional);
264    }
265}