1use std::any::{Any, TypeId};
8use std::cell::RefCell;
9use std::collections::HashMap;
10#[cfg(feature = "reload")]
11use std::rc::Rc;
12use std::sync::OnceLock;
13
14use crate::core::{AutoBuilder, BuildFn};
15use crate::error::TraitKitError;
16use crate::i18n::tr;
17
18#[cfg(feature = "encryption")]
19use super::EncryptedBlob;
20use super::TypeMap;
21use super::{DependencyGraph, GraphError, ModuleEntry};
22
23#[cfg(feature = "lifecycle")]
24type ShutdownCallback = Box<dyn Fn(&TypeMap)>;
25#[cfg(feature = "lifecycle")]
26type ReadyCallback = Box<dyn Fn(&Kit<Ready>) -> Result<(), TraitKitError>>;
27#[cfg(feature = "health")]
28type HealthCheckerFn = Box<dyn Fn(&TypeMap) -> crate::core::health::HealthStatus>;
29#[cfg(feature = "observer")]
30type ObserverRef = std::sync::Arc<dyn crate::core::observer::BuildObserver>;
31#[cfg(feature = "decorator")]
32type DecoratorFn = Box<dyn Fn(Box<dyn Any>) -> Box<dyn Any>>;
33
34#[cfg(feature = "encryption")]
37const KEY_DERIVATION_VERSION: &str = "v1";
38
39#[cfg(feature = "encryption")]
41fn derive_kit_field_key(
42 master_key: &[u8],
43 path: &'static str,
44 context: &'static str,
45) -> Result<[u8; 32], TraitKitError> {
46 super::config::derive_field_key(master_key, path, KEY_DERIVATION_VERSION).map_err(|e| {
47 TraitKitError::BuildFailed {
48 context: context.to_string(),
49 source: Box::new(e),
50 }
51 })
52}
53
54pub struct Unbuilt;
56
57pub struct Ready;
59
60#[cfg(feature = "reload")]
62type SubscriberMap = RefCell<HashMap<TypeId, Vec<Rc<dyn Fn()>>>>;
63
64#[cfg(feature = "encryption")]
66type EncryptedConfigMap = RefCell<HashMap<TypeId, EncryptedBlob>>;
67
68pub(crate) struct LazySlot {
75 pub(crate) builder: Option<BuildFn>,
76 pub(crate) cell: OnceLock<Box<dyn Any>>,
77}
78
79pub struct Kit<S = Unbuilt> {
81 builders: RefCell<HashMap<TypeId, BuildFn>>,
82 overrides: RefCell<HashMap<TypeId, Box<dyn Any>>>,
85 lazy_builders: RefCell<HashMap<TypeId, BuildFn>>,
88 lazy_slots: RefCell<HashMap<TypeId, LazySlot>>,
91 multi_builders: RefCell<HashMap<TypeId, Vec<BuildFn>>>,
97 multi_capabilities: RefCell<HashMap<TypeId, Vec<Box<dyn Any>>>>,
101 #[cfg(feature = "interface")]
106 interface_builders: RefCell<HashMap<TypeId, BuildFn>>,
107 graph: DependencyGraph,
108 configs: TypeMap,
109 capabilities: TypeMap,
110 #[cfg(feature = "reload")]
111 subscribers: SubscriberMap,
112 #[cfg(feature = "encryption")]
113 encrypted_configs: EncryptedConfigMap,
114 #[cfg(feature = "confers")]
115 config_snapshots: RefCell<HashMap<TypeId, Box<dyn Any>>>,
116 #[cfg(feature = "toggle")]
117 toggles: RefCell<HashMap<String, bool>>,
118 #[cfg(feature = "lifecycle")]
119 shutdown_callbacks: RefCell<Vec<(TypeId, ShutdownCallback)>>,
120 #[cfg(feature = "lifecycle")]
121 ready_callbacks: RefCell<Vec<(TypeId, ReadyCallback)>>,
122 #[cfg(feature = "health")]
123 health_checkers: RefCell<HashMap<TypeId, (&'static str, HealthCheckerFn)>>,
124 #[cfg(feature = "observer")]
125 observers: RefCell<Vec<ObserverRef>>,
126 #[cfg(feature = "decorator")]
127 decorators: RefCell<HashMap<TypeId, Vec<DecoratorFn>>>,
128 #[cfg(feature = "decorator")]
132 decorator_module_to_cap: RefCell<HashMap<TypeId, TypeId>>,
133 _state: std::marker::PhantomData<S>,
134}
135
136impl Kit {
137 #[must_use]
139 pub fn new() -> Self {
140 Kit {
141 builders: RefCell::new(HashMap::new()),
142 overrides: RefCell::new(HashMap::new()),
143 lazy_builders: RefCell::new(HashMap::new()),
144 lazy_slots: RefCell::new(HashMap::new()),
145 multi_builders: RefCell::new(HashMap::new()),
146 multi_capabilities: RefCell::new(HashMap::new()),
147 #[cfg(feature = "interface")]
148 interface_builders: RefCell::new(HashMap::new()),
149 graph: DependencyGraph::new(),
150 configs: TypeMap::new(),
151 capabilities: TypeMap::new(),
152 #[cfg(feature = "reload")]
153 subscribers: RefCell::new(HashMap::new()),
154 #[cfg(feature = "encryption")]
155 encrypted_configs: RefCell::new(HashMap::new()),
156 #[cfg(feature = "confers")]
157 config_snapshots: RefCell::new(HashMap::new()),
158 #[cfg(feature = "toggle")]
159 toggles: RefCell::new(HashMap::new()),
160 #[cfg(feature = "lifecycle")]
161 shutdown_callbacks: RefCell::new(Vec::new()),
162 #[cfg(feature = "lifecycle")]
163 ready_callbacks: RefCell::new(Vec::new()),
164 #[cfg(feature = "health")]
165 health_checkers: RefCell::new(HashMap::new()),
166 #[cfg(feature = "observer")]
167 observers: RefCell::new(Vec::new()),
168 #[cfg(feature = "decorator")]
169 decorators: RefCell::new(HashMap::new()),
170 #[cfg(feature = "decorator")]
171 decorator_module_to_cap: RefCell::new(HashMap::new()),
172 _state: std::marker::PhantomData,
173 }
174 }
175
176 pub fn register<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError> {
182 let entry = ModuleEntry {
183 type_id: TypeId::of::<M>(),
184 name: M::NAME,
185 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
186 };
187
188 self.graph
189 .add(entry)
190 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
191
192 let build_fn: BuildFn = Box::new(|kit| {
193 let capability = M::build(kit)
194 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
195 Ok(Box::new(capability) as Box<dyn Any>)
196 });
197
198 self.builders
199 .borrow_mut()
200 .insert(TypeId::of::<M>(), build_fn);
201 Ok(())
202 }
203
204 pub fn register_lazy<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError>
221 where
222 M::Capability: Clone + 'static,
223 {
224 let entry = ModuleEntry {
225 type_id: TypeId::of::<M>(),
226 name: M::NAME,
227 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
228 };
229
230 self.graph
231 .add(entry)
232 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
233
234 let build_fn: BuildFn = Box::new(|kit| {
235 let capability = M::build(kit)
236 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
237 Ok(Box::new(capability) as Box<dyn Any>)
238 });
239
240 self.lazy_builders
241 .borrow_mut()
242 .insert(TypeId::of::<M>(), build_fn);
243 Ok(())
244 }
245
246 pub fn register_multi<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError>
268 where
269 M::Capability: Clone + 'static,
270 {
271 let entry = ModuleEntry {
272 type_id: TypeId::of::<M>(),
273 name: M::NAME,
274 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
275 };
276
277 self.graph
278 .add(entry)
279 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
280
281 let build_fn: BuildFn = Box::new(|kit| {
282 let capability = M::build(kit)
283 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
284 Ok(Box::new(capability) as Box<dyn Any>)
285 });
286
287 let cap_id = TypeId::of::<M::Capability>();
290 self.multi_builders
291 .borrow_mut()
292 .entry(cap_id)
293 .or_default()
294 .push(build_fn);
295 Ok(())
296 }
297
298 #[cfg(feature = "interface")]
316 pub fn register_as<M>(&mut self) -> Result<(), TraitKitError>
317 where
318 M: crate::core::InterfaceBuilder,
319 {
320 let interface_id = TypeId::of::<M::Interface>();
321
322 if self.interface_builders.borrow().contains_key(&interface_id) {
324 return Err(TraitKitError::AlreadyRegistered { module: M::NAME });
325 }
326
327 let entry = ModuleEntry {
328 type_id: TypeId::of::<M>(),
329 name: M::NAME,
330 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
331 };
332
333 self.graph
334 .add(entry)
335 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
336
337 let build_fn: BuildFn = Box::new(|kit| {
338 let cap = M::build(kit)
339 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
340 let iface: std::sync::Arc<M::Interface> = M::into_interface(cap);
341 Ok(Box::new(iface) as Box<dyn Any>)
342 });
343
344 self.interface_builders
345 .borrow_mut()
346 .insert(interface_id, build_fn);
347 Ok(())
348 }
349
350 pub fn override_module<M: AutoBuilder>(&self, capability: M::Capability)
360 where
361 M::Capability: 'static,
362 {
363 self.overrides
364 .borrow_mut()
365 .insert(TypeId::of::<M>(), Box::new(capability));
366 }
367
368 pub fn override_module_strict<M: AutoBuilder>(
384 &mut self,
385 capability: M::Capability,
386 ) -> Result<(), TraitKitError>
387 where
388 M::Capability: 'static,
389 {
390 for (dep_name, dep_id) in M::dependencies() {
391 if self.graph.name_of(*dep_id).is_none() {
392 return Err(TraitKitError::DependencyMissing {
393 module: M::NAME,
394 missing: dep_name,
395 });
396 }
397 }
398 self.overrides
399 .borrow_mut()
400 .insert(TypeId::of::<M>(), Box::new(capability));
401 Ok(())
402 }
403
404 pub fn set_config<C: Clone + 'static>(&self, config: C) {
406 self.configs.insert(config);
407 }
408
409 #[cfg(feature = "confers")]
419 pub fn load_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
420 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
421 context: "load_config".into(),
422 source: e,
423 })?;
424 self.set_config(config);
425 Ok(())
426 }
427
428 #[cfg(feature = "confers")]
439 pub fn load_and_validate<C>(&self) -> Result<(), TraitKitError>
440 where
441 C: super::Configurable + super::Validatable,
442 {
443 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
444 context: "load_and_validate".into(),
445 source: e,
446 })?;
447 match config.validate() {
448 Ok(()) => {
449 self.set_config(config);
450 Ok(())
451 }
452 Err(errors) => Err(TraitKitError::BuildFailed {
453 context: "load_and_validate".into(),
454 source: Box::new(super::ValidationError { errors }),
455 }),
456 }
457 }
458
459 #[cfg(feature = "confers")]
465 pub fn snapshot_config<C: Clone + 'static>(&self) -> bool {
466 if let Some(config) = self.configs.get_cloned::<C>() {
467 self.config_snapshots
468 .borrow_mut()
469 .insert(TypeId::of::<C>(), Box::new(config));
470 true
471 } else {
472 false
473 }
474 }
475
476 #[cfg(feature = "confers")]
485 pub fn restore_config<C: Clone + 'static>(&self) -> Result<(), TraitKitError> {
486 let snapshots = self.config_snapshots.borrow();
487 let boxed =
488 snapshots
489 .get(&TypeId::of::<C>())
490 .ok_or_else(|| TraitKitError::MissingConfig {
491 key: format!("{} (snapshot)", std::any::type_name::<C>()),
492 })?;
493 let config =
494 boxed
495 .downcast_ref::<C>()
496 .cloned()
497 .ok_or_else(|| TraitKitError::MissingConfig {
498 key: format!("{} (snapshot downcast)", std::any::type_name::<C>()),
499 })?;
500 drop(snapshots);
501 self.set_config(config);
502 Ok(())
503 }
504
505 #[cfg(feature = "confers")]
507 pub fn has_snapshot<C: 'static>(&self) -> bool {
508 self.config_snapshots
509 .borrow()
510 .contains_key(&TypeId::of::<C>())
511 }
512
513 #[cfg(feature = "confers")]
527 pub fn load_config_with<C, S: std::hash::BuildHasher>(
528 &self,
529 vars: &std::collections::HashMap<String, String, S>,
530 ) -> Result<(), TraitKitError>
531 where
532 C: super::Configurable + serde::Serialize + serde::de::DeserializeOwned,
533 {
534 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
535 context: "load_config_with".into(),
536 source: e,
537 })?;
538 let mut json_value =
539 serde_json::to_value(&config).map_err(|e| TraitKitError::BuildFailed {
540 context: "load_config_with (serialize)".into(),
541 source: Box::new(e),
542 })?;
543 super::config::interpolate_json_value(&mut json_value, vars);
544 let interpolated: C =
545 serde_json::from_value(json_value).map_err(|e| TraitKitError::BuildFailed {
546 context: "load_config_with (deserialize)".into(),
547 source: Box::new(e),
548 })?;
549 self.set_config(interpolated);
550 Ok(())
551 }
552
553 pub fn build(self) -> Result<Kit<Ready>, TraitKitError> {
564 let sorted = match self.graph.validate() {
565 Ok(sorted) => sorted,
566 Err(GraphError::DependencyMissing { module, missing }) => {
567 return Err(TraitKitError::DependencyMissing { module, missing });
568 }
569 Err(GraphError::CycleDetected { cycle }) => {
570 return Err(TraitKitError::CycleDetected { cycle });
571 }
572 };
573
574 self.build_eager_modules(&sorted)?;
576
577 self.transfer_lazy_builders();
579
580 self.build_multi_bindings()?;
582
583 #[cfg(feature = "interface")]
585 self.build_interface_modules()?;
586
587 #[cfg(feature = "lifecycle")]
589 let ready_callbacks: Vec<(TypeId, ReadyCallback)> =
590 { self.ready_callbacks.borrow_mut().drain(..).collect() };
591 #[cfg(feature = "lifecycle")]
592 let shutdown_callbacks: Vec<(TypeId, ShutdownCallback)> =
593 { self.shutdown_callbacks.borrow_mut().drain(..).collect() };
594
595 let kit = Kit {
596 builders: self.builders,
597 overrides: self.overrides,
598 lazy_builders: self.lazy_builders,
599 lazy_slots: self.lazy_slots,
600 multi_builders: self.multi_builders,
601 multi_capabilities: self.multi_capabilities,
602 #[cfg(feature = "interface")]
603 interface_builders: self.interface_builders,
604 graph: self.graph,
605 configs: self.configs,
606 capabilities: self.capabilities,
607 #[cfg(feature = "reload")]
608 subscribers: self.subscribers,
609 #[cfg(feature = "encryption")]
610 encrypted_configs: self.encrypted_configs,
611 #[cfg(feature = "confers")]
612 config_snapshots: self.config_snapshots,
613 #[cfg(feature = "toggle")]
614 toggles: self.toggles,
615 #[cfg(feature = "lifecycle")]
616 shutdown_callbacks: RefCell::new(shutdown_callbacks),
617 #[cfg(feature = "lifecycle")]
618 ready_callbacks: RefCell::new(Vec::new()),
619 #[cfg(feature = "health")]
620 health_checkers: self.health_checkers,
621 #[cfg(feature = "observer")]
622 observers: self.observers,
623 #[cfg(feature = "decorator")]
624 decorators: self.decorators,
625 #[cfg(feature = "decorator")]
626 decorator_module_to_cap: self.decorator_module_to_cap,
627 _state: std::marker::PhantomData,
628 };
629
630 #[cfg(feature = "lifecycle")]
632 {
633 for (_type_id, callback) in &ready_callbacks {
634 callback(&kit)?;
635 }
636 }
637
638 Ok(kit)
639 }
640
641 fn build_eager_modules(&self, sorted: &[TypeId]) -> Result<(), TraitKitError> {
649 for type_id in sorted {
650 let module_name = self.module_name(*type_id);
651
652 if let Some(boxed) = self.overrides.borrow_mut().remove(type_id) {
654 self.capabilities.insert_boxed(*type_id, boxed);
655 continue;
656 }
657
658 if self.lazy_builders.borrow().contains_key(type_id) {
660 continue;
661 }
662
663 let Some(build_fn) = self.builders.borrow_mut().remove(type_id) else {
665 continue;
666 };
667
668 #[cfg(feature = "observer")]
670 {
671 let start_instant = std::time::Instant::now();
672 for obs in self.observers.borrow().iter() {
673 obs.on_module_start(module_name);
674 }
675
676 match (build_fn)(self) {
677 Ok(boxed) => {
678 let elapsed = start_instant.elapsed();
679 #[cfg(feature = "decorator")]
680 let boxed = {
681 let cap_type_id = self
682 .decorator_module_to_cap
683 .borrow()
684 .get(type_id)
685 .copied()
686 .unwrap_or(*type_id);
687 self.apply_decorators(cap_type_id, boxed)
688 };
689 self.capabilities.insert_boxed(*type_id, boxed);
690 for obs in self.observers.borrow().iter() {
691 obs.on_module_built(module_name, elapsed);
692 }
693 }
694 Err(e) => {
695 let err = TraitKitError::BuildFailed {
696 context: module_name.to_string(),
697 source: e,
698 };
699 for obs in self.observers.borrow().iter() {
700 obs.on_build_error(module_name, &err);
701 }
702 return Err(err);
703 }
704 }
705 }
706
707 #[cfg(not(feature = "observer"))]
708 {
709 match (build_fn)(self) {
710 Ok(boxed) => {
711 #[cfg(feature = "decorator")]
712 let boxed = {
713 let cap_type_id = self
714 .decorator_module_to_cap
715 .borrow()
716 .get(type_id)
717 .copied()
718 .unwrap_or(*type_id);
719 self.apply_decorators(cap_type_id, boxed)
720 };
721 self.capabilities.insert_boxed(*type_id, boxed);
722 }
723 Err(e) => {
724 return Err(TraitKitError::BuildFailed {
725 context: module_name.to_string(),
726 source: e,
727 });
728 }
729 }
730 }
731 }
732
733 let remaining: Vec<(TypeId, Box<dyn Any>)> = self.overrides.borrow_mut().drain().collect();
735 for (type_id, boxed) in remaining {
736 self.capabilities.insert_boxed(type_id, boxed);
737 }
738 Ok(())
739 }
740
741 fn transfer_lazy_builders(&self) {
743 let lazy: Vec<(TypeId, BuildFn)> = self.lazy_builders.borrow_mut().drain().collect();
744 self.lazy_slots.borrow_mut().reserve(lazy.len());
745 for (type_id, builder) in lazy {
746 self.lazy_slots.borrow_mut().insert(
747 type_id,
748 LazySlot {
749 builder: Some(builder),
750 cell: OnceLock::new(),
751 },
752 );
753 }
754 }
755
756 fn build_multi_bindings(&self) -> Result<(), TraitKitError> {
758 let multi: Vec<(TypeId, Vec<BuildFn>)> = self.multi_builders.borrow_mut().drain().collect();
759 for (cap_id, build_fns) in multi {
760 let mut vec = Vec::with_capacity(build_fns.len());
761 for build_fn in build_fns {
762 let boxed = (build_fn)(self).map_err(|e| TraitKitError::BuildFailed {
763 context: tr("trait-kit-diag-multi-binding", &[]),
764 source: e,
765 })?;
766 #[cfg(feature = "decorator")]
767 let boxed = self.apply_decorators(cap_id, boxed);
768 vec.push(boxed);
769 }
770 self.multi_capabilities.borrow_mut().insert(cap_id, vec);
771 }
772 Ok(())
773 }
774
775 #[cfg(feature = "interface")]
777 fn build_interface_modules(&self) -> Result<(), TraitKitError> {
778 let interfaces: Vec<(TypeId, BuildFn)> =
779 self.interface_builders.borrow_mut().drain().collect();
780 for (interface_id, build_fn) in interfaces {
781 let boxed = (build_fn)(self).map_err(|e| TraitKitError::BuildFailed {
782 context: tr("trait-kit-diag-interface", &[]),
783 source: e,
784 })?;
785 #[cfg(feature = "decorator")]
786 let boxed = self.apply_decorators(interface_id, boxed);
787 self.capabilities.insert_boxed(interface_id, boxed);
788 }
789 Ok(())
790 }
791
792 fn module_name(&self, type_id: TypeId) -> &'static str {
793 self.graph.name_of(type_id).unwrap_or("<unknown>")
794 }
795
796 #[cfg(feature = "lifecycle")]
806 pub fn register_lifecycle<M>(&mut self)
807 where
808 M: crate::core::lifecycle::Lifecycle + 'static,
809 M::Capability: 'static,
810 {
811 let shutdown_cb: ShutdownCallback = Box::new(|caps: &TypeMap| {
813 let type_id = TypeId::of::<M>();
814 if let Some((_guard, cap_ref)) = caps.get_ref_by_type_id::<M::Capability>(type_id) {
815 M::on_shutdown(cap_ref);
816 }
817 });
818 self.shutdown_callbacks
819 .borrow_mut()
820 .push((TypeId::of::<M>(), shutdown_cb));
821
822 let ready_cb: ReadyCallback = Box::new(|kit: &Kit<Ready>| {
824 M::on_ready(kit).map_err(|e| TraitKitError::LifecycleFailed {
825 context: M::NAME.to_string(),
826 source: Box::new(e),
827 })
828 });
829 self.ready_callbacks
830 .borrow_mut()
831 .push((TypeId::of::<M>(), ready_cb));
832 }
833
834 #[cfg(feature = "health")]
843 pub fn register_health_check<M>(&mut self)
844 where
845 M: crate::core::health::HealthCheck + 'static,
846 M::Capability: 'static,
847 {
848 let checker: HealthCheckerFn = Box::new(|caps: &TypeMap| {
849 let type_id = TypeId::of::<M>();
850 match caps.get_ref_by_type_id::<M::Capability>(type_id) {
851 Some((_guard, cap_ref)) => M::check(cap_ref),
852 None => crate::core::health::HealthStatus::Unhealthy {
853 detail: "capability not found".to_string(),
854 },
855 }
856 });
857 self.health_checkers
858 .borrow_mut()
859 .insert(TypeId::of::<M>(), (M::NAME, checker));
860 }
861
862 pub fn register_if<M: AutoBuilder>(
874 &mut self,
875 predicate: impl FnOnce(&Kit) -> bool,
876 ) -> Result<bool, TraitKitError> {
877 if predicate(self) {
878 self.register::<M>()?;
879 Ok(true)
880 } else {
881 Ok(false)
882 }
883 }
884
885 #[cfg(feature = "toggle")]
892 pub fn enable_toggle(&self, key: impl Into<String>, enabled: bool) {
893 self.toggles.borrow_mut().insert(key.into(), enabled);
894 }
895
896 #[cfg(feature = "toggle")]
900 pub fn is_toggle_enabled(&self, key: &str) -> bool {
901 self.toggles.borrow().get(key).copied().unwrap_or(false)
902 }
903
904 #[cfg(feature = "toggle")]
914 pub fn register_if_toggle<M: AutoBuilder>(&mut self, key: &str) -> Result<bool, TraitKitError> {
915 let enabled = self.is_toggle_enabled(key);
916 if enabled {
917 self.register::<M>()?;
918 }
919 Ok(enabled)
920 }
921
922 #[cfg(feature = "observer")]
928 pub fn with_observer(
929 &mut self,
930 observer: std::sync::Arc<dyn crate::core::observer::BuildObserver>,
931 ) {
932 self.observers.borrow_mut().push(observer);
933 }
934
935 #[cfg(feature = "decorator")]
951 pub fn decorate<M: AutoBuilder>(
952 &self,
953 decorator: impl Fn(M::Capability) -> M::Capability + 'static,
954 ) where
955 M::Capability: 'static,
956 {
957 let wrapper: DecoratorFn = Box::new(move |boxed_cap| {
958 let cap = boxed_cap
959 .downcast::<M::Capability>()
960 .expect("decorator type mismatch");
961 let decorated = decorator(*cap);
962 Box::new(decorated) as Box<dyn Any>
963 });
964 self.decorators
965 .borrow_mut()
966 .entry(TypeId::of::<M::Capability>())
967 .or_default()
968 .push(wrapper);
969 self.decorator_module_to_cap
972 .borrow_mut()
973 .insert(TypeId::of::<M>(), TypeId::of::<M::Capability>());
974 }
975}
976
977impl<S> Kit<S> {
978 #[cfg(feature = "decorator")]
980 fn apply_decorators(&self, cap_type_id: TypeId, boxed: Box<dyn Any>) -> Box<dyn Any> {
981 let decorators = self.decorators.borrow();
982 let Some(dec_list) = decorators.get(&cap_type_id) else {
983 return boxed;
984 };
985 let mut current = boxed;
986 for dec in dec_list {
987 current = dec(current);
988 }
989 current
990 }
991
992 pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, TraitKitError> {
1008 let type_id = TypeId::of::<M>();
1009
1010 if let Some(cap) = self
1012 .capabilities
1013 .get_cloned_by_type_id::<M::Capability>(type_id)
1014 {
1015 return Ok(cap);
1016 }
1017
1018 if let Some(cached) = Self::get_lazy_cached::<M>(self, type_id) {
1020 return Ok(cached);
1021 }
1022
1023 let builder = self
1027 .lazy_slots
1028 .borrow_mut()
1029 .get_mut(&type_id)
1030 .and_then(|slot| slot.builder.take());
1031
1032 if let Some(builder) = builder {
1033 const _: () = assert!(
1045 std::mem::size_of::<Kit<Ready>>() == std::mem::size_of::<Kit>(),
1046 "Kit layout changed; unsafe cast is no longer sound"
1047 );
1048 #[allow(unsafe_code)]
1049 let kit_ref: &Kit = unsafe { &*std::ptr::from_ref(self).cast::<Kit>() };
1050 let boxed = (builder)(kit_ref).map_err(|e| TraitKitError::BuildFailed {
1051 context: M::NAME.to_string(),
1052 source: e,
1053 })?;
1054 #[cfg(feature = "decorator")]
1056 let boxed = self.apply_decorators(TypeId::of::<M::Capability>(), boxed);
1057 if let Some(slot) = self.lazy_slots.borrow().get(&type_id) {
1059 let _ = slot.cell.set(boxed);
1060 }
1061 return Self::get_lazy_cached::<M>(self, type_id).ok_or(
1062 TraitKitError::MissingCapability {
1063 key: M::NAME.to_string(),
1064 },
1065 );
1066 }
1067
1068 Err(TraitKitError::MissingCapability {
1070 key: M::NAME.to_string(),
1071 })
1072 }
1073
1074 fn get_lazy_cached<M: AutoBuilder>(&self, type_id: TypeId) -> Option<M::Capability> {
1077 self.lazy_slots
1078 .borrow()
1079 .get(&type_id)
1080 .and_then(|slot| slot.cell.get())
1081 .and_then(|b| b.downcast_ref::<M::Capability>().cloned())
1082 }
1083
1084 pub fn require_all<M: AutoBuilder>(&self) -> Result<Vec<M::Capability>, TraitKitError>
1096 where
1097 M::Capability: Clone + 'static,
1098 {
1099 let cap_id = TypeId::of::<M::Capability>();
1100 let multi = self.multi_capabilities.borrow();
1101 let vec = multi.get(&cap_id).ok_or(TraitKitError::MissingCapability {
1102 key: M::NAME.to_string(),
1103 })?;
1104
1105 let mut result = Vec::with_capacity(vec.len());
1106 for boxed in vec {
1107 let cap = boxed.downcast_ref::<M::Capability>().cloned().ok_or(
1108 TraitKitError::MissingCapability {
1109 key: M::NAME.to_string(),
1110 },
1111 )?;
1112 result.push(cap);
1113 }
1114 Ok(result)
1115 }
1116
1117 pub fn config<C: Clone + 'static>(&self) -> Result<C, TraitKitError> {
1123 self.configs
1124 .get_cloned::<C>()
1125 .ok_or(TraitKitError::MissingConfig {
1126 key: std::any::type_name::<C>().to_string(),
1127 })
1128 }
1129
1130 #[cfg(feature = "reload")]
1139 pub fn subscribe<C: 'static>(&self, callback: impl Fn() + 'static) {
1140 let callback: Rc<dyn Fn()> = Rc::new(callback);
1141 self.subscribers
1142 .borrow_mut()
1143 .entry(TypeId::of::<C>())
1144 .or_default()
1145 .push(callback);
1146 }
1147
1148 #[cfg(feature = "reload")]
1167 pub fn reload_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
1168 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
1169 context: "reload_config".into(),
1170 source: e,
1171 })?;
1172 self.configs.insert(config);
1173 let callbacks: Vec<Rc<dyn Fn()>> = match self.subscribers.borrow().get(&TypeId::of::<C>()) {
1176 Some(subs) => subs.iter().map(Rc::clone).collect(),
1177 None => Vec::new(),
1178 };
1179 for cb in &callbacks {
1180 cb();
1181 }
1182 Ok(())
1183 }
1184
1185 #[cfg(feature = "interface")]
1199 pub fn resolve<I>(&self) -> Result<std::sync::Arc<I>, TraitKitError>
1200 where
1201 I: ?Sized + 'static,
1202 {
1203 let interface_id = TypeId::of::<I>();
1204 self.capabilities
1205 .get_cloned_by_type_id::<std::sync::Arc<I>>(interface_id)
1206 .ok_or(TraitKitError::MissingCapability {
1207 key: "interface".into(),
1208 })
1209 }
1210}
1211
1212impl Kit {
1213 #[cfg(feature = "encryption")]
1229 pub fn set_encrypted<C>(&self, value: &C, master_key: &[u8]) -> Result<(), TraitKitError>
1230 where
1231 C: super::ModuleConfig + serde::Serialize,
1232 {
1233 use super::XChaCha20Crypto;
1234
1235 if master_key.len() < 16 {
1238 return Err(TraitKitError::BuildFailed {
1239 context: "set_encrypted".into(),
1240 source: Box::new(std::io::Error::new(
1241 std::io::ErrorKind::InvalidInput,
1242 format!(
1243 "master_key must be at least 16 bytes, got {}",
1244 master_key.len()
1245 ),
1246 )),
1247 });
1248 }
1249
1250 let plaintext = serde_json::to_vec(value).map_err(|e| TraitKitError::BuildFailed {
1251 context: "set_encrypted".into(),
1252 source: Box::new(e),
1253 })?;
1254
1255 let field_key = derive_kit_field_key(master_key, C::PATH, "set_encrypted")?;
1256
1257 let (nonce, ciphertext) = XChaCha20Crypto::new()
1258 .encrypt(&plaintext, &field_key)
1259 .map_err(|e| TraitKitError::BuildFailed {
1260 context: "set_encrypted".into(),
1261 source: Box::new(e),
1262 })?;
1263
1264 self.encrypted_configs
1265 .borrow_mut()
1266 .insert(TypeId::of::<C>(), EncryptedBlob::new(nonce, ciphertext));
1267 Ok(())
1268 }
1269
1270 #[cfg(feature = "encryption")]
1272 pub fn contains_encrypted<C: super::ModuleConfig>(&self) -> bool {
1273 self.encrypted_configs
1274 .borrow()
1275 .contains_key(&TypeId::of::<C>())
1276 }
1277
1278 #[cfg(feature = "confers")]
1295 pub fn load_config_or_default<C>(&self) -> Result<bool, TraitKitError>
1296 where
1297 C: super::Configurable + super::ModuleConfig,
1298 {
1299 match C::load() {
1300 Ok(value) => {
1301 self.set_config(value);
1302 Ok(true)
1303 }
1304 Err(_e) => {
1305 self.set_config(C::default_value());
1306 Ok(false)
1307 }
1308 }
1309 }
1310}
1311
1312impl Kit<Ready> {
1313 pub fn optional<M: AutoBuilder>(&self) -> Option<M::Capability> {
1315 let type_id = TypeId::of::<M>();
1316 self.capabilities
1317 .get_cloned_by_type_id::<M::Capability>(type_id)
1318 }
1319
1320 pub fn require_ref<M: AutoBuilder>(
1332 &self,
1333 ) -> Result<std::cell::Ref<'_, M::Capability>, TraitKitError>
1334 where
1335 M::Capability: 'static,
1336 {
1337 use std::cell::Ref;
1338
1339 let type_id = TypeId::of::<M>();
1340 if !self.capabilities.contains_by_type_id(type_id) {
1341 return Err(TraitKitError::MissingCapability {
1342 key: M::NAME.to_string(),
1343 });
1344 }
1345 Ref::filter_map(self.capabilities.inner_ref(), |map| {
1346 map.get(&type_id)
1347 .and_then(|b| b.downcast_ref::<M::Capability>())
1348 })
1349 .map_err(|_| TraitKitError::MissingCapability {
1350 key: M::NAME.to_string(),
1351 })
1352 }
1353
1354 pub fn contains<M: AutoBuilder>(&self) -> bool {
1356 self.capabilities.contains_by_type_id(TypeId::of::<M>())
1357 }
1358
1359 pub fn contains_config<C: Clone + 'static>(&self) -> bool {
1361 self.configs.contains::<C>()
1362 }
1363
1364 #[cfg(feature = "toggle")]
1368 pub fn is_toggle_enabled(&self, key: &str) -> bool {
1369 self.toggles.borrow().get(key).copied().unwrap_or(false)
1370 }
1371
1372 #[cfg(feature = "toggle")]
1374 pub fn enable_toggle(&self, key: impl Into<String>, enabled: bool) {
1375 self.toggles.borrow_mut().insert(key.into(), enabled);
1376 }
1377
1378 #[cfg(feature = "lifecycle")]
1387 pub fn shutdown(&self) {
1388 let callbacks: Vec<(TypeId, ShutdownCallback)> =
1389 self.shutdown_callbacks.borrow_mut().drain(..).collect();
1390 for (_type_id, callback) in callbacks.iter().rev() {
1392 callback(&self.capabilities);
1393 }
1394 }
1395
1396 #[cfg(feature = "health")]
1408 pub fn health_check<M: crate::core::health::HealthCheck>(
1409 &self,
1410 ) -> Result<crate::core::health::HealthStatus, TraitKitError> {
1411 let type_id = TypeId::of::<M>();
1412 let checkers = self.health_checkers.borrow();
1413 let (_name, checker) = checkers.get(&type_id).ok_or(TraitKitError::MissingConfig {
1414 key: M::NAME.to_string(),
1415 })?;
1416 Ok(checker(&self.capabilities))
1417 }
1418
1419 #[cfg(feature = "health")]
1425 pub fn health_report(&self) -> Vec<(&'static str, crate::core::health::HealthStatus)> {
1426 let checkers = self.health_checkers.borrow();
1427 checkers
1428 .values()
1429 .map(|(name, checker)| (*name, checker(&self.capabilities)))
1430 .collect()
1431 }
1432
1433 pub fn factory<M: AutoBuilder>(
1442 &self,
1443 ) -> impl Fn() -> Result<M::Capability, TraitKitError> + '_ {
1444 move || {
1445 #[allow(unsafe_code)]
1448 let kit_ref: &Kit = unsafe { &*std::ptr::from_ref::<Kit<Ready>>(self).cast::<Kit>() };
1449 M::build(kit_ref).map_err(|e| TraitKitError::BuildFailed {
1450 context: M::NAME.to_string(),
1451 source: Box::new(e),
1452 })
1453 }
1454 }
1455
1456 #[cfg(feature = "scope")]
1462 #[must_use]
1463 pub fn create_scope(&self) -> super::scope::Scope {
1464 super::scope::Scope::new()
1465 }
1466
1467 #[must_use]
1471 pub fn graph_dot(&self) -> String {
1472 self.graph.to_dot()
1473 }
1474
1475 #[must_use]
1477 pub fn graph_mermaid(&self) -> String {
1478 self.graph.to_mermaid()
1479 }
1480
1481 #[cfg(feature = "encryption")]
1494 pub fn get_encrypted<C>(&self, master_key: &[u8]) -> Result<C, TraitKitError>
1495 where
1496 C: super::ModuleConfig + serde::de::DeserializeOwned,
1497 {
1498 use super::XChaCha20Crypto;
1499
1500 if master_key.len() < 16 {
1501 return Err(TraitKitError::BuildFailed {
1502 context: "get_encrypted".into(),
1503 source: Box::new(std::io::Error::new(
1504 std::io::ErrorKind::InvalidInput,
1505 format!(
1506 "master_key must be at least 16 bytes, got {}",
1507 master_key.len()
1508 ),
1509 )),
1510 });
1511 }
1512
1513 let blob = self
1514 .encrypted_configs
1515 .borrow()
1516 .get(&TypeId::of::<C>())
1517 .cloned()
1518 .ok_or(TraitKitError::MissingConfig {
1519 key: std::any::type_name::<C>().to_string(),
1520 })?;
1521
1522 let field_key = derive_kit_field_key(master_key, C::PATH, "get_encrypted")?;
1523
1524 let plaintext = XChaCha20Crypto::new()
1525 .decrypt(blob.nonce(), blob.ciphertext(), &field_key)
1526 .map_err(|e| TraitKitError::BuildFailed {
1527 context: "get_encrypted".into(),
1528 source: Box::new(e),
1529 })?;
1530
1531 serde_json::from_slice(&plaintext).map_err(|e| TraitKitError::BuildFailed {
1532 context: "get_encrypted".into(),
1533 source: Box::new(e),
1534 })
1535 }
1536}
1537
1538impl Default for Kit {
1539 fn default() -> Self {
1540 Self::new()
1541 }
1542}
1543
1544impl std::fmt::Debug for Kit {
1545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1546 f.debug_struct("Kit<Unbuilt>")
1547 .field("modules", &self.graph.entries().len())
1548 .field("configs", &self.configs.len())
1549 .finish()
1550 }
1551}
1552
1553impl std::fmt::Debug for Kit<Ready> {
1554 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1555 f.debug_struct("Kit<Ready>")
1556 .field("modules", &self.graph.entries().len())
1557 .field("configs", &self.configs.len())
1558 .finish()
1559 }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564 use super::*;
1565 use crate::core::{AutoBuilder, ModuleMeta};
1566 use std::sync::Arc;
1567 use std::sync::atomic::{AtomicUsize, Ordering};
1568
1569 struct MockCapability;
1572 impl ModuleMeta for MockCapability {
1573 const NAME: &'static str = "mock";
1574 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1575 &[]
1576 }
1577 }
1578 impl AutoBuilder for MockCapability {
1579 type Capability = Arc<AtomicUsize>;
1580 type Error = TraitKitError;
1581 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1582 Ok(Arc::new(AtomicUsize::new(0)))
1583 }
1584 }
1585
1586 struct DependentModule;
1587 impl ModuleMeta for DependentModule {
1588 const NAME: &'static str = "dependent";
1589 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1590 static DEPS: &[(&str, std::any::TypeId)] =
1591 &[("mock", std::any::TypeId::of::<MockCapability>())];
1592 DEPS
1593 }
1594 }
1595 impl AutoBuilder for DependentModule {
1596 type Capability = Arc<AtomicUsize>;
1597 type Error = TraitKitError;
1598 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1599 Ok(Arc::new(AtomicUsize::new(0)))
1600 }
1601 }
1602
1603 #[test]
1606 fn overrides_field_is_empty_on_new() {
1607 let kit = Kit::new();
1608 assert_eq!(kit.overrides.borrow().len(), 0);
1609 }
1610
1611 #[test]
1612 fn overrides_field_is_empty_after_build() {
1613 let kit = Kit::new();
1614 assert_eq!(kit.overrides.borrow().len(), 0);
1615 }
1616
1617 #[test]
1620 fn override_module_inserts_into_overrides_map() {
1621 let kit = Kit::new();
1622 assert_eq!(kit.overrides.borrow().len(), 0);
1623 kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(42)));
1624 assert_eq!(kit.overrides.borrow().len(), 1);
1625 }
1626
1627 #[test]
1628 fn override_module_strict_succeeds_when_deps_registered() {
1629 let mut kit = Kit::new();
1630 kit.register::<MockCapability>().unwrap();
1632 let result = kit.override_module_strict::<DependentModule>(Arc::new(AtomicUsize::new(99)));
1634 assert!(result.is_ok());
1635 assert_eq!(kit.overrides.borrow().len(), 1);
1636 }
1637
1638 #[test]
1639 fn override_module_strict_fails_when_deps_missing() {
1640 let mut kit = Kit::new();
1641 let result = kit.override_module_strict::<DependentModule>(Arc::new(AtomicUsize::new(99)));
1643 assert!(matches!(
1644 result,
1645 Err(TraitKitError::DependencyMissing {
1646 module: "dependent",
1647 missing: "mock"
1648 })
1649 ));
1650 assert_eq!(kit.overrides.borrow().len(), 0);
1652 }
1653
1654 struct CountingModule;
1658 impl ModuleMeta for CountingModule {
1659 const NAME: &'static str = "counting";
1660 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1661 &[]
1662 }
1663 }
1664 impl AutoBuilder for CountingModule {
1665 type Capability = Arc<AtomicUsize>;
1666 type Error = TraitKitError;
1667 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1668 Ok(Arc::new(AtomicUsize::new(0)))
1671 }
1672 }
1673
1674 #[test]
1675 fn build_uses_override_and_skips_build_fn() {
1676 let kit = Kit::new();
1677 let mut kit = kit;
1679 kit.register::<CountingModule>().unwrap();
1680 kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(42)));
1682 let built = kit.build().unwrap();
1684 let cap = built.require::<CountingModule>().unwrap();
1686 assert_eq!(cap.load(Ordering::SeqCst), 42);
1687 }
1688
1689 #[test]
1690 fn build_uses_build_fn_when_no_override() {
1691 let mut kit = Kit::new();
1692 kit.register::<CountingModule>().unwrap();
1693 let built = kit.build().unwrap();
1695 let cap = built.require::<CountingModule>().unwrap();
1696 assert_eq!(cap.load(Ordering::SeqCst), 0);
1697 }
1698
1699 #[test]
1700 fn build_inserts_unregistered_override_after_topo_loop() {
1701 let kit = Kit::new();
1704 kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(77)));
1705 let built = kit.build().unwrap();
1706 let cap = built.require::<MockCapability>().unwrap();
1707 assert_eq!(cap.load(Ordering::SeqCst), 77);
1708 }
1709
1710 #[test]
1713 fn require_ref_returns_reference_to_built_capability() {
1714 let mut kit = Kit::new();
1715 kit.register::<CountingModule>().unwrap();
1716 let built = kit.build().unwrap();
1717 let r = built.require_ref::<CountingModule>().unwrap();
1718 assert_eq!((*r).load(Ordering::SeqCst), 0);
1720 }
1721
1722 #[test]
1723 fn require_ref_returns_override_value() {
1724 let mut kit = Kit::new();
1725 kit.register::<CountingModule>().unwrap();
1726 kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(55)));
1727 let built = kit.build().unwrap();
1728 let r = built.require_ref::<CountingModule>().unwrap();
1729 assert_eq!((*r).load(Ordering::SeqCst), 55);
1730 }
1731
1732 #[test]
1733 fn require_ref_returns_missing_capability_for_unbuilt() {
1734 let kit = Kit::new();
1735 let built = kit.build().unwrap();
1736 let result = built.require_ref::<CountingModule>();
1737 assert!(matches!(
1738 result,
1739 Err(TraitKitError::MissingCapability { ref key }) if key == "counting"
1740 ));
1741 }
1742
1743 #[test]
1746 fn register_lazy_does_not_build_during_build() {
1747 let mut kit = Kit::new();
1748 kit.register_lazy::<CountingModule>().unwrap();
1749 let built = kit.build().unwrap();
1751 assert!(!built.contains::<CountingModule>());
1753 }
1754
1755 #[test]
1756 fn register_lazy_adds_to_dependency_graph() {
1757 let mut kit = Kit::new();
1758 kit.register::<MockCapability>().unwrap();
1760 kit.register_lazy::<DependentModule>().unwrap();
1762 let built = kit.build().unwrap();
1764 assert!(built.contains::<MockCapability>());
1766 assert!(!built.contains::<DependentModule>());
1767 }
1768
1769 #[test]
1770 fn register_lazy_returns_already_registered_for_duplicate() {
1771 let mut kit = Kit::new();
1772 kit.register_lazy::<CountingModule>().unwrap();
1773 let result = kit.register_lazy::<CountingModule>();
1774 assert!(matches!(
1775 result,
1776 Err(TraitKitError::AlreadyRegistered { module: "counting" })
1777 ));
1778 }
1779
1780 #[test]
1783 fn lazy_slots_empty_on_new_kit() {
1784 let kit = Kit::new();
1785 assert_eq!(kit.lazy_slots.borrow().len(), 0);
1786 }
1787
1788 #[test]
1789 fn build_transfers_lazy_builders_to_lazy_slots() {
1790 let mut kit = Kit::new();
1791 kit.register_lazy::<CountingModule>().unwrap();
1792 assert_eq!(kit.lazy_builders.borrow().len(), 1);
1793 assert_eq!(kit.lazy_slots.borrow().len(), 0);
1794
1795 let built = kit.build().unwrap();
1796
1797 assert_eq!(built.lazy_builders.borrow().len(), 0);
1799 assert_eq!(built.lazy_slots.borrow().len(), 1);
1800 assert!(
1801 built
1802 .lazy_slots
1803 .borrow()
1804 .contains_key(&TypeId::of::<CountingModule>())
1805 );
1806 }
1807
1808 #[test]
1809 fn lazy_slots_cells_empty_after_build() {
1810 let mut kit = Kit::new();
1811 kit.register_lazy::<CountingModule>().unwrap();
1812 let built = kit.build().unwrap();
1813
1814 let slots = built.lazy_slots.borrow();
1817 let slot = slots
1818 .get(&TypeId::of::<CountingModule>())
1819 .expect("slot exists");
1820 assert!(slot.cell.get().is_none());
1821 }
1822
1823 #[test]
1824 fn build_transfers_multiple_lazy_builders_to_lazy_slots() {
1825 let mut kit = Kit::new();
1826 kit.register::<MockCapability>().unwrap();
1827 kit.register_lazy::<DependentModule>().unwrap();
1828 kit.register_lazy::<CountingModule>().unwrap();
1829 assert_eq!(kit.lazy_builders.borrow().len(), 2);
1830
1831 let built = kit.build().unwrap();
1832
1833 assert_eq!(built.lazy_builders.borrow().len(), 0);
1834 assert_eq!(built.lazy_slots.borrow().len(), 2);
1835 assert!(
1836 built
1837 .lazy_slots
1838 .borrow()
1839 .contains_key(&TypeId::of::<DependentModule>())
1840 );
1841 assert!(
1842 built
1843 .lazy_slots
1844 .borrow()
1845 .contains_key(&TypeId::of::<CountingModule>())
1846 );
1847 }
1848
1849 #[test]
1852 fn require_triggers_lazy_construction_on_first_access() {
1853 let mut kit = Kit::new();
1854 kit.register_lazy::<CountingModule>().unwrap();
1855 let built = kit.build().unwrap();
1856
1857 assert!(!built.contains::<CountingModule>());
1859
1860 let cap = built.require::<CountingModule>().unwrap();
1862 assert_eq!(cap.load(Ordering::SeqCst), 0);
1863 }
1864
1865 #[test]
1866 fn require_does_not_rebuild_lazy_on_second_call() {
1867 static COUNT: AtomicUsize = AtomicUsize::new(0);
1869
1870 struct CountedModule;
1871 impl ModuleMeta for CountedModule {
1872 const NAME: &'static str = "test-counted";
1873 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1874 &[]
1875 }
1876 }
1877 impl AutoBuilder for CountedModule {
1878 type Capability = Arc<AtomicUsize>;
1879 type Error = TraitKitError;
1880 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1881 let n = COUNT.fetch_add(1, Ordering::SeqCst);
1882 Ok(Arc::new(AtomicUsize::new(n)))
1883 }
1884 }
1885
1886 COUNT.store(0, Ordering::SeqCst);
1887 let mut kit = Kit::new();
1888 kit.register_lazy::<CountedModule>().unwrap();
1889 let built = kit.build().unwrap();
1890
1891 let cap1 = built.require::<CountedModule>().unwrap();
1892 let cap2 = built.require::<CountedModule>().unwrap();
1893
1894 assert_eq!(
1896 cap1.load(Ordering::SeqCst),
1897 0,
1898 "first require returns count 0"
1899 );
1900 assert_eq!(
1901 cap2.load(Ordering::SeqCst),
1902 0,
1903 "second require returns same count"
1904 );
1905 assert_eq!(
1906 COUNT.load(Ordering::SeqCst),
1907 1,
1908 "builder invoked exactly once"
1909 );
1910 }
1911
1912 #[test]
1913 fn require_lazy_with_registered_dependency_succeeds() {
1914 struct LazyDependentModule;
1916 impl ModuleMeta for LazyDependentModule {
1917 const NAME: &'static str = "lazy-dependent";
1918 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1919 static DEPS: &[(&str, std::any::TypeId)] =
1920 &[("mock", std::any::TypeId::of::<MockCapability>())];
1921 DEPS
1922 }
1923 }
1924 impl AutoBuilder for LazyDependentModule {
1925 type Capability = Arc<AtomicUsize>;
1926 type Error = TraitKitError;
1927 fn build(kit: &Kit) -> Result<Self::Capability, Self::Error> {
1928 let mock = kit.require::<MockCapability>()?;
1930 Ok(Arc::new(AtomicUsize::new(
1931 mock.load(Ordering::SeqCst) + 100,
1932 )))
1933 }
1934 }
1935
1936 let mut kit = Kit::new();
1937 kit.register::<MockCapability>().unwrap();
1940 kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(42)));
1941 kit.register_lazy::<LazyDependentModule>().unwrap();
1942 let built = kit.build().unwrap();
1943
1944 let cap = built.require::<LazyDependentModule>().unwrap();
1946 assert_eq!(
1947 cap.load(Ordering::SeqCst),
1948 142,
1949 "lazy build accessed eager dep (42 + 100)"
1950 );
1951 }
1952
1953 struct MultiModuleA;
1957 impl ModuleMeta for MultiModuleA {
1958 const NAME: &'static str = "multi-a";
1959 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1960 &[]
1961 }
1962 }
1963 impl AutoBuilder for MultiModuleA {
1964 type Capability = Arc<AtomicUsize>;
1965 type Error = TraitKitError;
1966 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1967 Ok(Arc::new(AtomicUsize::new(10)))
1968 }
1969 }
1970
1971 struct MultiModuleB;
1973 impl ModuleMeta for MultiModuleB {
1974 const NAME: &'static str = "multi-b";
1975 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1976 &[]
1977 }
1978 }
1979 impl AutoBuilder for MultiModuleB {
1980 type Capability = Arc<AtomicUsize>;
1981 type Error = TraitKitError;
1982 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1983 Ok(Arc::new(AtomicUsize::new(20)))
1984 }
1985 }
1986
1987 struct MultiModuleC;
1989 impl ModuleMeta for MultiModuleC {
1990 const NAME: &'static str = "multi-c";
1991 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1992 &[]
1993 }
1994 }
1995 impl AutoBuilder for MultiModuleC {
1996 type Capability = Arc<AtomicUsize>;
1997 type Error = TraitKitError;
1998 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1999 Ok(Arc::new(AtomicUsize::new(30)))
2000 }
2001 }
2002
2003 #[test]
2004 fn multi_builders_empty_on_new_kit() {
2005 let kit = Kit::new();
2006 assert_eq!(kit.multi_builders.borrow().len(), 0);
2007 }
2008
2009 #[test]
2010 fn register_multi_adds_to_multi_builders() {
2011 let mut kit = Kit::new();
2012 assert_eq!(kit.multi_builders.borrow().len(), 0);
2013
2014 kit.register_multi::<MultiModuleA>().unwrap();
2015
2016 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2018 assert_eq!(kit.multi_builders.borrow().len(), 1);
2019 assert!(kit.multi_builders.borrow().contains_key(&cap_id));
2020 assert_eq!(
2021 kit.multi_builders.borrow().get(&cap_id).unwrap().len(),
2022 1,
2023 "first register_multi should produce Vec of length 1"
2024 );
2025 }
2026
2027 #[test]
2028 fn register_multi_three_times_appends_to_vec() {
2029 let mut kit = Kit::new();
2030 kit.register_multi::<MultiModuleA>().unwrap();
2031 kit.register_multi::<MultiModuleB>().unwrap();
2032 kit.register_multi::<MultiModuleC>().unwrap();
2033
2034 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2035 let builders = kit.multi_builders.borrow();
2036 let vec = builders.get(&cap_id).expect("cap_id exists");
2037 assert_eq!(
2038 vec.len(),
2039 3,
2040 "three register_multi calls should produce Vec of length 3"
2041 );
2042 }
2043
2044 #[test]
2045 fn register_multi_adds_module_to_dependency_graph() {
2046 let mut kit = Kit::new();
2047 kit.register_multi::<MultiModuleA>().unwrap();
2048
2049 assert!(kit.graph.name_of(TypeId::of::<MultiModuleA>()).is_some());
2051 }
2052
2053 #[test]
2054 fn register_multi_returns_already_registered_for_duplicate_module() {
2055 let mut kit = Kit::new();
2056 kit.register_multi::<MultiModuleA>().unwrap();
2057
2058 let result = kit.register_multi::<MultiModuleA>();
2059 assert!(matches!(
2060 result,
2061 Err(TraitKitError::AlreadyRegistered { module: "multi-a" })
2062 ));
2063 }
2064
2065 #[test]
2066 fn register_multi_returns_already_registered_if_already_registered_via_register() {
2067 let mut kit = Kit::new();
2068 kit.register::<MockCapability>().unwrap();
2069
2070 let result = kit.register_multi::<MockCapability>();
2071 assert!(matches!(
2072 result,
2073 Err(TraitKitError::AlreadyRegistered { module: "mock" })
2074 ));
2075 }
2076
2077 #[test]
2078 fn register_multi_coexists_with_register_for_different_modules() {
2079 let mut kit = Kit::new();
2080 kit.register::<MockCapability>().unwrap();
2081 kit.register_multi::<MultiModuleA>().unwrap();
2082 kit.register_multi::<MultiModuleB>().unwrap();
2083
2084 assert!(
2086 kit.builders
2087 .borrow()
2088 .contains_key(&TypeId::of::<MockCapability>())
2089 );
2090 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2091 assert_eq!(kit.multi_builders.borrow().get(&cap_id).unwrap().len(), 2);
2092 }
2093
2094 #[test]
2097 fn require_all_returns_empty_for_unregistered_capability() {
2098 let mut kit = Kit::new();
2099 kit.register::<MockCapability>().unwrap();
2101 let built = kit.build().unwrap();
2102
2103 let result = built.require_all::<MultiModuleA>();
2105 assert!(matches!(
2106 result,
2107 Err(TraitKitError::MissingCapability { ref key }) if key == "multi-a"
2108 ));
2109 }
2110
2111 #[test]
2112 fn require_all_returns_vec_of_three_after_three_register_multi() {
2113 let mut kit = Kit::new();
2114 kit.register_multi::<MultiModuleA>().unwrap();
2115 kit.register_multi::<MultiModuleB>().unwrap();
2116 kit.register_multi::<MultiModuleC>().unwrap();
2117 let built = kit.build().unwrap();
2118
2119 let caps = built.require_all::<MultiModuleA>().unwrap();
2120 assert_eq!(
2121 caps.len(),
2122 3,
2123 "three register_multi calls should return Vec of length 3"
2124 );
2125 }
2126
2127 #[test]
2128 fn require_all_preserves_registration_order() {
2129 let mut kit = Kit::new();
2130 kit.register_multi::<MultiModuleA>().unwrap(); kit.register_multi::<MultiModuleB>().unwrap(); kit.register_multi::<MultiModuleC>().unwrap(); let built = kit.build().unwrap();
2134
2135 let caps = built.require_all::<MultiModuleA>().unwrap();
2136 assert_eq!(caps.len(), 3);
2137 assert_eq!(
2139 caps[0].load(Ordering::SeqCst),
2140 10,
2141 "first cap should be 10 (MultiModuleA)"
2142 );
2143 assert_eq!(
2144 caps[1].load(Ordering::SeqCst),
2145 20,
2146 "second cap should be 20 (MultiModuleB)"
2147 );
2148 assert_eq!(
2149 caps[2].load(Ordering::SeqCst),
2150 30,
2151 "third cap should be 30 (MultiModuleC)"
2152 );
2153 }
2154
2155 #[test]
2156 fn require_all_returns_missing_capability_before_build() {
2157 let mut kit = Kit::new();
2158 kit.register_multi::<MultiModuleA>().unwrap();
2159 let result = kit.require_all::<MultiModuleA>();
2162 assert!(matches!(
2163 result,
2164 Err(TraitKitError::MissingCapability { ref key }) if key == "multi-a"
2165 ));
2166 }
2167
2168 #[test]
2169 fn build_drains_multi_builders_into_multi_capabilities() {
2170 let mut kit = Kit::new();
2171 kit.register_multi::<MultiModuleA>().unwrap();
2172 kit.register_multi::<MultiModuleB>().unwrap();
2173
2174 assert_eq!(kit.multi_builders.borrow().len(), 1); assert_eq!(kit.multi_capabilities.borrow().len(), 0);
2177
2178 let built = kit.build().unwrap();
2179
2180 assert_eq!(built.multi_builders.borrow().len(), 0);
2182 assert_eq!(built.multi_capabilities.borrow().len(), 1);
2183 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2184 assert_eq!(
2185 built
2186 .multi_capabilities
2187 .borrow()
2188 .get(&cap_id)
2189 .unwrap()
2190 .len(),
2191 2
2192 );
2193 }
2194
2195 #[test]
2196 fn require_all_coexists_with_require_for_single_binding() {
2197 let mut kit = Kit::new();
2198 kit.register::<MockCapability>().unwrap();
2200 kit.register_multi::<MultiModuleA>().unwrap();
2202 kit.register_multi::<MultiModuleB>().unwrap();
2203 let built = kit.build().unwrap();
2204
2205 let single = built.require::<MockCapability>().unwrap();
2207 assert_eq!(single.load(Ordering::SeqCst), 0);
2208
2209 let multi = built.require_all::<MultiModuleA>().unwrap();
2211 assert_eq!(multi.len(), 2);
2212 assert_eq!(multi[0].load(Ordering::SeqCst), 10);
2213 assert_eq!(multi[1].load(Ordering::SeqCst), 20);
2214 }
2215
2216 #[test]
2217 fn multi_binding_build_error_returns_build_failed() {
2218 struct FailMultiModule;
2219 impl ModuleMeta for FailMultiModule {
2220 const NAME: &'static str = "fail-multi";
2221 fn dependencies() -> &'static [(&'static str, TypeId)] {
2222 &[]
2223 }
2224 }
2225 impl AutoBuilder for FailMultiModule {
2226 type Capability = Arc<AtomicUsize>;
2227 type Error = TraitKitError;
2228 fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2229 Err(TraitKitError::BuildFailed {
2230 context: "fail-multi".into(),
2231 source: Box::new(std::io::Error::other("multi fail")),
2232 })
2233 }
2234 }
2235
2236 let mut kit = Kit::new();
2237 kit.register_multi::<FailMultiModule>().unwrap();
2238 let result = kit.build();
2239 assert!(result.is_err());
2240 assert!(matches!(
2241 result.unwrap_err(),
2242 TraitKitError::BuildFailed { .. }
2243 ));
2244 }
2245}
2246
2247#[cfg(all(test, feature = "interface"))]
2248mod interface_tests {
2249 use super::*;
2250 use crate::core::{InterfaceBuilder, ModuleMeta};
2251 use std::sync::Arc;
2252 use std::sync::atomic::{AtomicUsize, Ordering};
2253
2254 trait Logger: 'static {
2258 fn log(&self, msg: &str) -> String;
2259 }
2260
2261 struct ConsoleLogger;
2263
2264 impl Logger for ConsoleLogger {
2265 fn log(&self, msg: &str) -> String {
2266 format!("[console] {msg}")
2267 }
2268 }
2269
2270 struct FileLogger;
2272
2273 impl Logger for FileLogger {
2274 fn log(&self, msg: &str) -> String {
2275 format!("[file] {msg}")
2276 }
2277 }
2278
2279 #[derive(Debug)]
2281 struct InterfaceTestError;
2282
2283 impl std::fmt::Display for InterfaceTestError {
2284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2285 write!(f, "interface test error")
2286 }
2287 }
2288
2289 impl std::error::Error for InterfaceTestError {}
2290
2291 struct ConsoleLoggerModule;
2293
2294 impl ModuleMeta for ConsoleLoggerModule {
2295 const NAME: &'static str = "console-logger-iface";
2296 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
2297 &[]
2298 }
2299 }
2300
2301 impl InterfaceBuilder for ConsoleLoggerModule {
2302 type Interface = dyn Logger;
2303 type Capability = Arc<ConsoleLogger>;
2304 type Error = InterfaceTestError;
2305
2306 fn build(_kit: &Kit) -> Result<Arc<ConsoleLogger>, InterfaceTestError> {
2307 Ok(Arc::new(ConsoleLogger))
2308 }
2309
2310 fn into_interface(cap: Arc<ConsoleLogger>) -> Arc<dyn Logger> {
2311 cap
2312 }
2313 }
2314
2315 struct FileLoggerModule;
2317
2318 impl ModuleMeta for FileLoggerModule {
2319 const NAME: &'static str = "file-logger";
2320 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
2321 &[]
2322 }
2323 }
2324
2325 impl InterfaceBuilder for FileLoggerModule {
2326 type Interface = dyn Logger;
2327 type Capability = Arc<FileLogger>;
2328 type Error = InterfaceTestError;
2329
2330 fn build(_kit: &Kit) -> Result<Arc<FileLogger>, InterfaceTestError> {
2331 Ok(Arc::new(FileLogger))
2332 }
2333
2334 fn into_interface(cap: Arc<FileLogger>) -> Arc<dyn Logger> {
2335 cap
2336 }
2337 }
2338
2339 #[test]
2342 fn register_as_then_resolve_returns_arc_dyn_trait() {
2343 let mut kit = Kit::new();
2344 kit.register_as::<ConsoleLoggerModule>()
2345 .expect("register_as succeeds");
2346 let built = kit.build().expect("build succeeds");
2347
2348 let logger: Arc<dyn Logger> = built.resolve::<dyn Logger>().expect("resolve succeeds");
2349 assert_eq!(logger.log("hello"), "[console] hello");
2350 }
2351
2352 #[test]
2353 fn register_as_twice_same_interface_returns_already_registered() {
2354 let mut kit = Kit::new();
2355 kit.register_as::<ConsoleLoggerModule>()
2356 .expect("first register_as succeeds");
2357 let err = kit.register_as::<FileLoggerModule>().unwrap_err();
2358 assert!(
2359 matches!(err, TraitKitError::AlreadyRegistered { .. }),
2360 "expected AlreadyRegistered, got {err:?}"
2361 );
2362 }
2363
2364 #[test]
2365 fn resolve_before_build_returns_missing_capability() {
2366 let mut kit = Kit::new();
2367 kit.register_as::<ConsoleLoggerModule>()
2368 .expect("register_as succeeds");
2369 assert!(kit.resolve::<dyn Logger>().is_err());
2371 }
2372
2373 #[test]
2374 fn resolve_unregistered_interface_returns_missing_capability() {
2375 let kit = Kit::new();
2376 let built = kit.build().expect("build succeeds");
2377 assert!(built.resolve::<dyn Logger>().is_err());
2378 }
2379
2380 #[test]
2381 fn register_as_builds_during_build() {
2382 let mut kit = Kit::new();
2383 kit.register_as::<ConsoleLoggerModule>()
2384 .expect("register_as succeeds");
2385 let built = kit.build().expect("build succeeds");
2386 let logger = built.resolve::<dyn Logger>().expect("resolve succeeds");
2388 assert_eq!(logger.log("test"), "[console] test");
2389 }
2390
2391 #[test]
2392 fn resolve_returns_callable_trait_object() {
2393 let mut kit = Kit::new();
2394 kit.register_as::<ConsoleLoggerModule>()
2395 .expect("register_as succeeds");
2396 let built = kit.build().expect("build succeeds");
2397
2398 let logger: Arc<dyn Logger> = built.resolve().expect("resolve succeeds");
2399 let result = logger.log("world");
2400 assert_eq!(result, "[console] world");
2401 }
2402
2403 #[test]
2404 fn register_as_coexists_with_register() {
2405 struct RegularModule;
2408 impl ModuleMeta for RegularModule {
2409 const NAME: &'static str = "regular";
2410 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
2411 &[]
2412 }
2413 }
2414 impl AutoBuilder for RegularModule {
2415 type Capability = Arc<AtomicUsize>;
2416 type Error = TraitKitError;
2417 fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2418 Ok(Arc::new(AtomicUsize::new(42)))
2419 }
2420 }
2421
2422 let mut kit = Kit::new();
2423 kit.register::<RegularModule>().expect("register succeeds");
2424 kit.register_as::<ConsoleLoggerModule>()
2425 .expect("register_as succeeds");
2426 let built = kit.build().expect("build succeeds");
2427
2428 let cap = built.require::<RegularModule>().expect("require succeeds");
2430 assert_eq!(cap.load(Ordering::SeqCst), 42);
2431
2432 let logger = built.resolve::<dyn Logger>().expect("resolve succeeds");
2433 assert_eq!(logger.log("coexist"), "[console] coexist");
2434 }
2435
2436 #[test]
2437 fn register_as_same_module_twice_returns_already_registered() {
2438 let mut kit = Kit::new();
2439 kit.register_as::<ConsoleLoggerModule>()
2440 .expect("first register_as succeeds");
2441 let err = kit.register_as::<ConsoleLoggerModule>().unwrap_err();
2443 assert!(
2444 matches!(err, TraitKitError::AlreadyRegistered { .. }),
2445 "expected AlreadyRegistered, got {err:?}"
2446 );
2447 }
2448
2449 #[test]
2450 fn file_logger_interface_build_and_resolve() {
2451 let mut kit = Kit::new();
2452 kit.register_as::<FileLoggerModule>()
2453 .expect("register_as succeeds");
2454 let built = kit.build().expect("build succeeds");
2455 let logger: Arc<dyn Logger> = built.resolve::<dyn Logger>().expect("resolve succeeds");
2456 assert_eq!(logger.log("hello"), "[file] hello");
2457 }
2458
2459 #[test]
2460 fn interface_test_error_display() {
2461 let e = InterfaceTestError;
2462 assert_eq!(format!("{e}"), "interface test error");
2463 }
2464
2465 #[test]
2466 fn interface_build_error_returns_build_failed() {
2467 struct FailIfaceModule;
2468 impl ModuleMeta for FailIfaceModule {
2469 const NAME: &'static str = "fail-iface";
2470 fn dependencies() -> &'static [(&'static str, TypeId)] {
2471 &[]
2472 }
2473 }
2474 impl InterfaceBuilder for FailIfaceModule {
2475 type Interface = dyn Logger;
2476 type Capability = Arc<()>;
2477 type Error = InterfaceTestError;
2478 fn build(_kit: &Kit) -> Result<Arc<()>, InterfaceTestError> {
2479 Err(InterfaceTestError)
2480 }
2481 fn into_interface(_cap: Arc<()>) -> Arc<dyn Logger> {
2482 unreachable!()
2483 }
2484 }
2485
2486 let mut kit = Kit::new();
2487 kit.register_as::<FailIfaceModule>().unwrap();
2488 let result = kit.build();
2489 assert!(result.is_err());
2490 assert!(matches!(
2491 result.unwrap_err(),
2492 TraitKitError::BuildFailed { .. }
2493 ));
2494 }
2495}
2496
2497#[cfg(all(test, feature = "lifecycle"))]
2500mod lifecycle_tests {
2501 use super::*;
2502 use crate::core::ModuleMeta;
2503 use crate::core::lifecycle::Lifecycle;
2504 use std::sync::Arc;
2505 use std::sync::atomic::{AtomicUsize, Ordering};
2506
2507 static LC_SHUTDOWN: AtomicUsize = AtomicUsize::new(0);
2508 static LC_READY: AtomicUsize = AtomicUsize::new(0);
2509
2510 struct LcModule;
2511 impl ModuleMeta for LcModule {
2512 const NAME: &'static str = "lc-module";
2513 fn dependencies() -> &'static [(&'static str, TypeId)] {
2514 &[]
2515 }
2516 }
2517 impl AutoBuilder for LcModule {
2518 type Capability = Arc<AtomicUsize>;
2519 type Error = TraitKitError;
2520 fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2521 Ok(Arc::new(AtomicUsize::new(0)))
2522 }
2523 }
2524 impl Lifecycle for LcModule {
2525 fn on_ready(_kit: &Kit<Ready>) -> Result<(), Self::Error> {
2526 LC_READY.fetch_add(1, Ordering::SeqCst);
2527 Ok(())
2528 }
2529 fn on_shutdown(_cap: &Arc<AtomicUsize>) {
2530 LC_SHUTDOWN.fetch_add(1, Ordering::SeqCst);
2531 }
2532 }
2533
2534 #[test]
2535 fn lifecycle_on_ready_called_during_build() {
2536 LC_READY.store(0, Ordering::SeqCst);
2537 let mut kit = Kit::new();
2538 kit.register::<LcModule>().unwrap();
2539 kit.register_lifecycle::<LcModule>();
2540 let _built = kit.build().unwrap();
2541 assert_eq!(
2542 LC_READY.load(Ordering::SeqCst),
2543 1,
2544 "on_ready should be called once"
2545 );
2546 }
2547
2548 #[test]
2549 fn lifecycle_shutdown_called_in_reverse_order() {
2550 LC_SHUTDOWN.store(0, Ordering::SeqCst);
2551 let mut kit = Kit::new();
2552 kit.register::<LcModule>().unwrap();
2553 kit.register_lifecycle::<LcModule>();
2554 let built = kit.build().unwrap();
2555 built.shutdown();
2556 assert_eq!(
2557 LC_SHUTDOWN.load(Ordering::SeqCst),
2558 1,
2559 "on_shutdown should be called once"
2560 );
2561 }
2562
2563 #[test]
2564 fn lifecycle_on_ready_failure_propagates() {
2565 struct FailReadyModule;
2566 impl ModuleMeta for FailReadyModule {
2567 const NAME: &'static str = "fail-ready";
2568 fn dependencies() -> &'static [(&'static str, TypeId)] {
2569 &[]
2570 }
2571 }
2572 impl AutoBuilder for FailReadyModule {
2573 type Capability = Arc<()>;
2574 type Error = TraitKitError;
2575 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2576 Ok(Arc::new(()))
2577 }
2578 }
2579 impl Lifecycle for FailReadyModule {
2580 fn on_ready(_kit: &Kit<Ready>) -> Result<(), TraitKitError> {
2581 Err(TraitKitError::BuildFailed {
2582 context: "on_ready".into(),
2583 source: Box::new(std::io::Error::other("intentional failure")),
2584 })
2585 }
2586 }
2587
2588 let mut kit = Kit::new();
2589 kit.register::<FailReadyModule>().unwrap();
2590 kit.register_lifecycle::<FailReadyModule>();
2591 let result = kit.build();
2592 assert!(result.is_err(), "build should fail when on_ready fails");
2593 let err = result.unwrap_err();
2594 assert!(matches!(err, TraitKitError::LifecycleFailed { .. }));
2595 }
2596}
2597
2598#[cfg(all(test, feature = "health"))]
2599mod health_tests {
2600 use super::*;
2601 use crate::core::ModuleMeta;
2602 use crate::core::health::{HealthCheck, HealthStatus};
2603 use std::sync::Arc;
2604
2605 #[derive(Debug, Clone)]
2606 struct HcCap {
2607 val: i32,
2608 }
2609
2610 struct HcModule;
2611 impl ModuleMeta for HcModule {
2612 const NAME: &'static str = "hc-module";
2613 fn dependencies() -> &'static [(&'static str, TypeId)] {
2614 &[]
2615 }
2616 }
2617 impl AutoBuilder for HcModule {
2618 type Capability = Arc<HcCap>;
2619 type Error = TraitKitError;
2620 fn build(_kit: &Kit) -> Result<Arc<HcCap>, TraitKitError> {
2621 Ok(Arc::new(HcCap { val: 42 }))
2622 }
2623 }
2624 impl HealthCheck for HcModule {
2625 fn check(cap: &Arc<HcCap>) -> HealthStatus {
2626 if cap.val > 0 {
2627 HealthStatus::Healthy
2628 } else {
2629 HealthStatus::Unhealthy {
2630 detail: "zero".into(),
2631 }
2632 }
2633 }
2634 }
2635
2636 #[test]
2637 fn health_check_registered_and_queryable() {
2638 let mut kit = Kit::new();
2639 kit.register::<HcModule>().unwrap();
2640 kit.register_health_check::<HcModule>();
2641 let built = kit.build().unwrap();
2642 let status = built.health_check::<HcModule>().unwrap();
2643 assert_eq!(status, HealthStatus::Healthy);
2644 }
2645
2646 #[test]
2647 fn health_report_returns_all_checkers() {
2648 let mut kit = Kit::new();
2649 kit.register::<HcModule>().unwrap();
2650 kit.register_health_check::<HcModule>();
2651 let built = kit.build().unwrap();
2652 let report = built.health_report();
2653 assert_eq!(report.len(), 1);
2654 assert_eq!(report[0].0, "hc-module");
2655 assert_eq!(report[0].1, HealthStatus::Healthy);
2656 }
2657
2658 #[test]
2659 fn health_check_unregistered_returns_error() {
2660 let mut kit = Kit::new();
2661 kit.register::<HcModule>().unwrap();
2662 let built = kit.build().unwrap();
2663 let err = built.health_check::<HcModule>().unwrap_err();
2664 assert!(matches!(err, TraitKitError::MissingConfig { .. }));
2665 }
2666
2667 #[test]
2668 fn health_check_unhealthy_for_zero_value() {
2669 struct ZeroHcModule;
2670 impl ModuleMeta for ZeroHcModule {
2671 const NAME: &'static str = "zero-hc";
2672 fn dependencies() -> &'static [(&'static str, TypeId)] {
2673 &[]
2674 }
2675 }
2676 impl AutoBuilder for ZeroHcModule {
2677 type Capability = Arc<HcCap>;
2678 type Error = TraitKitError;
2679 fn build(_kit: &Kit) -> Result<Arc<HcCap>, TraitKitError> {
2680 Ok(Arc::new(HcCap { val: 0 }))
2681 }
2682 }
2683 impl HealthCheck for ZeroHcModule {
2684 fn check(cap: &Arc<HcCap>) -> HealthStatus {
2685 if cap.val > 0 {
2686 HealthStatus::Healthy
2687 } else {
2688 HealthStatus::Unhealthy {
2689 detail: "zero".into(),
2690 }
2691 }
2692 }
2693 }
2694
2695 let mut kit = Kit::new();
2696 kit.register::<ZeroHcModule>().unwrap();
2697 kit.register_health_check::<ZeroHcModule>();
2698 let built = kit.build().unwrap();
2699 let status = built.health_check::<ZeroHcModule>().unwrap();
2700 assert!(matches!(status, HealthStatus::Unhealthy { .. }));
2701 }
2702}
2703
2704#[cfg(all(test, feature = "observer"))]
2705mod observability_tests {
2706 use super::*;
2707 use crate::core::ModuleMeta;
2708 use crate::core::observer::BuildObserver;
2709 use std::sync::Arc;
2710 use std::sync::atomic::{AtomicUsize, Ordering};
2711 use std::time::Duration;
2712
2713 struct CountingObs {
2714 start: Arc<AtomicUsize>,
2715 built: Arc<AtomicUsize>,
2716 }
2717 impl BuildObserver for CountingObs {
2718 fn on_module_start(&self, _: &'static str) {
2719 self.start.fetch_add(1, Ordering::SeqCst);
2720 }
2721 fn on_module_built(&self, _: &'static str, _: Duration) {
2722 self.built.fetch_add(1, Ordering::SeqCst);
2723 }
2724 }
2725
2726 struct ObsModule;
2727 impl ModuleMeta for ObsModule {
2728 const NAME: &'static str = "obs-module";
2729 fn dependencies() -> &'static [(&'static str, TypeId)] {
2730 &[]
2731 }
2732 }
2733 impl AutoBuilder for ObsModule {
2734 type Capability = Arc<()>;
2735 type Error = TraitKitError;
2736 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2737 Ok(Arc::new(()))
2738 }
2739 }
2740
2741 #[test]
2742 fn observer_callbacks_fired_during_build() {
2743 let start = Arc::new(AtomicUsize::new(0));
2744 let built = Arc::new(AtomicUsize::new(0));
2745 let obs = Arc::new(CountingObs {
2746 start: Arc::clone(&start),
2747 built: Arc::clone(&built),
2748 });
2749 let mut kit = Kit::new();
2750 kit.with_observer(obs);
2751 kit.register::<ObsModule>().unwrap();
2752 kit.build().unwrap();
2753 assert_eq!(
2754 start.load(Ordering::SeqCst),
2755 1,
2756 "on_module_start should fire"
2757 );
2758 assert_eq!(
2759 built.load(Ordering::SeqCst),
2760 1,
2761 "on_module_built should fire"
2762 );
2763 }
2764
2765 #[test]
2766 fn observer_on_build_error_called_on_failure() {
2767 struct FailObs {
2768 errors: Arc<AtomicUsize>,
2769 }
2770 impl BuildObserver for FailObs {
2771 fn on_build_error(&self, _: &'static str, _: &TraitKitError) {
2772 self.errors.fetch_add(1, Ordering::SeqCst);
2773 }
2774 }
2775
2776 struct FailBuildModule;
2777 impl ModuleMeta for FailBuildModule {
2778 const NAME: &'static str = "fail-build";
2779 fn dependencies() -> &'static [(&'static str, TypeId)] {
2780 &[]
2781 }
2782 }
2783 impl AutoBuilder for FailBuildModule {
2784 type Capability = Arc<()>;
2785 type Error = TraitKitError;
2786 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2787 Err(TraitKitError::BuildFailed {
2788 context: "intentional".into(),
2789 source: Box::new(std::io::Error::other("test failure")),
2790 })
2791 }
2792 }
2793
2794 let errors = Arc::new(AtomicUsize::new(0));
2795 let obs = Arc::new(FailObs {
2796 errors: Arc::clone(&errors),
2797 });
2798 let mut kit = Kit::new();
2799 kit.with_observer(obs);
2800 kit.register::<FailBuildModule>().unwrap();
2801 let result = kit.build();
2802 assert!(result.is_err(), "build should fail");
2803 assert_eq!(
2804 errors.load(Ordering::SeqCst),
2805 1,
2806 "on_build_error should fire once"
2807 );
2808 }
2809}
2810
2811#[cfg(test)]
2812mod factory_tests {
2813 use super::*;
2814 use crate::core::ModuleMeta;
2815 use std::sync::Arc;
2816 use std::sync::atomic::{AtomicUsize, Ordering};
2817
2818 static FACTORY_COUNT: AtomicUsize = AtomicUsize::new(0);
2819
2820 struct FactoryModule;
2821 impl ModuleMeta for FactoryModule {
2822 const NAME: &'static str = "factory-module";
2823 fn dependencies() -> &'static [(&'static str, TypeId)] {
2824 &[]
2825 }
2826 }
2827 impl AutoBuilder for FactoryModule {
2828 type Capability = Arc<AtomicUsize>;
2829 type Error = TraitKitError;
2830 fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2831 let n = FACTORY_COUNT.fetch_add(1, Ordering::SeqCst);
2832 Ok(Arc::new(AtomicUsize::new(n)))
2833 }
2834 }
2835
2836 #[test]
2837 fn factory_creates_new_instance_each_call() {
2838 FACTORY_COUNT.store(0, Ordering::SeqCst);
2839 let mut kit = Kit::new();
2840 kit.register::<FactoryModule>().unwrap();
2841 let built = kit.build().unwrap();
2842 let factory = built.factory::<FactoryModule>();
2843 let cap1 = factory().unwrap();
2844 let cap2 = factory().unwrap();
2845 assert_ne!(
2847 cap1.load(Ordering::SeqCst),
2848 cap2.load(Ordering::SeqCst),
2849 "factory should produce different instances"
2850 );
2851 }
2852}
2853
2854#[cfg(all(test, feature = "scope"))]
2855mod scope_tests {
2856 use super::*;
2857 use crate::core::ModuleMeta;
2858 use std::sync::Arc;
2859
2860 struct ScopeMockModule;
2861 impl ModuleMeta for ScopeMockModule {
2862 const NAME: &'static str = "scope-mock";
2863 fn dependencies() -> &'static [(&'static str, TypeId)] {
2864 &[]
2865 }
2866 }
2867 impl AutoBuilder for ScopeMockModule {
2868 type Capability = Arc<()>;
2869 type Error = TraitKitError;
2870 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2871 Ok(Arc::new(()))
2872 }
2873 }
2874
2875 #[test]
2876 fn create_scope_returns_empty_scope() {
2877 let mut kit = Kit::new();
2878 kit.register::<ScopeMockModule>().unwrap();
2879 let built = kit.build().unwrap();
2880 let scope = built.create_scope();
2881 assert!(!scope.contains::<ScopeMockModule>());
2882 }
2883}
2884
2885#[cfg(test)]
2886mod conditional_tests {
2887 use super::*;
2888 use crate::core::ModuleMeta;
2889 use std::sync::Arc;
2890
2891 struct CondMockModule;
2892 impl ModuleMeta for CondMockModule {
2893 const NAME: &'static str = "cond-mock";
2894 fn dependencies() -> &'static [(&'static str, TypeId)] {
2895 &[]
2896 }
2897 }
2898 impl AutoBuilder for CondMockModule {
2899 type Capability = Arc<()>;
2900 type Error = TraitKitError;
2901 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2902 Ok(Arc::new(()))
2903 }
2904 }
2905
2906 #[test]
2907 fn register_if_true_registers_module() {
2908 let mut kit = Kit::new();
2909 let registered = kit.register_if::<CondMockModule>(|_| true).unwrap();
2910 assert!(registered);
2911 let built = kit.build().unwrap();
2912 assert!(built.contains::<CondMockModule>());
2913 }
2914
2915 #[test]
2916 fn register_if_false_skips_module() {
2917 let mut kit = Kit::new();
2918 let registered = kit.register_if::<CondMockModule>(|_| false).unwrap();
2919 assert!(!registered);
2920 let built = kit.build().unwrap();
2921 assert!(!built.contains::<CondMockModule>());
2922 }
2923}
2924
2925#[cfg(all(test, feature = "decorator"))]
2926mod decorator_tests {
2927 use super::*;
2928 use crate::core::ModuleMeta;
2929 use std::sync::Arc;
2930
2931 #[derive(Debug, Clone)]
2932 struct DecCap {
2933 val: String,
2934 }
2935
2936 struct DecModule;
2937 impl ModuleMeta for DecModule {
2938 const NAME: &'static str = "dec-module";
2939 fn dependencies() -> &'static [(&'static str, TypeId)] {
2940 &[]
2941 }
2942 }
2943 impl AutoBuilder for DecModule {
2944 type Capability = Arc<DecCap>;
2945 type Error = TraitKitError;
2946 fn build(_kit: &Kit) -> Result<Arc<DecCap>, TraitKitError> {
2947 Ok(Arc::new(DecCap {
2948 val: "original".into(),
2949 }))
2950 }
2951 }
2952
2953 #[test]
2954 fn decorate_registers_decorator() {
2955 let mut kit = Kit::new();
2956 kit.register_lazy::<DecModule>().unwrap();
2957 kit.decorate::<DecModule>(|cap| {
2958 Arc::new(DecCap {
2959 val: format!("{}+decorated", cap.val),
2960 })
2961 });
2962 let built = kit.build().unwrap();
2964 let cap = built.require::<DecModule>().unwrap();
2965 assert_eq!(cap.val, "original+decorated");
2966 }
2967}
2968
2969#[cfg(all(test, feature = "encryption"))]
2970mod encryption_tests {
2971 use super::*;
2972 use crate::kit::ModuleConfig;
2973 use serde::{Deserialize, Serialize};
2974
2975 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2976 struct SecretConfig {
2977 api_key: String,
2978 }
2979
2980 impl ModuleConfig for SecretConfig {
2981 const PATH: &'static str = "test.secret";
2982 fn default_value() -> Self {
2983 Self {
2984 api_key: "default".into(),
2985 }
2986 }
2987 }
2988
2989 #[test]
2990 fn set_and_get_encrypted_roundtrip() {
2991 let kit = Kit::new();
2992 let master_key = [0x42u8; 32];
2993 let config = SecretConfig {
2994 api_key: "super-secret".into(),
2995 };
2996 kit.set_encrypted(&config, &master_key).unwrap();
2997 assert!(kit.contains_encrypted::<SecretConfig>());
2998 let built = kit.build().unwrap();
2999 let decrypted: SecretConfig = built.get_encrypted(&master_key).unwrap();
3000 assert_eq!(decrypted, config);
3001 }
3002
3003 #[test]
3004 fn contains_encrypted_false_for_missing() {
3005 let kit = Kit::new();
3006 assert!(!kit.contains_encrypted::<SecretConfig>());
3007 }
3008
3009 #[test]
3010 fn get_encrypted_missing_returns_error() {
3011 let kit = Kit::new();
3012 let built = kit.build().unwrap();
3013 let master_key = [0x42u8; 32];
3014 let err = built
3015 .get_encrypted::<SecretConfig>(&master_key)
3016 .unwrap_err();
3017 assert!(matches!(err, TraitKitError::MissingConfig { .. }));
3018 }
3019
3020 #[test]
3021 fn secret_config_default_value() {
3022 let default = SecretConfig::default_value();
3023 assert_eq!(default.api_key, "default");
3024 }
3025
3026 #[test]
3027 fn get_encrypted_wrong_key_returns_error() {
3028 let kit = Kit::new();
3029 let master_key = [0x42u8; 32];
3030 let config = SecretConfig {
3031 api_key: "secret".into(),
3032 };
3033 kit.set_encrypted(&config, &master_key).unwrap();
3034 let built = kit.build().unwrap();
3035 let wrong_key = [0xFFu8; 32];
3037 let err = built.get_encrypted::<SecretConfig>(&wrong_key).unwrap_err();
3038 assert!(matches!(err, TraitKitError::BuildFailed { .. }));
3039 }
3040}
3041
3042#[cfg(test)]
3045mod ready_tests {
3046 use super::*;
3047 use crate::core::ModuleMeta;
3048 use std::sync::Arc;
3049
3050 struct ReadyMockModule;
3051 impl ModuleMeta for ReadyMockModule {
3052 const NAME: &'static str = "ready-mock";
3053 fn dependencies() -> &'static [(&'static str, TypeId)] {
3054 &[]
3055 }
3056 }
3057 impl AutoBuilder for ReadyMockModule {
3058 type Capability = Arc<()>;
3059 type Error = TraitKitError;
3060 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3061 Ok(Arc::new(()))
3062 }
3063 }
3064
3065 #[test]
3066 fn ready_optional_returns_none_for_unbuilt() {
3067 let kit = Kit::new();
3068 let built = kit.build().unwrap();
3069 assert!(built.optional::<ReadyMockModule>().is_none());
3070 }
3071
3072 #[test]
3073 fn ready_optional_returns_some_for_built() {
3074 let mut kit = Kit::new();
3075 kit.register::<ReadyMockModule>().unwrap();
3076 let built = kit.build().unwrap();
3077 assert!(built.optional::<ReadyMockModule>().is_some());
3078 }
3079
3080 #[test]
3081 fn ready_contains_returns_true_for_built() {
3082 let mut kit = Kit::new();
3083 kit.register::<ReadyMockModule>().unwrap();
3084 let built = kit.build().unwrap();
3085 assert!(built.contains::<ReadyMockModule>());
3086 }
3087
3088 #[test]
3089 fn ready_contains_returns_false_for_unbuilt() {
3090 let kit = Kit::new();
3091 let built = kit.build().unwrap();
3092 assert!(!built.contains::<ReadyMockModule>());
3093 }
3094
3095 #[test]
3096 fn ready_contains_config_returns_true() {
3097 let kit = Kit::new();
3098 kit.set_config(42i32);
3099 let built = kit.build().unwrap();
3100 assert!(built.contains_config::<i32>());
3101 }
3102
3103 #[test]
3104 fn ready_contains_config_returns_false() {
3105 let kit = Kit::new();
3106 let built = kit.build().unwrap();
3107 assert!(!built.contains_config::<u64>());
3108 }
3109
3110 #[test]
3111 fn debug_unbuilt_format() {
3112 let kit = Kit::new();
3113 let debug = format!("{kit:?}");
3114 assert!(debug.contains("Kit<Unbuilt>"));
3115 assert!(debug.contains("modules"));
3116 }
3117
3118 #[test]
3119 fn debug_ready_format() {
3120 let mut kit = Kit::new();
3121 kit.register::<ReadyMockModule>().unwrap();
3122 let built = kit.build().unwrap();
3123 let debug = format!("{built:?}");
3124 assert!(debug.contains("Kit<Ready>"));
3125 assert!(debug.contains("modules"));
3126 }
3127
3128 #[test]
3129 fn default_creates_empty_kit() {
3130 let kit = Kit::default();
3131 let built = kit.build().unwrap();
3132 assert_eq!(built.graph.entries().len(), 0);
3133 }
3134
3135 #[test]
3136 fn graph_dot_returns_valid_string() {
3137 let mut kit = Kit::new();
3138 kit.register::<ReadyMockModule>().unwrap();
3139 let built = kit.build().unwrap();
3140 let dot = built.graph_dot();
3141 assert!(dot.contains("digraph"));
3142 }
3143
3144 #[test]
3145 fn graph_mermaid_returns_valid_string() {
3146 let mut kit = Kit::new();
3147 kit.register::<ReadyMockModule>().unwrap();
3148 let built = kit.build().unwrap();
3149 let mermaid = built.graph_mermaid();
3150 assert!(mermaid.contains("graph TD"));
3151 }
3152
3153 #[test]
3154 fn config_missing_returns_error() {
3155 let kit = Kit::new();
3156 let built = kit.build().unwrap();
3157 let err = built.config::<i32>().unwrap_err();
3158 assert!(matches!(err, TraitKitError::MissingConfig { .. }));
3159 }
3160
3161 #[test]
3162 fn require_ref_returns_missing_for_unbuilt() {
3163 let kit = Kit::new();
3164 let built = kit.build().unwrap();
3165 let err = built.require_ref::<ReadyMockModule>().unwrap_err();
3166 assert!(matches!(err, TraitKitError::MissingCapability { .. }));
3167 }
3168
3169 #[test]
3170 fn build_missing_dependency_returns_error() {
3171 struct DepModule;
3172 impl ModuleMeta for DepModule {
3173 const NAME: &'static str = "dep";
3174 fn dependencies() -> &'static [(&'static str, TypeId)] {
3175 &[]
3176 }
3177 }
3178 impl AutoBuilder for DepModule {
3179 type Capability = Arc<()>;
3180 type Error = TraitKitError;
3181 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3182 Ok(Arc::new(()))
3183 }
3184 }
3185
3186 struct NeedsDepModule;
3187 impl ModuleMeta for NeedsDepModule {
3188 const NAME: &'static str = "needs-dep";
3189 fn dependencies() -> &'static [(&'static str, TypeId)] {
3190 static DEPS: &[(&str, TypeId)] = &[("dep", TypeId::of::<DepModule>())];
3191 DEPS
3192 }
3193 }
3194 impl AutoBuilder for NeedsDepModule {
3195 type Capability = Arc<()>;
3196 type Error = TraitKitError;
3197 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3198 Ok(Arc::new(()))
3199 }
3200 }
3201
3202 let mut kit = Kit::new();
3203 kit.register::<NeedsDepModule>().unwrap();
3204 let result = kit.build();
3206 assert!(result.is_err());
3207 assert!(matches!(
3208 result.unwrap_err(),
3209 TraitKitError::DependencyMissing { .. }
3210 ));
3211 }
3212
3213 #[test]
3214 fn build_cycle_detected_returns_error() {
3215 struct CycleA;
3216 impl ModuleMeta for CycleA {
3217 const NAME: &'static str = "cycle-a";
3218 fn dependencies() -> &'static [(&'static str, TypeId)] {
3219 static DEPS: &[(&str, TypeId)] = &[("cycle-b", TypeId::of::<CycleB>())];
3220 DEPS
3221 }
3222 }
3223 impl AutoBuilder for CycleA {
3224 type Capability = Arc<()>;
3225 type Error = TraitKitError;
3226 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3227 Ok(Arc::new(()))
3228 }
3229 }
3230
3231 struct CycleB;
3232 impl ModuleMeta for CycleB {
3233 const NAME: &'static str = "cycle-b";
3234 fn dependencies() -> &'static [(&'static str, TypeId)] {
3235 static DEPS: &[(&str, TypeId)] = &[("cycle-a", TypeId::of::<CycleA>())];
3236 DEPS
3237 }
3238 }
3239 impl AutoBuilder for CycleB {
3240 type Capability = Arc<()>;
3241 type Error = TraitKitError;
3242 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3243 Ok(Arc::new(()))
3244 }
3245 }
3246
3247 let mut kit = Kit::new();
3248 kit.register::<CycleA>().unwrap();
3249 kit.register::<CycleB>().unwrap();
3250 let result = kit.build();
3251 assert!(result.is_err());
3252 assert!(matches!(
3253 result.unwrap_err(),
3254 TraitKitError::CycleDetected { .. }
3255 ));
3256 }
3257
3258 #[test]
3259 fn lazy_require_build_error() {
3260 struct LazyFailModule;
3261 impl ModuleMeta for LazyFailModule {
3262 const NAME: &'static str = "lazy-fail";
3263 fn dependencies() -> &'static [(&'static str, TypeId)] {
3264 &[]
3265 }
3266 }
3267 impl AutoBuilder for LazyFailModule {
3268 type Capability = Arc<()>;
3269 type Error = TraitKitError;
3270 fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3271 Err(TraitKitError::BuildFailed {
3272 context: "lazy-fail".into(),
3273 source: Box::new(std::io::Error::other("lazy fail")),
3274 })
3275 }
3276 }
3277
3278 let mut kit = Kit::new();
3279 kit.register_lazy::<LazyFailModule>().unwrap();
3280 let built = kit.build().unwrap();
3281 let err = built.require::<LazyFailModule>().unwrap_err();
3282 assert!(matches!(err, TraitKitError::BuildFailed { .. }));
3283 }
3284}
3285
3286#[cfg(all(test, feature = "confers"))]
3289mod validation_tests {
3290 use super::*;
3291 use crate::kit::config::{Configurable, Validatable};
3292 use std::error::Error;
3293
3294 #[derive(Clone, Debug, PartialEq)]
3295 struct ValidConfig {
3296 port: u16,
3297 }
3298
3299 impl Configurable for ValidConfig {
3300 fn load() -> Result<Self, Box<dyn Error + Send>> {
3301 Ok(Self { port: 8080 })
3302 }
3303 }
3304
3305 impl Validatable for ValidConfig {
3306 fn validate(&self) -> Result<(), Vec<String>> {
3307 if self.port > 0 && self.port < 65535 {
3308 Ok(())
3309 } else {
3310 Err(vec!["port out of range".to_string()])
3311 }
3312 }
3313 }
3314
3315 #[derive(Clone, Debug, PartialEq)]
3316 struct InvalidConfig {
3317 port: u16,
3318 }
3319
3320 impl Configurable for InvalidConfig {
3321 fn load() -> Result<Self, Box<dyn Error + Send>> {
3322 Ok(Self { port: 0 })
3323 }
3324 }
3325
3326 impl Validatable for InvalidConfig {
3327 fn validate(&self) -> Result<(), Vec<String>> {
3328 Err(vec![
3329 "port must be > 0".to_string(),
3330 "port must be < 65535".to_string(),
3331 ])
3332 }
3333 }
3334
3335 #[test]
3336 fn load_and_validate_succeeds_with_valid_config() {
3337 let kit = Kit::new();
3338 kit.load_and_validate::<ValidConfig>()
3339 .expect("valid config should pass");
3340 let config: ValidConfig = kit.config().expect("config should be stored");
3341 assert_eq!(config.port, 8080);
3342 }
3343
3344 #[test]
3345 fn load_and_validate_fails_with_invalid_config() {
3346 let kit = Kit::new();
3347 let err = kit
3348 .load_and_validate::<InvalidConfig>()
3349 .expect_err("invalid config should fail");
3350 let msg = format!("{err}");
3351 assert!(
3352 msg.contains("port must be > 0"),
3353 "error should contain first validation error: {msg}"
3354 );
3355 assert!(
3356 msg.contains("port must be < 65535"),
3357 "error should contain second validation error: {msg}"
3358 );
3359 }
3360
3361 #[test]
3362 fn load_and_validate_does_not_store_on_failure() {
3363 let kit = Kit::new();
3364 let _ = kit.load_and_validate::<InvalidConfig>();
3365 let result: Result<InvalidConfig, _> = kit.config();
3366 assert!(result.is_err(), "invalid config should not be stored");
3367 }
3368
3369 #[test]
3370 fn load_and_validate_retry_after_failure() {
3371 let kit = Kit::new();
3372 let _ = kit.load_and_validate::<InvalidConfig>();
3373 kit.load_and_validate::<ValidConfig>()
3375 .expect("valid config should succeed after previous failure");
3376 let config: ValidConfig = kit.config().expect("valid config should be stored");
3377 assert_eq!(config.port, 8080);
3378 }
3379}
3380
3381#[cfg(all(test, feature = "confers"))]
3384mod snapshot_tests {
3385 use super::*;
3386 use crate::kit::config::Configurable;
3387 use std::error::Error;
3388
3389 #[derive(Clone, Debug, PartialEq)]
3390 struct SnapConfig {
3391 value: String,
3392 }
3393
3394 impl Configurable for SnapConfig {
3395 fn load() -> Result<Self, Box<dyn Error + Send>> {
3396 Ok(Self {
3397 value: "loaded".to_string(),
3398 })
3399 }
3400 }
3401
3402 #[test]
3403 fn snapshot_returns_true_when_config_exists() {
3404 let kit = Kit::new();
3405 kit.set_config(SnapConfig {
3406 value: "original".to_string(),
3407 });
3408 assert!(kit.snapshot_config::<SnapConfig>());
3409 }
3410
3411 #[test]
3412 fn snapshot_returns_false_when_config_missing() {
3413 let kit = Kit::new();
3414 assert!(!kit.snapshot_config::<SnapConfig>());
3415 }
3416
3417 #[test]
3418 fn restore_overwrites_current_config() {
3419 let kit = Kit::new();
3420 kit.set_config(SnapConfig {
3421 value: "original".to_string(),
3422 });
3423 kit.snapshot_config::<SnapConfig>();
3424 kit.set_config(SnapConfig {
3426 value: "modified".to_string(),
3427 });
3428 let current: SnapConfig = kit.config().unwrap();
3429 assert_eq!(current.value, "modified");
3430 kit.restore_config::<SnapConfig>()
3432 .expect("restore should succeed");
3433 let restored: SnapConfig = kit.config().unwrap();
3434 assert_eq!(restored.value, "original");
3435 }
3436
3437 #[test]
3438 fn restore_returns_error_when_no_snapshot() {
3439 let kit = Kit::new();
3440 let err = kit
3441 .restore_config::<SnapConfig>()
3442 .expect_err("restore without snapshot should fail");
3443 assert!(matches!(err, TraitKitError::MissingConfig { .. }));
3444 }
3445
3446 #[test]
3447 fn has_snapshot_reflects_state() {
3448 let kit = Kit::new();
3449 assert!(!kit.has_snapshot::<SnapConfig>());
3450 kit.set_config(SnapConfig {
3451 value: "test".to_string(),
3452 });
3453 kit.snapshot_config::<SnapConfig>();
3454 assert!(kit.has_snapshot::<SnapConfig>());
3455 }
3456
3457 #[test]
3458 fn snapshot_overwrite_replaces_previous() {
3459 let kit = Kit::new();
3460 kit.set_config(SnapConfig {
3461 value: "v1".to_string(),
3462 });
3463 kit.snapshot_config::<SnapConfig>();
3464 kit.set_config(SnapConfig {
3465 value: "v2".to_string(),
3466 });
3467 kit.snapshot_config::<SnapConfig>();
3468 kit.set_config(SnapConfig {
3470 value: "current".to_string(),
3471 });
3472 kit.restore_config::<SnapConfig>().unwrap();
3473 let restored: SnapConfig = kit.config().unwrap();
3474 assert_eq!(restored.value, "v2");
3475 }
3476}
3477
3478#[cfg(all(test, feature = "reload"))]
3481mod reload_tests {
3482 use super::*;
3483 use crate::kit::config::Configurable;
3484 use std::error::Error;
3485 use std::sync::Arc;
3486 use std::sync::atomic::{AtomicU32, Ordering};
3487
3488 #[derive(Clone, Debug, PartialEq)]
3489 struct ReloadConfig {
3490 version: u32,
3491 }
3492
3493 static LOAD_COUNT: AtomicU32 = AtomicU32::new(0);
3495
3496 impl Configurable for ReloadConfig {
3497 fn load() -> Result<Self, Box<dyn Error + Send>> {
3498 let v = LOAD_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
3499 Ok(Self { version: v })
3500 }
3501 }
3502
3503 #[test]
3504 fn reload_config_updates_value_and_fires_subscribers() {
3505 let kit = Kit::new();
3506 let notified = Arc::new(AtomicU32::new(0));
3507 let notified_clone = Arc::clone(¬ified);
3508 kit.subscribe::<ReloadConfig>(move || {
3509 notified_clone.fetch_add(1, Ordering::SeqCst);
3510 });
3511
3512 let before = LOAD_COUNT.load(Ordering::SeqCst);
3514 kit.set_config(ReloadConfig { version: 0 });
3515 kit.reload_config::<ReloadConfig>()
3516 .expect("reload should succeed");
3517 let cfg: ReloadConfig = kit.config().expect("config present");
3518 assert!(cfg.version > before, "reload must call Configurable::load");
3519 assert_eq!(
3520 notified.load(Ordering::SeqCst),
3521 1,
3522 "subscriber must be invoked exactly once"
3523 );
3524 }
3525
3526 #[test]
3527 fn reload_config_fires_all_subscribers() {
3528 let kit = Kit::new();
3529 let count = Arc::new(AtomicU32::new(0));
3530 for _ in 0..3 {
3531 let count = Arc::clone(&count);
3532 kit.subscribe::<ReloadConfig>(move || {
3533 count.fetch_add(1, Ordering::SeqCst);
3534 });
3535 }
3536 kit.set_config(ReloadConfig { version: 0 });
3537 kit.reload_config::<ReloadConfig>().expect("reload ok");
3538 assert_eq!(
3539 count.load(Ordering::SeqCst),
3540 3,
3541 "all three subscribers must fire"
3542 );
3543 }
3544}
3545
3546#[cfg(all(test, feature = "toggle"))]
3549mod toggle_tests {
3550 use super::*;
3551 use crate::core::ModuleMeta;
3552 use std::sync::Arc;
3553
3554 struct ToggleModule;
3555 impl ModuleMeta for ToggleModule {
3556 const NAME: &'static str = "toggle-mod";
3557 fn dependencies() -> &'static [(&'static str, TypeId)] {
3558 &[]
3559 }
3560 }
3561 impl AutoBuilder for ToggleModule {
3562 type Capability = Arc<String>;
3563 type Error = TraitKitError;
3564 fn build(_kit: &Kit) -> Result<Arc<String>, TraitKitError> {
3565 Ok(Arc::new("toggle-cap".to_string()))
3566 }
3567 }
3568
3569 #[test]
3570 fn enable_toggle_sets_value() {
3571 let kit = Kit::new();
3572 kit.enable_toggle("feature-a", true);
3573 assert!(kit.is_toggle_enabled("feature-a"));
3574 kit.enable_toggle("feature-a", false);
3575 assert!(!kit.is_toggle_enabled("feature-a"));
3576 }
3577
3578 #[test]
3579 fn is_toggle_enabled_returns_false_for_unknown() {
3580 let kit = Kit::new();
3581 assert!(!kit.is_toggle_enabled("nonexistent"));
3582 }
3583
3584 #[test]
3585 fn register_if_toggle_registers_when_enabled() {
3586 let mut kit = Kit::new();
3587 kit.enable_toggle("mod-x", true);
3588 let registered = kit
3589 .register_if_toggle::<ToggleModule>("mod-x")
3590 .expect("registration should succeed");
3591 assert!(registered);
3592 }
3593
3594 #[test]
3595 fn register_if_toggle_skips_when_disabled() {
3596 let mut kit = Kit::new();
3597 kit.enable_toggle("mod-x", false);
3598 let registered = kit
3599 .register_if_toggle::<ToggleModule>("mod-x")
3600 .expect("should return Ok(false)");
3601 assert!(!registered);
3602 }
3603
3604 #[test]
3605 fn register_if_toggle_returns_error_on_duplicate() {
3606 let mut kit = Kit::new();
3607 kit.enable_toggle("mod-x", true);
3608 kit.register_if_toggle::<ToggleModule>("mod-x")
3609 .expect("first registration");
3610 let err = kit
3611 .register_if_toggle::<ToggleModule>("mod-x")
3612 .expect_err("duplicate should fail");
3613 assert!(matches!(err, TraitKitError::AlreadyRegistered { .. }));
3614 }
3615
3616 #[test]
3617 fn toggle_state_survives_build() {
3618 let mut kit = Kit::new();
3619 kit.enable_toggle("persist", true);
3620 kit.register_if_toggle::<ToggleModule>("persist").unwrap();
3621 let ready = kit.build().unwrap();
3622 assert!(ready.is_toggle_enabled("persist"));
3623 }
3624
3625 #[test]
3626 fn toggle_enable_on_ready_state() {
3627 let mut kit = Kit::new();
3628 kit.register::<ToggleModule>().unwrap();
3629 let ready = kit.build().unwrap();
3630 ready.enable_toggle("runtime", true);
3631 assert!(ready.is_toggle_enabled("runtime"));
3632 }
3633
3634 #[test]
3635 fn toggle_disabled_capability_not_retrievable() {
3636 let mut kit = Kit::new();
3639 kit.enable_toggle("feature-gate", false);
3640 let registered = kit
3641 .register_if_toggle::<ToggleModule>("feature-gate")
3642 .expect("should return Ok(false)");
3643 assert!(!registered, "disabled toggle must not register the module");
3644
3645 let ready = kit.build().expect("build without the module succeeds");
3646 let err = ready.require::<ToggleModule>().unwrap_err();
3647 assert!(matches!(
3648 err,
3649 TraitKitError::MissingCapability { ref key } if key == "toggle-mod"
3650 ));
3651 assert!(!ready.contains::<ToggleModule>());
3652 assert!(ready.optional::<ToggleModule>().is_none());
3653 }
3654}
3655
3656#[cfg(all(test, feature = "confers"))]
3659mod interpolation_tests {
3660 use crate::kit::config::interpolate_json_value;
3661 use std::collections::HashMap;
3662
3663 #[test]
3664 fn basic_var_replacement() {
3665 let mut value = serde_json::json!("${HOST}");
3666 let mut vars = HashMap::new();
3667 vars.insert("HOST".to_string(), "localhost".to_string());
3668 interpolate_json_value(&mut value, &vars);
3669 assert_eq!(value, serde_json::json!("localhost"));
3670 }
3671
3672 #[test]
3673 fn default_value_when_var_missing() {
3674 let mut value = serde_json::json!("${HOST:-127.0.0.1}");
3675 let vars = HashMap::new();
3676 interpolate_json_value(&mut value, &vars);
3677 assert_eq!(value, serde_json::json!("127.0.0.1"));
3678 }
3679
3680 #[test]
3681 fn default_value_ignored_when_var_present() {
3682 let mut value = serde_json::json!("${HOST:-127.0.0.1}");
3683 let mut vars = HashMap::new();
3684 vars.insert("HOST".to_string(), "10.0.0.1".to_string());
3685 interpolate_json_value(&mut value, &vars);
3686 assert_eq!(value, serde_json::json!("10.0.0.1"));
3687 }
3688
3689 #[test]
3690 fn no_match_preserved() {
3691 let mut value = serde_json::json!("${UNKNOWN}");
3692 let vars = HashMap::new();
3693 interpolate_json_value(&mut value, &vars);
3694 assert_eq!(value, serde_json::json!("${UNKNOWN}"));
3695 }
3696
3697 #[test]
3698 fn nested_object_replacement() {
3699 let mut value = serde_json::json!({
3700 "db": {
3701 "host": "${DB_HOST}",
3702 "port": 5432
3703 }
3704 });
3705 let mut vars = HashMap::new();
3706 vars.insert("DB_HOST".to_string(), "db.example.com".to_string());
3707 interpolate_json_value(&mut value, &vars);
3708 assert_eq!(value["db"]["host"], serde_json::json!("db.example.com"));
3709 assert_eq!(value["db"]["port"], serde_json::json!(5432));
3711 }
3712
3713 #[test]
3714 fn array_string_elements_replaced() {
3715 let mut value = serde_json::json!(["${A}", "${B}", 42]);
3716 let mut vars = HashMap::new();
3717 vars.insert("A".to_string(), "alpha".to_string());
3718 vars.insert("B".to_string(), "beta".to_string());
3719 interpolate_json_value(&mut value, &vars);
3720 assert_eq!(value[0], serde_json::json!("alpha"));
3721 assert_eq!(value[1], serde_json::json!("beta"));
3722 assert_eq!(value[2], serde_json::json!(42));
3723 }
3724
3725 #[test]
3726 fn non_string_values_untouched() {
3727 let mut value = serde_json::json!({
3728 "num": 42,
3729 "bool": true,
3730 "null": null
3731 });
3732 let vars = HashMap::new();
3733 interpolate_json_value(&mut value, &vars);
3734 assert_eq!(value["num"], serde_json::json!(42));
3735 assert_eq!(value["bool"], serde_json::json!(true));
3736 assert_eq!(value["null"], serde_json::json!(null));
3737 }
3738
3739 #[test]
3740 fn object_keys_not_replaced() {
3741 let mut value = serde_json::json!({"${KEY}": "value"});
3742 let vars = HashMap::new();
3743 interpolate_json_value(&mut value, &vars);
3744 assert!(value.as_object().unwrap().contains_key("${KEY}"));
3746 }
3747
3748 #[test]
3749 fn multiple_vars_in_one_string() {
3750 let mut value = serde_json::json!("${HOST}:${PORT}");
3751 let mut vars = HashMap::new();
3752 vars.insert("HOST".to_string(), "localhost".to_string());
3753 vars.insert("PORT".to_string(), "8080".to_string());
3754 interpolate_json_value(&mut value, &vars);
3755 assert_eq!(value, serde_json::json!("localhost:8080"));
3756 }
3757}