elif_core/container/
binding.rs

1use crate::container::autowiring::Injectable;
2use crate::container::descriptor::{ServiceDescriptor, ServiceDescriptorFactoryBuilder, ServiceId};
3use crate::container::scope::ServiceScope;
4use crate::errors::CoreError;
5
6/// Conditional binding function type
7pub type ConditionFn = Box<dyn Fn() -> bool + Send + Sync>;
8
9/// Environment condition type
10pub type EnvCondition = (&'static str, String);
11
12/// Binding configuration for advanced features
13pub struct BindingConfig {
14    /// Named/tagged identifier
15    pub name: Option<String>,
16    /// Service lifetime
17    pub lifetime: ServiceScope,
18    /// Environment-based conditions
19    pub env_conditions: Vec<EnvCondition>,
20    /// Feature flag conditions
21    pub feature_conditions: Vec<(String, bool)>,
22    /// Custom condition functions
23    pub conditions: Vec<ConditionFn>,
24    /// Whether this is the default implementation
25    pub is_default: bool,
26    /// Profile-based conditions
27    pub profile_conditions: Vec<String>,
28}
29
30impl Default for BindingConfig {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl BindingConfig {
37    pub fn new() -> Self {
38        Self {
39            name: None,
40            lifetime: ServiceScope::Transient,
41            env_conditions: Vec::new(),
42            feature_conditions: Vec::new(),
43            conditions: Vec::new(),
44            is_default: false,
45            profile_conditions: Vec::new(),
46        }
47    }
48
49    /// Check if all conditions are met
50    pub fn evaluate_conditions(&self) -> bool {
51        // Check environment conditions
52        for (key, expected_value) in &self.env_conditions {
53            if let Ok(actual_value) = std::env::var(key) {
54                if actual_value != *expected_value {
55                    return false;
56                }
57            } else {
58                return false;
59            }
60        }
61
62        // Check feature conditions
63        for (feature, expected) in &self.feature_conditions {
64            let feature_enabled =
65                std::env::var(format!("FEATURE_{}", feature.to_uppercase())).is_ok();
66            if feature_enabled != *expected {
67                return false;
68            }
69        }
70
71        // Check profile conditions
72        if !self.profile_conditions.is_empty() {
73            let current_profile =
74                std::env::var("PROFILE").unwrap_or_else(|_| "development".to_string());
75            if !self.profile_conditions.contains(&current_profile) {
76                return false;
77            }
78        }
79
80        // Check custom conditions
81        for condition in &self.conditions {
82            if !condition() {
83                return false;
84            }
85        }
86
87        true
88    }
89}
90
91/// Advanced binding builder for fluent configuration
92pub struct AdvancedBindingBuilder<TInterface: ?Sized + 'static> {
93    config: BindingConfig,
94    _phantom: std::marker::PhantomData<*const TInterface>,
95}
96
97impl<TInterface: ?Sized + 'static> Default for AdvancedBindingBuilder<TInterface> {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl<TInterface: ?Sized + 'static> AdvancedBindingBuilder<TInterface> {
104    pub fn new() -> Self {
105        Self {
106            config: BindingConfig::new(),
107            _phantom: std::marker::PhantomData,
108        }
109    }
110
111    /// Set service name/tag
112    pub fn named(mut self, name: impl Into<String>) -> Self {
113        self.config.name = Some(name.into());
114        self
115    }
116
117    /// Set service lifetime
118    pub fn with_lifetime(mut self, lifetime: ServiceScope) -> Self {
119        self.config.lifetime = lifetime;
120        self
121    }
122
123    /// Add environment condition
124    pub fn when_env(mut self, key: &'static str, value: impl Into<String>) -> Self {
125        self.config.env_conditions.push((key, value.into()));
126        self
127    }
128
129    /// Add feature flag condition
130    pub fn when_feature(mut self, feature: impl Into<String>) -> Self {
131        self.config.feature_conditions.push((feature.into(), true));
132        self
133    }
134
135    /// Add inverse feature flag condition
136    pub fn when_not_feature(mut self, feature: impl Into<String>) -> Self {
137        self.config.feature_conditions.push((feature.into(), false));
138        self
139    }
140
141    /// Add custom condition
142    pub fn when<F>(mut self, condition: F) -> Self
143    where
144        F: Fn() -> bool + Send + Sync + 'static,
145    {
146        self.config.conditions.push(Box::new(condition));
147        self
148    }
149
150    /// Mark as default implementation
151    pub fn as_default(mut self) -> Self {
152        self.config.is_default = true;
153        self
154    }
155
156    /// Add profile condition
157    pub fn in_profile(mut self, profile: impl Into<String>) -> Self {
158        self.config.profile_conditions.push(profile.into());
159        self
160    }
161
162    /// Get the configuration
163    pub fn config(self) -> BindingConfig {
164        self.config
165    }
166}
167
168/// Binding API for the IoC container
169pub trait ServiceBinder {
170    /// Add a pre-built service descriptor
171    fn add_service_descriptor(
172        &mut self,
173        descriptor: crate::container::descriptor::ServiceDescriptor,
174    ) -> Result<&mut Self, crate::errors::CoreError>;
175
176    /// Bind an interface to an implementation
177    fn bind<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
178        &mut self,
179    ) -> &mut Self;
180
181    /// Bind an interface to an implementation with singleton lifetime
182    fn bind_singleton<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
183        &mut self,
184    ) -> &mut Self;
185
186    /// Bind an interface to an implementation with transient lifetime
187    fn bind_transient<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
188        &mut self,
189    ) -> &mut Self;
190
191    /// Bind a service using a factory function
192    fn bind_factory<TInterface: ?Sized + 'static, F, T>(&mut self, factory: F) -> &mut Self
193    where
194        F: Fn() -> Result<T, CoreError> + Send + Sync + 'static,
195        T: Send + Sync + 'static;
196
197    /// Bind a pre-created instance
198    fn bind_instance<TInterface: ?Sized + 'static, TImpl: Send + Sync + Clone + 'static>(
199        &mut self,
200        instance: TImpl,
201    ) -> &mut Self;
202
203    /// Bind a named service
204    fn bind_named<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
205        &mut self,
206        name: &str,
207    ) -> &mut Self;
208
209    /// Bind an Injectable service with auto-wiring
210    fn bind_injectable<T: Injectable>(&mut self) -> &mut Self;
211
212    /// Bind an Injectable service as singleton with auto-wiring  
213    fn bind_injectable_singleton<T: Injectable>(&mut self) -> &mut Self;
214
215    // Advanced binding methods
216
217    /// Advanced bind with fluent configuration - returns builder for chaining
218    fn bind_with<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
219        &mut self,
220    ) -> AdvancedBindingBuilder<TInterface>;
221
222    /// Complete advanced binding with implementation
223    fn with_implementation<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
224        &mut self,
225        config: BindingConfig,
226    ) -> &mut Self;
227
228    /// Bind a lazy service using factory that gets called only when needed
229    fn bind_lazy<TInterface: ?Sized + 'static, F, T>(&mut self, factory: F) -> &mut Self
230    where
231        F: Fn() -> T + Send + Sync + 'static,
232        T: Send + Sync + 'static;
233
234    /// Bind with parameterized factory
235    fn bind_parameterized_factory<TInterface: ?Sized + 'static, P, F, T>(
236        &mut self,
237        factory: F,
238    ) -> &mut Self
239    where
240        F: Fn(P) -> Result<T, CoreError> + Send + Sync + 'static,
241        T: Send + Sync + 'static,
242        P: Send + Sync + 'static;
243
244    /// Bind a collection of services using a closure-based configuration
245    fn bind_collection<TInterface: ?Sized + 'static, F>(&mut self, configure: F) -> &mut Self
246    where
247        F: FnOnce(&mut CollectionBindingBuilder<TInterface>);
248}
249
250/// Builder for collection bindings that works with closure-based configuration
251pub struct CollectionBindingBuilder<TInterface: ?Sized + 'static> {
252    services: Vec<ServiceDescriptor>,
253    _phantom: std::marker::PhantomData<*const TInterface>,
254}
255
256impl<TInterface: ?Sized + 'static> Default for CollectionBindingBuilder<TInterface> {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262impl<TInterface: ?Sized + 'static> CollectionBindingBuilder<TInterface> {
263    pub fn new() -> Self {
264        Self {
265            services: Vec::new(),
266            _phantom: std::marker::PhantomData,
267        }
268    }
269
270    /// Add a service to the collection
271    pub fn add<TImpl: Send + Sync + Default + 'static>(&mut self) -> &mut Self {
272        let descriptor = ServiceDescriptor::bind::<TInterface, TImpl>()
273            .with_lifetime(ServiceScope::Transient)
274            .build();
275        self.services.push(descriptor);
276        self
277    }
278
279    /// Add a named service to the collection
280    pub fn add_named<TImpl: Send + Sync + Default + 'static>(
281        &mut self,
282        name: impl Into<String>,
283    ) -> &mut Self {
284        let descriptor = ServiceDescriptor::bind_named::<TInterface, TImpl>(name)
285            .with_lifetime(ServiceScope::Transient)
286            .build();
287        self.services.push(descriptor);
288        self
289    }
290
291    /// Add a service with singleton lifetime
292    pub fn add_singleton<TImpl: Send + Sync + Default + 'static>(&mut self) -> &mut Self {
293        let descriptor = ServiceDescriptor::bind::<TInterface, TImpl>()
294            .with_lifetime(ServiceScope::Singleton)
295            .build();
296        self.services.push(descriptor);
297        self
298    }
299
300    /// Add a named singleton service
301    pub fn add_named_singleton<TImpl: Send + Sync + Default + 'static>(
302        &mut self,
303        name: impl Into<String>,
304    ) -> &mut Self {
305        let descriptor = ServiceDescriptor::bind_named::<TInterface, TImpl>(name)
306            .with_lifetime(ServiceScope::Singleton)
307            .build();
308        self.services.push(descriptor);
309        self
310    }
311
312    /// Get the collection of service descriptors (internal use)
313    pub(crate) fn into_services(self) -> Vec<ServiceDescriptor> {
314        self.services
315    }
316}
317
318/// Collection of service bindings
319#[derive(Debug)]
320pub struct ServiceBindings {
321    descriptors: Vec<ServiceDescriptor>,
322}
323
324impl ServiceBindings {
325    /// Create a new service bindings collection
326    pub fn new() -> Self {
327        Self {
328            descriptors: Vec::new(),
329        }
330    }
331
332    /// Add a service descriptor
333    pub fn add_descriptor(&mut self, descriptor: ServiceDescriptor) {
334        self.descriptors.push(descriptor);
335    }
336
337    /// Get all service descriptors
338    pub fn descriptors(&self) -> &[ServiceDescriptor] {
339        &self.descriptors
340    }
341
342    /// Get service descriptors by service ID
343    pub fn get_descriptor(&self, service_id: &ServiceId) -> Option<&ServiceDescriptor> {
344        self.descriptors
345            .iter()
346            .find(|d| d.service_id == *service_id)
347    }
348
349    /// Get service descriptor by type and name without allocation
350    pub fn get_descriptor_named<T: 'static + ?Sized>(
351        &self,
352        name: &str,
353    ) -> Option<&ServiceDescriptor> {
354        self.descriptors
355            .iter()
356            .find(|d| d.service_id.matches_named::<T>(name))
357    }
358
359    /// Get all service IDs
360    pub fn service_ids(&self) -> Vec<ServiceId> {
361        self.descriptors
362            .iter()
363            .map(|d| d.service_id.clone())
364            .collect()
365    }
366
367    /// Check if a service is registered
368    pub fn contains(&self, service_id: &ServiceId) -> bool {
369        self.descriptors.iter().any(|d| d.service_id == *service_id)
370    }
371
372    /// Check if a named service is registered without allocation
373    pub fn contains_named<T: 'static + ?Sized>(&self, name: &str) -> bool {
374        self.descriptors
375            .iter()
376            .any(|d| d.service_id.matches_named::<T>(name))
377    }
378
379    /// Get the number of registered services
380    pub fn count(&self) -> usize {
381        self.descriptors.len()
382    }
383
384    /// Consume self and return all descriptors
385    pub fn into_descriptors(self) -> Vec<ServiceDescriptor> {
386        self.descriptors
387    }
388}
389
390impl Default for ServiceBindings {
391    fn default() -> Self {
392        Self::new()
393    }
394}
395
396impl ServiceBinder for ServiceBindings {
397    fn add_service_descriptor(
398        &mut self,
399        descriptor: crate::container::descriptor::ServiceDescriptor,
400    ) -> Result<&mut Self, crate::errors::CoreError> {
401        self.add_descriptor(descriptor);
402        Ok(self)
403    }
404
405    fn bind<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
406        &mut self,
407    ) -> &mut Self {
408        let descriptor = ServiceDescriptor::bind::<TInterface, TImpl>()
409            .with_lifetime(ServiceScope::Transient)
410            .build();
411        self.add_descriptor(descriptor);
412        self
413    }
414
415    fn bind_singleton<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
416        &mut self,
417    ) -> &mut Self {
418        let descriptor = ServiceDescriptor::bind::<TInterface, TImpl>()
419            .with_lifetime(ServiceScope::Singleton)
420            .build();
421        self.add_descriptor(descriptor);
422        self
423    }
424
425    fn bind_transient<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
426        &mut self,
427    ) -> &mut Self {
428        let descriptor = ServiceDescriptor::bind::<TInterface, TImpl>()
429            .with_lifetime(ServiceScope::Transient)
430            .build();
431        self.add_descriptor(descriptor);
432        self
433    }
434
435    fn bind_factory<TInterface: ?Sized + 'static, F, T>(&mut self, factory: F) -> &mut Self
436    where
437        F: Fn() -> Result<T, CoreError> + Send + Sync + 'static,
438        T: Send + Sync + 'static,
439    {
440        let descriptor = ServiceDescriptorFactoryBuilder::<TInterface>::new()
441            .with_factory(factory)
442            .build()
443            .expect("Failed to build factory descriptor");
444        self.add_descriptor(descriptor);
445        self
446    }
447
448    fn bind_instance<TInterface: ?Sized + 'static, TImpl: Send + Sync + Clone + 'static>(
449        &mut self,
450        instance: TImpl,
451    ) -> &mut Self {
452        let descriptor = ServiceDescriptorFactoryBuilder::<TInterface>::new()
453            .with_lifetime(ServiceScope::Singleton)
454            .with_factory({
455                let instance = instance.clone();
456                move || -> Result<TImpl, CoreError> { Ok(instance.clone()) }
457            })
458            .build()
459            .expect("Failed to build instance descriptor");
460        self.add_descriptor(descriptor);
461        self
462    }
463
464    fn bind_named<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
465        &mut self,
466        name: &str,
467    ) -> &mut Self {
468        let descriptor = ServiceDescriptor::bind_named::<TInterface, TImpl>(name)
469            .with_lifetime(ServiceScope::Transient)
470            .build();
471        self.add_descriptor(descriptor);
472        self
473    }
474
475    fn bind_injectable<T: Injectable>(&mut self) -> &mut Self {
476        let dependencies = T::dependencies();
477        let descriptor = ServiceDescriptor::autowired::<T>(dependencies);
478        self.add_descriptor(descriptor);
479        self
480    }
481
482    fn bind_injectable_singleton<T: Injectable>(&mut self) -> &mut Self {
483        let dependencies = T::dependencies();
484        let descriptor = ServiceDescriptor::autowired_singleton::<T>(dependencies);
485        self.add_descriptor(descriptor);
486        self
487    }
488
489    // Advanced binding methods implementation
490
491    fn bind_with<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
492        &mut self,
493    ) -> AdvancedBindingBuilder<TInterface> {
494        AdvancedBindingBuilder::new()
495    }
496
497    fn with_implementation<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
498        &mut self,
499        config: BindingConfig,
500    ) -> &mut Self {
501        // Only add binding if conditions are met
502        if config.evaluate_conditions() {
503            let mut builder = if let Some(name) = &config.name {
504                ServiceDescriptor::bind_named::<TInterface, TImpl>(name.clone())
505            } else {
506                ServiceDescriptor::bind::<TInterface, TImpl>()
507            };
508
509            builder = builder.with_lifetime(config.lifetime);
510            let descriptor = builder.build();
511            self.add_descriptor(descriptor);
512        }
513        self
514    }
515
516    fn bind_lazy<TInterface: ?Sized + 'static, F, T>(&mut self, factory: F) -> &mut Self
517    where
518        F: Fn() -> T + Send + Sync + 'static,
519        T: Send + Sync + 'static,
520    {
521        let lazy_factory = move || -> Result<T, CoreError> { Ok(factory()) };
522
523        let descriptor = ServiceDescriptorFactoryBuilder::<TInterface>::new()
524            .with_factory(lazy_factory)
525            .build()
526            .expect("Failed to build lazy factory descriptor");
527        self.add_descriptor(descriptor);
528        self
529    }
530
531    fn bind_parameterized_factory<TInterface: ?Sized + 'static, P, F, T>(
532        &mut self,
533        _factory: F,
534    ) -> &mut Self
535    where
536        F: Fn(P) -> Result<T, CoreError> + Send + Sync + 'static,
537        T: Send + Sync + 'static,
538        P: Send + Sync + 'static,
539    {
540        // For now, parameterized factory stores the factory but requires parameter injection
541        // This is a complex feature that would need parameter resolution at runtime
542        let descriptor = ServiceDescriptorFactoryBuilder::<TInterface>::new()
543            .with_factory(move || -> Result<T, CoreError> {
544                // This would need to be resolved at runtime with proper parameter injection
545                // For now, this is a placeholder implementation
546                Err(CoreError::ServiceNotFound {
547                    service_type: format!(
548                        "Parameterized factory for {} requires runtime parameter resolution",
549                        std::any::type_name::<TInterface>()
550                    ),
551                })
552            })
553            .build()
554            .expect("Failed to build parameterized factory descriptor");
555        self.add_descriptor(descriptor);
556        self
557    }
558
559    fn bind_collection<TInterface: ?Sized + 'static, F>(&mut self, configure: F) -> &mut Self
560    where
561        F: FnOnce(&mut CollectionBindingBuilder<TInterface>),
562    {
563        let mut builder = CollectionBindingBuilder::new();
564        configure(&mut builder);
565        let services = builder.into_services();
566        for service in services {
567            self.add_descriptor(service);
568        }
569        self
570    }
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576    use serial_test::serial;
577
578    #[allow(dead_code)]
579    trait TestRepository: Send + Sync {
580        fn find(&self, id: u32) -> Option<String>;
581    }
582
583    #[derive(Default)]
584    struct PostgresRepository;
585
586    unsafe impl Send for PostgresRepository {}
587    unsafe impl Sync for PostgresRepository {}
588
589    impl TestRepository for PostgresRepository {
590        fn find(&self, _id: u32) -> Option<String> {
591            Some("postgres".to_string())
592        }
593    }
594
595    #[allow(dead_code)]
596    trait TestService: Send + Sync {
597        fn get_data(&self) -> String;
598    }
599
600    #[derive(Default)]
601    struct UserService;
602
603    unsafe impl Send for UserService {}
604    unsafe impl Sync for UserService {}
605
606    impl TestService for UserService {
607        fn get_data(&self) -> String {
608            "user_data".to_string()
609        }
610    }
611
612    #[test]
613    fn test_service_bindings() {
614        let mut bindings = ServiceBindings::new();
615
616        bindings
617            .bind::<PostgresRepository, PostgresRepository>()
618            .bind_singleton::<UserService, UserService>()
619            .bind_named::<PostgresRepository, PostgresRepository>("postgres");
620
621        assert_eq!(bindings.count(), 3);
622
623        let service_ids = bindings.service_ids();
624        assert_eq!(service_ids.len(), 3);
625
626        // Check that we have the expected services
627        assert!(bindings.contains(&ServiceId::of::<PostgresRepository>()));
628        assert!(bindings.contains(&ServiceId::of::<UserService>()));
629        assert!(bindings.contains(&ServiceId::named::<PostgresRepository>("postgres")));
630    }
631
632    #[test]
633    fn test_factory_binding() {
634        let mut bindings = ServiceBindings::new();
635
636        bindings.bind_factory::<UserService, _, _>(|| Ok(UserService::default()));
637
638        assert_eq!(bindings.count(), 1);
639        assert!(bindings.contains(&ServiceId::of::<UserService>()));
640    }
641
642    #[test]
643    #[serial]
644    fn test_advanced_binding_with_environment_conditions() {
645        let mut bindings = ServiceBindings::new();
646
647        // Set up environment for test
648        std::env::set_var("CACHE_PROVIDER", "redis");
649
650        let config = AdvancedBindingBuilder::<dyn TestRepository>::new()
651            .named("redis")
652            .when_env("CACHE_PROVIDER", "redis")
653            .with_lifetime(ServiceScope::Singleton)
654            .config();
655
656        bindings.with_implementation::<dyn TestRepository, PostgresRepository>(config);
657
658        assert_eq!(bindings.count(), 1);
659        assert!(bindings.contains_named::<dyn TestRepository>("redis"));
660
661        // Clean up environment
662        std::env::remove_var("CACHE_PROVIDER");
663    }
664
665    #[test]
666    fn test_conditional_binding_not_met() {
667        let mut bindings = ServiceBindings::new();
668
669        // Environment condition not met
670        let config = AdvancedBindingBuilder::<dyn TestRepository>::new()
671            .named("nonexistent")
672            .when_env("NON_EXISTENT_VAR", "value")
673            .config();
674
675        bindings.with_implementation::<dyn TestRepository, PostgresRepository>(config);
676
677        // Should not add binding since condition is not met
678        assert_eq!(bindings.count(), 0);
679    }
680
681    #[test]
682    #[serial]
683    fn test_feature_flag_conditions() {
684        let mut bindings = ServiceBindings::new();
685
686        // Set up feature flag
687        std::env::set_var("FEATURE_ADVANCED_CACHE", "1");
688
689        let config = AdvancedBindingBuilder::<dyn TestRepository>::new()
690            .when_feature("advanced_cache")
691            .config();
692
693        bindings.with_implementation::<dyn TestRepository, PostgresRepository>(config);
694
695        assert_eq!(bindings.count(), 1);
696
697        // Clean up
698        std::env::remove_var("FEATURE_ADVANCED_CACHE");
699    }
700
701    #[test]
702    #[serial]
703    fn test_profile_conditions() {
704        let mut bindings = ServiceBindings::new();
705
706        // Test with development profile
707        std::env::set_var("PROFILE", "development");
708
709        let config = AdvancedBindingBuilder::<dyn TestService>::new()
710            .in_profile("development")
711            .config();
712
713        bindings.with_implementation::<dyn TestService, UserService>(config);
714
715        assert_eq!(bindings.count(), 1);
716
717        // Test with production profile (should not bind)
718        std::env::set_var("PROFILE", "production");
719
720        let config2 = AdvancedBindingBuilder::<dyn TestRepository>::new()
721            .in_profile("development")
722            .config();
723
724        bindings.with_implementation::<dyn TestRepository, PostgresRepository>(config2);
725
726        // Should still be 1, not 2
727        assert_eq!(bindings.count(), 1);
728
729        // Clean up
730        std::env::remove_var("PROFILE");
731    }
732
733    #[test]
734    fn test_custom_conditions() {
735        let mut bindings = ServiceBindings::new();
736
737        let config = AdvancedBindingBuilder::<dyn TestService>::new()
738            .when(|| true) // Always true
739            .config();
740
741        bindings.with_implementation::<dyn TestService, UserService>(config);
742
743        assert_eq!(bindings.count(), 1);
744
745        let config2 = AdvancedBindingBuilder::<dyn TestRepository>::new()
746            .when(|| false) // Always false
747            .config();
748
749        bindings.with_implementation::<dyn TestRepository, PostgresRepository>(config2);
750
751        // Should still be 1, not 2
752        assert_eq!(bindings.count(), 1);
753    }
754
755    #[test]
756    fn test_lazy_binding() {
757        let mut bindings = ServiceBindings::new();
758
759        bindings.bind_lazy::<UserService, _, _>(|| UserService::default());
760
761        assert_eq!(bindings.count(), 1);
762        assert!(bindings.contains(&ServiceId::of::<UserService>()));
763    }
764
765    #[test]
766    fn test_collection_binding() {
767        let mut bindings = ServiceBindings::new();
768
769        // Use the new closure-based API that actually registers services
770        bindings.bind_collection::<dyn TestService, _>(|collection| {
771            collection
772                .add::<UserService>()
773                .add_named::<UserService>("named_user_service");
774        });
775
776        // Verify that the services were actually registered in the bindings
777        assert_eq!(bindings.count(), 2);
778        assert!(bindings.contains(&ServiceId::of::<dyn TestService>()));
779        assert!(bindings.contains(&ServiceId::named::<dyn TestService>("named_user_service")));
780    }
781
782    #[test]
783    #[serial]
784    fn test_multiple_conditions() {
785        let mut bindings = ServiceBindings::new();
786
787        // Set up multiple conditions
788        std::env::set_var("ENV_VAR", "test_value");
789        std::env::set_var("FEATURE_TEST", "1");
790        std::env::set_var("PROFILE", "test");
791
792        let config = AdvancedBindingBuilder::<dyn TestService>::new()
793            .when_env("ENV_VAR", "test_value")
794            .when_feature("test")
795            .in_profile("test")
796            .when(|| true)
797            .named("complex_service")
798            .with_lifetime(ServiceScope::Singleton)
799            .config();
800
801        bindings.with_implementation::<dyn TestService, UserService>(config);
802
803        assert_eq!(bindings.count(), 1);
804        assert!(bindings.contains_named::<dyn TestService>("complex_service"));
805
806        // Clean up
807        std::env::remove_var("ENV_VAR");
808        std::env::remove_var("FEATURE_TEST");
809        std::env::remove_var("PROFILE");
810    }
811}