use crate::container::{Container, ContainerBuilder};
use crate::errors::CoreError;
#[derive(Debug, thiserror::Error)]
pub enum ProviderError {
#[error("Circular dependency detected in provider: {provider}")]
CircularDependency { provider: String },
#[error("Missing dependency '{dependency}' for provider '{provider}'")]
MissingDependency {
provider: String,
dependency: String,
},
#[error("Provider registration failed: {message}")]
RegistrationFailed { message: String },
#[error("Provider boot failed: {message}")]
BootFailed { message: String },
#[error("Container error: {0}")]
Container(#[from] CoreError),
}
pub trait ServiceProvider: Send + Sync {
fn name(&self) -> &'static str;
fn register(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ProviderError>;
fn boot(&self, container: &Container) -> Result<(), ProviderError> {
let _ = container; Ok(())
}
fn dependencies(&self) -> Vec<&'static str> {
vec![]
}
fn defer_boot(&self) -> bool {
false
}
fn version(&self) -> Option<&'static str> {
None
}
fn description(&self) -> Option<&'static str> {
None
}
fn is_optional(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct ProviderMetadata {
pub name: String,
pub version: Option<String>,
pub description: Option<String>,
pub dependencies: Vec<String>,
pub defer_boot: bool,
pub is_optional: bool,
}
impl ProviderMetadata {
pub fn from_provider<P: ServiceProvider + ?Sized>(provider: &P) -> Self {
Self {
name: provider.name().to_string(),
version: provider.version().map(|v| v.to_string()),
description: provider.description().map(|d| d.to_string()),
dependencies: provider
.dependencies()
.iter()
.map(|d| d.to_string())
.collect(),
defer_boot: provider.defer_boot(),
is_optional: provider.is_optional(),
}
}
}
#[derive(Debug)]
pub struct BaseProvider {
name: &'static str,
version: Option<&'static str>,
description: Option<&'static str>,
dependencies: Vec<&'static str>,
defer_boot: bool,
is_optional: bool,
}
impl BaseProvider {
pub fn new(name: &'static str) -> Self {
Self {
name,
version: None,
description: None,
dependencies: Vec::new(),
defer_boot: false,
is_optional: true,
}
}
pub fn with_version(mut self, version: &'static str) -> Self {
self.version = Some(version);
self
}
pub fn with_description(mut self, description: &'static str) -> Self {
self.description = Some(description);
self
}
pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
self.dependencies = dependencies;
self
}
pub fn with_defer_boot(mut self, defer_boot: bool) -> Self {
self.defer_boot = defer_boot;
self
}
pub fn with_optional(mut self, is_optional: bool) -> Self {
self.is_optional = is_optional;
self
}
}
impl ServiceProvider for BaseProvider {
fn name(&self) -> &'static str {
self.name
}
fn register(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ProviderError> {
Ok(builder)
}
fn dependencies(&self) -> Vec<&'static str> {
self.dependencies.clone()
}
fn defer_boot(&self) -> bool {
self.defer_boot
}
fn version(&self) -> Option<&'static str> {
self.version
}
fn description(&self) -> Option<&'static str> {
self.description
}
fn is_optional(&self) -> bool {
self.is_optional
}
}
#[macro_export]
macro_rules! provider {
(
name: $name:expr,
$(version: $version:expr,)?
$(description: $description:expr,)?
$(dependencies: [$($dep:expr),* $(,)?],)?
$(defer_boot: $defer:expr,)?
$(optional: $optional:expr,)?
register: |$builder:ident| $register:block
$(, boot: |$container:ident| $boot:block)?
) => {
{
struct CustomProvider;
impl $crate::providers::ServiceProvider for CustomProvider {
fn name(&self) -> &'static str {
$name
}
$(fn version(&self) -> Option<&'static str> {
Some($version)
})?
$(fn description(&self) -> Option<&'static str> {
Some($description)
})?
$(fn dependencies(&self) -> Vec<&'static str> {
vec![$($dep),*]
})?
$(fn defer_boot(&self) -> bool {
$defer
})?
$(fn is_optional(&self) -> bool {
$optional
})?
fn register(&self, $builder: $crate::container::ContainerBuilder)
-> Result<$crate::container::ContainerBuilder, $crate::providers::ProviderError>
{
$register
}
$(fn boot(&self, $container: &$crate::container::Container)
-> Result<(), $crate::providers::ProviderError>
{
$boot
})?
}
CustomProvider
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_provider_metadata() {
let base_provider = BaseProvider::new("test_provider")
.with_version("1.0.0")
.with_description("A test provider")
.with_dependencies(vec!["dependency1", "dependency2"])
.with_defer_boot(true)
.with_optional(false);
let metadata = ProviderMetadata::from_provider(&base_provider);
assert_eq!(metadata.name, "test_provider");
assert_eq!(metadata.version, Some("1.0.0".to_string()));
assert_eq!(metadata.description, Some("A test provider".to_string()));
assert_eq!(metadata.dependencies, vec!["dependency1", "dependency2"]);
assert!(metadata.defer_boot);
assert!(!metadata.is_optional);
}
}