Skip to main content

ferro_rs/container/
provider.rs

1//! Service auto-registration for Ferro framework
2//!
3//! This module provides automatic service registration via macros:
4//! - `#[service(ConcreteType)]` - auto-register trait bindings
5//! - `#[derive(Injectable)]` - auto-register concrete types as singletons
6//!
7//! # Example - Trait binding
8//!
9//! ```rust,ignore
10//! use ferro_rs::service;
11//!
12//! // Auto-register: dyn CacheStore → RedisCache
13//! #[service(RedisCache)]
14//! pub trait CacheStore: Send + Sync + 'static {
15//!     fn get(&self, key: &str) -> Option<String>;
16//!     fn set(&self, key: &str, value: &str);
17//! }
18//!
19//! pub struct RedisCache;
20//! impl Default for RedisCache {
21//!     fn default() -> Self { Self }
22//! }
23//! impl CacheStore for RedisCache { ... }
24//! ```
25//!
26//! # Example - Concrete singleton
27//!
28//! ```rust,ignore
29//! use ferro_rs::injectable;
30//!
31//! #[injectable]
32//! pub struct AppState {
33//!     pub counter: u32,
34//! }
35//!
36//! // Resolve via:
37//! let state: AppState = App::get().unwrap();
38//! ```
39
40/// Entry for inventory-collected service bindings (trait → impl)
41///
42/// Used internally by the `#[service(ConcreteType)]` macro to register
43/// service bindings at compile time.
44pub struct ServiceBindingEntry {
45    /// Function to register the service binding
46    pub register: fn(),
47    /// Service name for debugging/logging
48    pub name: &'static str,
49}
50
51/// Entry for inventory-collected singleton registrations (concrete types)
52///
53/// Used internally by the `#[derive(Injectable)]` macro to register
54/// concrete singletons at compile time.
55pub struct SingletonEntry {
56    /// Function to register the singleton
57    pub register: fn(),
58    /// Type name for debugging/logging
59    pub name: &'static str,
60}
61
62// Inventory collection for auto-registered service bindings
63inventory::collect!(ServiceBindingEntry);
64
65// Inventory collection for auto-registered singletons
66inventory::collect!(SingletonEntry);
67
68/// Register all service bindings from inventory
69///
70/// This is called automatically by `Server::from_config()`.
71/// It registers all services marked with `#[service(ConcreteType)]`.
72pub fn register_service_bindings() {
73    for entry in inventory::iter::<ServiceBindingEntry> {
74        (entry.register)();
75    }
76}
77
78/// Register all singleton entries from inventory
79///
80/// This is called automatically by `Server::from_config()`.
81/// It registers all types marked with `#[derive(Injectable)]`.
82pub fn register_singletons() {
83    for entry in inventory::iter::<SingletonEntry> {
84        (entry.register)();
85    }
86}
87
88/// Full bootstrap sequence for services
89///
90/// Called automatically by `Server::from_config()`.
91pub fn bootstrap() {
92    register_service_bindings();
93    register_singletons();
94}
95
96/// Service info for introspection
97#[derive(Debug, Clone, serde::Serialize)]
98pub struct ServiceInfo {
99    /// Service name (trait or concrete type)
100    pub name: String,
101    /// Type of binding
102    pub binding_type: ServiceBindingType,
103}
104
105/// Type of service binding
106#[derive(Debug, Clone, serde::Serialize)]
107#[serde(rename_all = "snake_case")]
108pub enum ServiceBindingType {
109    /// Trait binding (dyn Trait → Impl)
110    TraitBinding,
111    /// Concrete singleton
112    Singleton,
113}
114
115/// Get all registered services for introspection
116///
117/// Returns a list of all services registered via `#[service(ConcreteType)]`
118/// and `#[injectable]` macros.
119pub fn get_registered_services() -> Vec<ServiceInfo> {
120    let mut services = Vec::new();
121
122    // Collect trait bindings
123    for entry in inventory::iter::<ServiceBindingEntry> {
124        services.push(ServiceInfo {
125            name: entry.name.to_string(),
126            binding_type: ServiceBindingType::TraitBinding,
127        });
128    }
129
130    // Collect singletons
131    for entry in inventory::iter::<SingletonEntry> {
132        services.push(ServiceInfo {
133            name: entry.name.to_string(),
134            binding_type: ServiceBindingType::Singleton,
135        });
136    }
137
138    services
139}