1use std::any::{Any, TypeId};
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::sync::OnceLock;
11#[cfg(feature = "hot-reload")]
12use std::rc::Rc;
13
14use crate::core::{AutoBuilder, BuildFn};
15use crate::error::TraitKitError;
16
17#[cfg(feature = "encryption")]
18use super::EncryptedBlob;
19use super::TypeMap;
20use super::{DependencyGraph, GraphError, ModuleEntry};
21
22#[cfg(feature = "encryption")]
25const KEY_DERIVATION_VERSION: &str = "v1";
26
27#[cfg(feature = "encryption")]
29fn derive_kit_field_key(
30 master_key: &[u8],
31 path: &'static str,
32 context: &'static str,
33) -> Result<[u8; 32], TraitKitError> {
34 super::config::derive_field_key(master_key, path, KEY_DERIVATION_VERSION).map_err(|e| {
35 TraitKitError::BuildFailed {
36 context,
37 source: Box::new(e),
38 }
39 })
40}
41
42pub struct Unbuilt;
44
45pub struct Ready;
47
48#[cfg(feature = "hot-reload")]
50type SubscriberMap = RefCell<HashMap<TypeId, Vec<Rc<dyn Fn()>>>>;
51
52#[cfg(feature = "encryption")]
54type EncryptedConfigMap = RefCell<HashMap<TypeId, EncryptedBlob>>;
55
56struct LazySlot {
61 builder: Option<BuildFn>,
62 cell: OnceLock<Box<dyn Any>>,
63}
64
65pub struct Kit<S = Unbuilt> {
67 builders: RefCell<HashMap<TypeId, BuildFn>>,
68 overrides: RefCell<HashMap<TypeId, Box<dyn Any>>>,
71 lazy_builders: RefCell<HashMap<TypeId, BuildFn>>,
74 lazy_slots: RefCell<HashMap<TypeId, LazySlot>>,
77 multi_builders: RefCell<HashMap<TypeId, Vec<BuildFn>>>,
83 multi_capabilities: RefCell<HashMap<TypeId, Vec<Box<dyn Any>>>>,
87 #[cfg(feature = "interface")]
92 interface_builders: RefCell<HashMap<TypeId, BuildFn>>,
93 graph: DependencyGraph,
94 configs: TypeMap,
95 capabilities: TypeMap,
96 #[cfg(feature = "hot-reload")]
97 subscribers: SubscriberMap,
98 #[cfg(feature = "encryption")]
99 encrypted_configs: EncryptedConfigMap,
100 _state: std::marker::PhantomData<S>,
101}
102
103impl Kit {
104 #[must_use]
106 pub fn new() -> Self {
107 Kit {
108 builders: RefCell::new(HashMap::new()),
109 overrides: RefCell::new(HashMap::new()),
110 lazy_builders: RefCell::new(HashMap::new()),
111 lazy_slots: RefCell::new(HashMap::new()),
112 multi_builders: RefCell::new(HashMap::new()),
113 multi_capabilities: RefCell::new(HashMap::new()),
114 #[cfg(feature = "interface")]
115 interface_builders: RefCell::new(HashMap::new()),
116 graph: DependencyGraph::new(),
117 configs: TypeMap::new(),
118 capabilities: TypeMap::new(),
119 #[cfg(feature = "hot-reload")]
120 subscribers: RefCell::new(HashMap::new()),
121 #[cfg(feature = "encryption")]
122 encrypted_configs: RefCell::new(HashMap::new()),
123 _state: std::marker::PhantomData,
124 }
125 }
126
127 pub fn register<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError> {
133 let entry = ModuleEntry {
134 type_id: TypeId::of::<M>(),
135 name: M::NAME,
136 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
137 };
138
139 self.graph
140 .add(entry)
141 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
142
143 let build_fn: BuildFn = Box::new(|kit| {
144 let capability = M::build(kit)
145 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
146 Ok(Box::new(capability) as Box<dyn Any>)
147 });
148
149 self.builders
150 .borrow_mut()
151 .insert(TypeId::of::<M>(), build_fn);
152 Ok(())
153 }
154
155 pub fn register_lazy<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError>
172 where
173 M::Capability: Clone + 'static,
174 {
175 let entry = ModuleEntry {
176 type_id: TypeId::of::<M>(),
177 name: M::NAME,
178 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
179 };
180
181 self.graph
182 .add(entry)
183 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
184
185 let build_fn: BuildFn = Box::new(|kit| {
186 let capability = M::build(kit)
187 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
188 Ok(Box::new(capability) as Box<dyn Any>)
189 });
190
191 self.lazy_builders
192 .borrow_mut()
193 .insert(TypeId::of::<M>(), build_fn);
194 Ok(())
195 }
196
197 pub fn register_multi<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError>
219 where
220 M::Capability: Clone + 'static,
221 {
222 let entry = ModuleEntry {
223 type_id: TypeId::of::<M>(),
224 name: M::NAME,
225 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
226 };
227
228 self.graph
229 .add(entry)
230 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
231
232 let build_fn: BuildFn = Box::new(|kit| {
233 let capability = M::build(kit)
234 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
235 Ok(Box::new(capability) as Box<dyn Any>)
236 });
237
238 let cap_id = TypeId::of::<M::Capability>();
241 self.multi_builders
242 .borrow_mut()
243 .entry(cap_id)
244 .or_default()
245 .push(build_fn);
246 Ok(())
247 }
248
249 #[cfg(feature = "interface")]
267 pub fn register_as<M>(&mut self) -> Result<(), TraitKitError>
268 where
269 M: crate::core::InterfaceBuilder,
270 {
271 let interface_id = TypeId::of::<M::Interface>();
272
273 if self.interface_builders.borrow().contains_key(&interface_id) {
275 return Err(TraitKitError::AlreadyRegistered { module: M::NAME });
276 }
277
278 let entry = ModuleEntry {
279 type_id: TypeId::of::<M>(),
280 name: M::NAME,
281 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
282 };
283
284 self.graph
285 .add(entry)
286 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
287
288 let build_fn: BuildFn = Box::new(|kit| {
289 let cap = M::build(kit)
290 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
291 let iface: std::sync::Arc<M::Interface> = M::into_interface(cap);
292 Ok(Box::new(iface) as Box<dyn Any>)
293 });
294
295 self.interface_builders
296 .borrow_mut()
297 .insert(interface_id, build_fn);
298 Ok(())
299 }
300
301 pub fn override_module<M: AutoBuilder>(&self, capability: M::Capability)
311 where
312 M::Capability: 'static,
313 {
314 self.overrides
315 .borrow_mut()
316 .insert(TypeId::of::<M>(), Box::new(capability));
317 }
318
319 pub fn override_module_strict<M: AutoBuilder>(
335 &mut self,
336 capability: M::Capability,
337 ) -> Result<(), TraitKitError>
338 where
339 M::Capability: 'static,
340 {
341 for (dep_name, dep_id) in M::dependencies() {
342 if self.graph.name_of(*dep_id).is_none() {
343 return Err(TraitKitError::DependencyMissing {
344 module: M::NAME,
345 missing: *dep_name,
346 });
347 }
348 }
349 self.overrides
350 .borrow_mut()
351 .insert(TypeId::of::<M>(), Box::new(capability));
352 Ok(())
353 }
354
355 pub fn set_config<C: Clone + 'static>(&self, config: C) {
357 self.configs.insert(config);
358 }
359
360 #[cfg(feature = "confers")]
370 pub fn load_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
371 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
372 context: "load_config",
373 source: e,
374 })?;
375 self.set_config(config);
376 Ok(())
377 }
378
379 pub fn build(self) -> Result<Kit<Ready>, TraitKitError> {
390 let sorted = match self.graph.validate() {
391 Ok(sorted) => sorted,
392 Err(GraphError::DependencyMissing { module, missing }) => {
393 return Err(TraitKitError::DependencyMissing { module, missing });
394 }
395 Err(GraphError::CycleDetected { cycle }) => {
396 return Err(TraitKitError::CycleDetected { cycle });
397 }
398 };
399
400 {
401 let kit_ref: &Self = &self;
402
403 for type_id in &sorted {
404 let module_name = kit_ref.module_name(*type_id);
405
406 if let Some(boxed) = kit_ref.overrides.borrow_mut().remove(type_id) {
409 kit_ref.capabilities.insert_boxed(*type_id, boxed);
410 continue;
411 }
412
413 if kit_ref.lazy_builders.borrow().contains_key(type_id) {
417 continue;
418 }
419
420 let build_fn = match kit_ref.builders.borrow_mut().remove(type_id) {
425 Some(fn_) => fn_,
426 None => continue,
427 };
428
429 let result = (build_fn)(kit_ref);
430 match result {
431 Ok(boxed) => {
432 kit_ref.capabilities.insert_boxed(*type_id, boxed);
433 }
434 Err(e) => {
435 return Err(TraitKitError::BuildFailed {
436 context: module_name,
437 source: e,
438 });
439 }
440 }
441 }
442 }
443
444 {
448 let remaining: Vec<(TypeId, Box<dyn Any>)> =
449 self.overrides.borrow_mut().drain().collect();
450 for (type_id, boxed) in remaining {
451 self.capabilities.insert_boxed(type_id, boxed);
452 }
453 }
454
455 {
460 let lazy: Vec<(TypeId, BuildFn)> = self.lazy_builders.borrow_mut().drain().collect();
461 for (type_id, builder) in lazy {
462 self.lazy_slots.borrow_mut().insert(
463 type_id,
464 LazySlot {
465 builder: Some(builder),
466 cell: OnceLock::new(),
467 },
468 );
469 }
470 }
471
472 {
478 let multi: Vec<(TypeId, Vec<BuildFn>)> =
479 self.multi_builders.borrow_mut().drain().collect();
480 for (cap_id, build_fns) in multi {
481 let mut vec = Vec::with_capacity(build_fns.len());
482 for build_fn in build_fns {
483 let boxed = (build_fn)(&self).map_err(|e| TraitKitError::BuildFailed {
484 context: "<multi-binding>",
485 source: e,
486 })?;
487 vec.push(boxed);
488 }
489 self.multi_capabilities.borrow_mut().insert(cap_id, vec);
490 }
491 }
492
493 #[cfg(feature = "interface")]
498 {
499 let interfaces: Vec<(TypeId, BuildFn)> =
500 self.interface_builders.borrow_mut().drain().collect();
501 for (interface_id, build_fn) in interfaces {
502 let boxed = (build_fn)(&self).map_err(|e| TraitKitError::BuildFailed {
503 context: "<interface>",
504 source: e,
505 })?;
506 self.capabilities.insert_boxed(interface_id, boxed);
507 }
508 }
509
510 Ok(Kit {
511 builders: self.builders,
512 overrides: self.overrides,
513 lazy_builders: self.lazy_builders,
514 lazy_slots: self.lazy_slots,
515 multi_builders: self.multi_builders,
516 multi_capabilities: self.multi_capabilities,
517 #[cfg(feature = "interface")]
518 interface_builders: self.interface_builders,
519 graph: self.graph,
520 configs: self.configs,
521 capabilities: self.capabilities,
522 #[cfg(feature = "hot-reload")]
523 subscribers: self.subscribers,
524 #[cfg(feature = "encryption")]
525 encrypted_configs: self.encrypted_configs,
526 _state: std::marker::PhantomData,
527 })
528 }
529
530 fn module_name(&self, type_id: TypeId) -> &'static str {
531 self.graph.name_of(type_id).unwrap_or("<unknown>")
532 }
533}
534
535impl<S> Kit<S> {
536 pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, TraitKitError> {
552 let type_id = TypeId::of::<M>();
553
554 if let Some(cap) = self
556 .capabilities
557 .get_cloned_by_type_id::<M::Capability>(type_id)
558 {
559 return Ok(cap);
560 }
561
562 if let Some(boxed) = self
564 .lazy_slots
565 .borrow()
566 .get(&type_id)
567 .and_then(|slot| slot.cell.get())
568 {
569 return boxed
570 .downcast_ref::<M::Capability>()
571 .cloned()
572 .ok_or(TraitKitError::MissingCapability { key: M::NAME });
573 }
574
575 let builder = self
579 .lazy_slots
580 .borrow_mut()
581 .get_mut(&type_id)
582 .and_then(|slot| slot.builder.take());
583
584 if let Some(builder) = builder {
585 #[allow(unsafe_code)]
593 let kit_ref: &Kit = unsafe {
594 &*(std::ptr::from_ref(self) as *const Kit)
595 };
596 let boxed = (builder)(kit_ref).map_err(|e| TraitKitError::BuildFailed {
597 context: M::NAME,
598 source: e,
599 })?;
600 if let Some(slot) = self.lazy_slots.borrow().get(&type_id) {
602 let _ = slot.cell.set(boxed);
603 }
604 return self
605 .lazy_slots
606 .borrow()
607 .get(&type_id)
608 .and_then(|slot| slot.cell.get())
609 .and_then(|b| b.downcast_ref::<M::Capability>().cloned())
610 .ok_or(TraitKitError::MissingCapability { key: M::NAME });
611 }
612
613 Err(TraitKitError::MissingCapability { key: M::NAME })
615 }
616
617 pub fn require_all<M: AutoBuilder>(&self) -> Result<Vec<M::Capability>, TraitKitError>
629 where
630 M::Capability: Clone + 'static,
631 {
632 let cap_id = TypeId::of::<M::Capability>();
633 let multi = self.multi_capabilities.borrow();
634 let vec = multi
635 .get(&cap_id)
636 .ok_or(TraitKitError::MissingCapability { key: M::NAME })?;
637
638 let mut result = Vec::with_capacity(vec.len());
639 for boxed in vec.iter() {
640 let cap = boxed
641 .downcast_ref::<M::Capability>()
642 .cloned()
643 .ok_or(TraitKitError::MissingCapability { key: M::NAME })?;
644 result.push(cap);
645 }
646 Ok(result)
647 }
648
649 pub fn config<C: Clone + 'static>(&self) -> Result<C, TraitKitError> {
655 self.configs
656 .get_cloned::<C>()
657 .ok_or(TraitKitError::MissingConfig {
658 key: std::any::type_name::<C>(),
659 })
660 }
661
662 #[cfg(feature = "hot-reload")]
671 pub fn subscribe<C: 'static>(&self, callback: impl Fn() + 'static) {
672 let callback: Rc<dyn Fn()> = Rc::new(callback);
673 self.subscribers
674 .borrow_mut()
675 .entry(TypeId::of::<C>())
676 .or_default()
677 .push(callback);
678 }
679
680 #[cfg(feature = "hot-reload")]
699 pub fn reload_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
700 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
701 context: "reload_config",
702 source: e,
703 })?;
704 self.configs.insert(config);
705 let callbacks: Vec<Rc<dyn Fn()>> = self
708 .subscribers
709 .borrow()
710 .get(&TypeId::of::<C>())
711 .cloned()
712 .unwrap_or_default();
713 for cb in &callbacks {
714 cb();
715 }
716 Ok(())
717 }
718
719 #[cfg(feature = "interface")]
733 pub fn resolve<I>(&self) -> Result<std::sync::Arc<I>, TraitKitError>
734 where
735 I: ?Sized + 'static,
736 {
737 let interface_id = TypeId::of::<I>();
738 self.capabilities
739 .get_cloned_by_type_id::<std::sync::Arc<I>>(interface_id)
740 .ok_or(TraitKitError::MissingCapability { key: "interface" })
741 }
742}
743
744impl Kit {
745 #[cfg(feature = "encryption")]
761 pub fn set_encrypted<C>(&self, value: &C, master_key: &[u8]) -> Result<(), TraitKitError>
762 where
763 C: super::ModuleConfig + serde::Serialize,
764 {
765 use super::XChaCha20Crypto;
766
767 let plaintext = serde_json::to_vec(value).map_err(|e| TraitKitError::BuildFailed {
768 context: "set_encrypted",
769 source: Box::new(e),
770 })?;
771
772 let field_key = derive_kit_field_key(master_key, C::PATH, "set_encrypted")?;
773
774 let (nonce, ciphertext) = XChaCha20Crypto::new()
775 .encrypt(&plaintext, &field_key)
776 .map_err(|e| TraitKitError::BuildFailed {
777 context: "set_encrypted",
778 source: Box::new(e),
779 })?;
780
781 self.encrypted_configs
782 .borrow_mut()
783 .insert(TypeId::of::<C>(), EncryptedBlob { nonce, ciphertext });
784 Ok(())
785 }
786
787 #[cfg(feature = "encryption")]
789 pub fn contains_encrypted<C: super::ModuleConfig>(&self) -> bool {
790 self.encrypted_configs
791 .borrow()
792 .contains_key(&TypeId::of::<C>())
793 }
794
795 #[cfg(feature = "confers-macros")]
807 pub fn load_config_or_default<C>(&self) -> Result<(), TraitKitError>
808 where
809 C: super::Configurable + super::ModuleConfig,
810 {
811 let config = match C::load() {
812 Ok(value) => value,
813 Err(_) => C::default_value(),
814 };
815 self.set_config(config);
816 Ok(())
817 }
818}
819
820impl Kit<Ready> {
821 pub fn optional<M: AutoBuilder>(&self) -> Option<M::Capability> {
823 let type_id = TypeId::of::<M>();
824 self.capabilities
825 .get_cloned_by_type_id::<M::Capability>(type_id)
826 }
827
828 pub fn require_ref<M: AutoBuilder>(&self) -> Result<std::cell::Ref<'_, M::Capability>, TraitKitError>
840 where
841 M::Capability: 'static,
842 {
843 use std::cell::Ref;
844
845 let type_id = TypeId::of::<M>();
846 if !self.capabilities.contains_by_type_id(type_id) {
847 return Err(TraitKitError::MissingCapability { key: M::NAME });
848 }
849 Ref::filter_map(self.capabilities.inner_ref(), |map| {
850 map.get(&type_id).and_then(|b| b.downcast_ref::<M::Capability>())
851 })
852 .map_err(|_| TraitKitError::MissingCapability { key: M::NAME })
853 }
854
855 pub fn contains<M: AutoBuilder>(&self) -> bool {
857 self.capabilities.contains_by_type_id(TypeId::of::<M>())
858 }
859
860 pub fn contains_config<C: Clone + 'static>(&self) -> bool {
862 self.configs.contains::<C>()
863 }
864
865 #[cfg(feature = "encryption")]
878 pub fn get_encrypted<C>(&self, master_key: &[u8]) -> Result<C, TraitKitError>
879 where
880 C: super::ModuleConfig + serde::de::DeserializeOwned,
881 {
882 use super::XChaCha20Crypto;
883
884 let blob = self
885 .encrypted_configs
886 .borrow()
887 .get(&TypeId::of::<C>())
888 .cloned()
889 .ok_or(TraitKitError::MissingConfig {
890 key: std::any::type_name::<C>(),
891 })?;
892
893 let field_key = derive_kit_field_key(master_key, C::PATH, "get_encrypted")?;
894
895 let plaintext = XChaCha20Crypto::new()
896 .decrypt(&blob.nonce, &blob.ciphertext, &field_key)
897 .map_err(|e| TraitKitError::BuildFailed {
898 context: "get_encrypted",
899 source: Box::new(e),
900 })?;
901
902 serde_json::from_slice(&plaintext).map_err(|e| TraitKitError::BuildFailed {
903 context: "get_encrypted",
904 source: Box::new(e),
905 })
906 }
907}
908
909impl Default for Kit {
910 fn default() -> Self {
911 Self::new()
912 }
913}
914
915impl std::fmt::Debug for Kit {
916 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
917 f.debug_struct("Kit<Unbuilt>")
918 .field("modules", &self.graph.entries().len())
919 .field("configs", &self.configs.len())
920 .finish()
921 }
922}
923
924impl std::fmt::Debug for Kit<Ready> {
925 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
926 f.debug_struct("Kit<Ready>")
927 .field("modules", &self.graph.entries().len())
928 .field("configs", &self.configs.len())
929 .finish()
930 }
931}
932
933#[cfg(test)]
934mod tests {
935 use super::*;
936 use crate::core::{AutoBuilder, ModuleMeta};
937 use std::sync::atomic::{AtomicUsize, Ordering};
938 use std::sync::Arc;
939
940 struct MockCapability;
943 impl ModuleMeta for MockCapability {
944 const NAME: &'static str = "mock";
945 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
946 &[]
947 }
948 }
949 impl AutoBuilder for MockCapability {
950 type Capability = Arc<AtomicUsize>;
951 type Error = TraitKitError;
952 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
953 Ok(Arc::new(AtomicUsize::new(0)))
954 }
955 }
956
957 struct DependentModule;
958 impl ModuleMeta for DependentModule {
959 const NAME: &'static str = "dependent";
960 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
961 static DEPS: &[(&str, std::any::TypeId)] =
962 &[("mock", std::any::TypeId::of::<MockCapability>())];
963 DEPS
964 }
965 }
966 impl AutoBuilder for DependentModule {
967 type Capability = Arc<AtomicUsize>;
968 type Error = TraitKitError;
969 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
970 Ok(Arc::new(AtomicUsize::new(0)))
971 }
972 }
973
974 #[test]
977 fn overrides_field_is_empty_on_new() {
978 let kit = Kit::new();
979 assert_eq!(kit.overrides.borrow().len(), 0);
980 }
981
982 #[test]
983 fn overrides_field_is_empty_after_build() {
984 let kit = Kit::new();
985 assert_eq!(kit.overrides.borrow().len(), 0);
986 }
987
988 #[test]
991 fn override_module_inserts_into_overrides_map() {
992 let kit = Kit::new();
993 assert_eq!(kit.overrides.borrow().len(), 0);
994 kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(42)));
995 assert_eq!(kit.overrides.borrow().len(), 1);
996 }
997
998 #[test]
999 fn override_module_strict_succeeds_when_deps_registered() {
1000 let mut kit = Kit::new();
1001 kit.register::<MockCapability>().unwrap();
1003 let result = kit.override_module_strict::<DependentModule>(Arc::new(AtomicUsize::new(99)));
1005 assert!(result.is_ok());
1006 assert_eq!(kit.overrides.borrow().len(), 1);
1007 }
1008
1009 #[test]
1010 fn override_module_strict_fails_when_deps_missing() {
1011 let mut kit = Kit::new();
1012 let result = kit.override_module_strict::<DependentModule>(Arc::new(AtomicUsize::new(99)));
1014 assert!(matches!(
1015 result,
1016 Err(TraitKitError::DependencyMissing { module: "dependent", missing: "mock" })
1017 ));
1018 assert_eq!(kit.overrides.borrow().len(), 0);
1020 }
1021
1022 struct CountingModule;
1026 impl ModuleMeta for CountingModule {
1027 const NAME: &'static str = "counting";
1028 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1029 &[]
1030 }
1031 }
1032 impl AutoBuilder for CountingModule {
1033 type Capability = Arc<AtomicUsize>;
1034 type Error = TraitKitError;
1035 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1036 Ok(Arc::new(AtomicUsize::new(0)))
1039 }
1040 }
1041
1042 #[test]
1043 fn build_uses_override_and_skips_build_fn() {
1044 let kit = Kit::new();
1045 let mut kit = kit;
1047 kit.register::<CountingModule>().unwrap();
1048 kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(42)));
1050 let built = kit.build().unwrap();
1052 let cap = built.require::<CountingModule>().unwrap();
1054 assert_eq!(cap.load(Ordering::SeqCst), 42);
1055 }
1056
1057 #[test]
1058 fn build_uses_build_fn_when_no_override() {
1059 let mut kit = Kit::new();
1060 kit.register::<CountingModule>().unwrap();
1061 let built = kit.build().unwrap();
1063 let cap = built.require::<CountingModule>().unwrap();
1064 assert_eq!(cap.load(Ordering::SeqCst), 0);
1065 }
1066
1067 #[test]
1068 fn build_inserts_unregistered_override_after_topo_loop() {
1069 let kit = Kit::new();
1072 kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(77)));
1073 let built = kit.build().unwrap();
1074 let cap = built.require::<MockCapability>().unwrap();
1075 assert_eq!(cap.load(Ordering::SeqCst), 77);
1076 }
1077
1078 #[test]
1081 fn require_ref_returns_reference_to_built_capability() {
1082 let mut kit = Kit::new();
1083 kit.register::<CountingModule>().unwrap();
1084 let built = kit.build().unwrap();
1085 let r = built.require_ref::<CountingModule>().unwrap();
1086 assert_eq!((*r).load(Ordering::SeqCst), 0);
1088 }
1089
1090 #[test]
1091 fn require_ref_returns_override_value() {
1092 let mut kit = Kit::new();
1093 kit.register::<CountingModule>().unwrap();
1094 kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(55)));
1095 let built = kit.build().unwrap();
1096 let r = built.require_ref::<CountingModule>().unwrap();
1097 assert_eq!((*r).load(Ordering::SeqCst), 55);
1098 }
1099
1100 #[test]
1101 fn require_ref_returns_missing_capability_for_unbuilt() {
1102 let kit = Kit::new();
1103 let built = kit.build().unwrap();
1104 let result = built.require_ref::<CountingModule>();
1105 assert!(matches!(
1106 result,
1107 Err(TraitKitError::MissingCapability { key: "counting" })
1108 ));
1109 }
1110
1111 #[test]
1114 fn register_lazy_does_not_build_during_build() {
1115 let mut kit = Kit::new();
1116 kit.register_lazy::<CountingModule>().unwrap();
1117 let built = kit.build().unwrap();
1119 assert!(!built.contains::<CountingModule>());
1121 }
1122
1123 #[test]
1124 fn register_lazy_adds_to_dependency_graph() {
1125 let mut kit = Kit::new();
1126 kit.register::<MockCapability>().unwrap();
1128 kit.register_lazy::<DependentModule>().unwrap();
1130 let built = kit.build().unwrap();
1132 assert!(built.contains::<MockCapability>());
1134 assert!(!built.contains::<DependentModule>());
1135 }
1136
1137 #[test]
1138 fn register_lazy_returns_already_registered_for_duplicate() {
1139 let mut kit = Kit::new();
1140 kit.register_lazy::<CountingModule>().unwrap();
1141 let result = kit.register_lazy::<CountingModule>();
1142 assert!(matches!(
1143 result,
1144 Err(TraitKitError::AlreadyRegistered { module: "counting" })
1145 ));
1146 }
1147
1148 #[test]
1151 fn lazy_slots_empty_on_new_kit() {
1152 let kit = Kit::new();
1153 assert_eq!(kit.lazy_slots.borrow().len(), 0);
1154 }
1155
1156 #[test]
1157 fn build_transfers_lazy_builders_to_lazy_slots() {
1158 let mut kit = Kit::new();
1159 kit.register_lazy::<CountingModule>().unwrap();
1160 assert_eq!(kit.lazy_builders.borrow().len(), 1);
1161 assert_eq!(kit.lazy_slots.borrow().len(), 0);
1162
1163 let built = kit.build().unwrap();
1164
1165 assert_eq!(built.lazy_builders.borrow().len(), 0);
1167 assert_eq!(built.lazy_slots.borrow().len(), 1);
1168 assert!(built
1169 .lazy_slots
1170 .borrow()
1171 .contains_key(&TypeId::of::<CountingModule>()));
1172 }
1173
1174 #[test]
1175 fn lazy_slots_cells_empty_after_build() {
1176 let mut kit = Kit::new();
1177 kit.register_lazy::<CountingModule>().unwrap();
1178 let built = kit.build().unwrap();
1179
1180 let slots = built.lazy_slots.borrow();
1183 let slot = slots.get(&TypeId::of::<CountingModule>()).expect("slot exists");
1184 assert!(slot.cell.get().is_none());
1185 }
1186
1187 #[test]
1188 fn build_transfers_multiple_lazy_builders_to_lazy_slots() {
1189 let mut kit = Kit::new();
1190 kit.register::<MockCapability>().unwrap();
1191 kit.register_lazy::<DependentModule>().unwrap();
1192 kit.register_lazy::<CountingModule>().unwrap();
1193 assert_eq!(kit.lazy_builders.borrow().len(), 2);
1194
1195 let built = kit.build().unwrap();
1196
1197 assert_eq!(built.lazy_builders.borrow().len(), 0);
1198 assert_eq!(built.lazy_slots.borrow().len(), 2);
1199 assert!(built
1200 .lazy_slots
1201 .borrow()
1202 .contains_key(&TypeId::of::<DependentModule>()));
1203 assert!(built
1204 .lazy_slots
1205 .borrow()
1206 .contains_key(&TypeId::of::<CountingModule>()));
1207 }
1208
1209 #[test]
1212 fn require_triggers_lazy_construction_on_first_access() {
1213 let mut kit = Kit::new();
1214 kit.register_lazy::<CountingModule>().unwrap();
1215 let built = kit.build().unwrap();
1216
1217 assert!(!built.contains::<CountingModule>());
1219
1220 let cap = built.require::<CountingModule>().unwrap();
1222 assert_eq!(cap.load(Ordering::SeqCst), 0);
1223 }
1224
1225 #[test]
1226 fn require_does_not_rebuild_lazy_on_second_call() {
1227 static COUNT: AtomicUsize = AtomicUsize::new(0);
1229
1230 struct CountedModule;
1231 impl ModuleMeta for CountedModule {
1232 const NAME: &'static str = "test-counted";
1233 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1234 &[]
1235 }
1236 }
1237 impl AutoBuilder for CountedModule {
1238 type Capability = Arc<AtomicUsize>;
1239 type Error = TraitKitError;
1240 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1241 let n = COUNT.fetch_add(1, Ordering::SeqCst);
1242 Ok(Arc::new(AtomicUsize::new(n)))
1243 }
1244 }
1245
1246 COUNT.store(0, Ordering::SeqCst);
1247 let mut kit = Kit::new();
1248 kit.register_lazy::<CountedModule>().unwrap();
1249 let built = kit.build().unwrap();
1250
1251 let cap1 = built.require::<CountedModule>().unwrap();
1252 let cap2 = built.require::<CountedModule>().unwrap();
1253
1254 assert_eq!(cap1.load(Ordering::SeqCst), 0, "first require returns count 0");
1256 assert_eq!(cap2.load(Ordering::SeqCst), 0, "second require returns same count");
1257 assert_eq!(COUNT.load(Ordering::SeqCst), 1, "builder invoked exactly once");
1258 }
1259
1260 #[test]
1261 fn require_lazy_with_registered_dependency_succeeds() {
1262 struct LazyDependentModule;
1264 impl ModuleMeta for LazyDependentModule {
1265 const NAME: &'static str = "lazy-dependent";
1266 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1267 static DEPS: &[(&str, std::any::TypeId)] =
1268 &[("mock", std::any::TypeId::of::<MockCapability>())];
1269 DEPS
1270 }
1271 }
1272 impl AutoBuilder for LazyDependentModule {
1273 type Capability = Arc<AtomicUsize>;
1274 type Error = TraitKitError;
1275 fn build(kit: &Kit) -> Result<Self::Capability, Self::Error> {
1276 let mock = kit.require::<MockCapability>()?;
1278 Ok(Arc::new(AtomicUsize::new(mock.load(Ordering::SeqCst) + 100)))
1279 }
1280 }
1281
1282 let mut kit = Kit::new();
1283 kit.register::<MockCapability>().unwrap();
1286 kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(42)));
1287 kit.register_lazy::<LazyDependentModule>().unwrap();
1288 let built = kit.build().unwrap();
1289
1290 let cap = built.require::<LazyDependentModule>().unwrap();
1292 assert_eq!(cap.load(Ordering::SeqCst), 142, "lazy build accessed eager dep (42 + 100)");
1293 }
1294
1295 struct MultiModuleA;
1299 impl ModuleMeta for MultiModuleA {
1300 const NAME: &'static str = "multi-a";
1301 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1302 &[]
1303 }
1304 }
1305 impl AutoBuilder for MultiModuleA {
1306 type Capability = Arc<AtomicUsize>;
1307 type Error = TraitKitError;
1308 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1309 Ok(Arc::new(AtomicUsize::new(10)))
1310 }
1311 }
1312
1313 struct MultiModuleB;
1315 impl ModuleMeta for MultiModuleB {
1316 const NAME: &'static str = "multi-b";
1317 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1318 &[]
1319 }
1320 }
1321 impl AutoBuilder for MultiModuleB {
1322 type Capability = Arc<AtomicUsize>;
1323 type Error = TraitKitError;
1324 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1325 Ok(Arc::new(AtomicUsize::new(20)))
1326 }
1327 }
1328
1329 struct MultiModuleC;
1331 impl ModuleMeta for MultiModuleC {
1332 const NAME: &'static str = "multi-c";
1333 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1334 &[]
1335 }
1336 }
1337 impl AutoBuilder for MultiModuleC {
1338 type Capability = Arc<AtomicUsize>;
1339 type Error = TraitKitError;
1340 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1341 Ok(Arc::new(AtomicUsize::new(30)))
1342 }
1343 }
1344
1345 #[test]
1346 fn multi_builders_empty_on_new_kit() {
1347 let kit = Kit::new();
1348 assert_eq!(kit.multi_builders.borrow().len(), 0);
1349 }
1350
1351 #[test]
1352 fn register_multi_adds_to_multi_builders() {
1353 let mut kit = Kit::new();
1354 assert_eq!(kit.multi_builders.borrow().len(), 0);
1355
1356 kit.register_multi::<MultiModuleA>().unwrap();
1357
1358 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
1360 assert_eq!(kit.multi_builders.borrow().len(), 1);
1361 assert!(kit.multi_builders.borrow().contains_key(&cap_id));
1362 assert_eq!(
1363 kit.multi_builders.borrow().get(&cap_id).unwrap().len(),
1364 1,
1365 "first register_multi should produce Vec of length 1"
1366 );
1367 }
1368
1369 #[test]
1370 fn register_multi_three_times_appends_to_vec() {
1371 let mut kit = Kit::new();
1372 kit.register_multi::<MultiModuleA>().unwrap();
1373 kit.register_multi::<MultiModuleB>().unwrap();
1374 kit.register_multi::<MultiModuleC>().unwrap();
1375
1376 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
1377 let builders = kit.multi_builders.borrow();
1378 let vec = builders.get(&cap_id).expect("cap_id exists");
1379 assert_eq!(vec.len(), 3, "three register_multi calls should produce Vec of length 3");
1380 }
1381
1382 #[test]
1383 fn register_multi_adds_module_to_dependency_graph() {
1384 let mut kit = Kit::new();
1385 kit.register_multi::<MultiModuleA>().unwrap();
1386
1387 assert!(kit.graph.name_of(TypeId::of::<MultiModuleA>()).is_some());
1389 }
1390
1391 #[test]
1392 fn register_multi_returns_already_registered_for_duplicate_module() {
1393 let mut kit = Kit::new();
1394 kit.register_multi::<MultiModuleA>().unwrap();
1395
1396 let result = kit.register_multi::<MultiModuleA>();
1397 assert!(matches!(
1398 result,
1399 Err(TraitKitError::AlreadyRegistered { module: "multi-a" })
1400 ));
1401 }
1402
1403 #[test]
1404 fn register_multi_returns_already_registered_if_already_registered_via_register() {
1405 let mut kit = Kit::new();
1406 kit.register::<MockCapability>().unwrap();
1407
1408 let result = kit.register_multi::<MockCapability>();
1409 assert!(matches!(
1410 result,
1411 Err(TraitKitError::AlreadyRegistered { module: "mock" })
1412 ));
1413 }
1414
1415 #[test]
1416 fn register_multi_coexists_with_register_for_different_modules() {
1417 let mut kit = Kit::new();
1418 kit.register::<MockCapability>().unwrap();
1419 kit.register_multi::<MultiModuleA>().unwrap();
1420 kit.register_multi::<MultiModuleB>().unwrap();
1421
1422 assert!(kit.builders.borrow().contains_key(&TypeId::of::<MockCapability>()));
1424 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
1425 assert_eq!(
1426 kit.multi_builders.borrow().get(&cap_id).unwrap().len(),
1427 2
1428 );
1429 }
1430
1431 #[test]
1434 fn require_all_returns_empty_for_unregistered_capability() {
1435 let mut kit = Kit::new();
1436 kit.register::<MockCapability>().unwrap();
1438 let built = kit.build().unwrap();
1439
1440 let result = built.require_all::<MultiModuleA>();
1442 assert!(matches!(
1443 result,
1444 Err(TraitKitError::MissingCapability { key: "multi-a" })
1445 ));
1446 }
1447
1448 #[test]
1449 fn require_all_returns_vec_of_three_after_three_register_multi() {
1450 let mut kit = Kit::new();
1451 kit.register_multi::<MultiModuleA>().unwrap();
1452 kit.register_multi::<MultiModuleB>().unwrap();
1453 kit.register_multi::<MultiModuleC>().unwrap();
1454 let built = kit.build().unwrap();
1455
1456 let caps = built.require_all::<MultiModuleA>().unwrap();
1457 assert_eq!(caps.len(), 3, "three register_multi calls should return Vec of length 3");
1458 }
1459
1460 #[test]
1461 fn require_all_preserves_registration_order() {
1462 let mut kit = Kit::new();
1463 kit.register_multi::<MultiModuleA>().unwrap(); kit.register_multi::<MultiModuleB>().unwrap(); kit.register_multi::<MultiModuleC>().unwrap(); let built = kit.build().unwrap();
1467
1468 let caps = built.require_all::<MultiModuleA>().unwrap();
1469 assert_eq!(caps.len(), 3);
1470 assert_eq!(caps[0].load(Ordering::SeqCst), 10, "first cap should be 10 (MultiModuleA)");
1472 assert_eq!(caps[1].load(Ordering::SeqCst), 20, "second cap should be 20 (MultiModuleB)");
1473 assert_eq!(caps[2].load(Ordering::SeqCst), 30, "third cap should be 30 (MultiModuleC)");
1474 }
1475
1476 #[test]
1477 fn require_all_returns_missing_capability_before_build() {
1478 let mut kit = Kit::new();
1479 kit.register_multi::<MultiModuleA>().unwrap();
1480 let result = kit.require_all::<MultiModuleA>();
1483 assert!(matches!(
1484 result,
1485 Err(TraitKitError::MissingCapability { key: "multi-a" })
1486 ));
1487 }
1488
1489 #[test]
1490 fn build_drains_multi_builders_into_multi_capabilities() {
1491 let mut kit = Kit::new();
1492 kit.register_multi::<MultiModuleA>().unwrap();
1493 kit.register_multi::<MultiModuleB>().unwrap();
1494
1495 assert_eq!(kit.multi_builders.borrow().len(), 1); assert_eq!(kit.multi_capabilities.borrow().len(), 0);
1498
1499 let built = kit.build().unwrap();
1500
1501 assert_eq!(built.multi_builders.borrow().len(), 0);
1503 assert_eq!(built.multi_capabilities.borrow().len(), 1);
1504 let cap_id = TypeId::of::<Arc<AtomicUsize>>();
1505 assert_eq!(
1506 built.multi_capabilities.borrow().get(&cap_id).unwrap().len(),
1507 2
1508 );
1509 }
1510
1511 #[test]
1512 fn require_all_coexists_with_require_for_single_binding() {
1513 let mut kit = Kit::new();
1514 kit.register::<MockCapability>().unwrap();
1516 kit.register_multi::<MultiModuleA>().unwrap();
1518 kit.register_multi::<MultiModuleB>().unwrap();
1519 let built = kit.build().unwrap();
1520
1521 let single = built.require::<MockCapability>().unwrap();
1523 assert_eq!(single.load(Ordering::SeqCst), 0);
1524
1525 let multi = built.require_all::<MultiModuleA>().unwrap();
1527 assert_eq!(multi.len(), 2);
1528 assert_eq!(multi[0].load(Ordering::SeqCst), 10);
1529 assert_eq!(multi[1].load(Ordering::SeqCst), 20);
1530 }
1531}
1532
1533#[cfg(all(test, feature = "interface"))]
1534mod interface_tests {
1535 use super::*;
1536 use crate::core::{InterfaceBuilder, ModuleMeta};
1537 use std::sync::atomic::{AtomicUsize, Ordering};
1538 use std::sync::Arc;
1539
1540 trait Logger: 'static {
1544 fn log(&self, msg: &str) -> String;
1545 }
1546
1547 struct ConsoleLogger;
1549
1550 impl Logger for ConsoleLogger {
1551 fn log(&self, msg: &str) -> String {
1552 format!("[console] {msg}")
1553 }
1554 }
1555
1556 struct FileLogger;
1558
1559 impl Logger for FileLogger {
1560 fn log(&self, msg: &str) -> String {
1561 format!("[file] {msg}")
1562 }
1563 }
1564
1565 #[derive(Debug)]
1567 struct InterfaceTestError;
1568
1569 impl std::fmt::Display for InterfaceTestError {
1570 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1571 write!(f, "interface test error")
1572 }
1573 }
1574
1575 impl std::error::Error for InterfaceTestError {}
1576
1577 struct ConsoleLoggerModule;
1579
1580 impl ModuleMeta for ConsoleLoggerModule {
1581 const NAME: &'static str = "console-logger-iface";
1582 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1583 &[]
1584 }
1585 }
1586
1587 impl InterfaceBuilder for ConsoleLoggerModule {
1588 type Interface = dyn Logger;
1589 type Capability = Arc<ConsoleLogger>;
1590 type Error = InterfaceTestError;
1591
1592 fn build(_kit: &Kit) -> Result<Arc<ConsoleLogger>, InterfaceTestError> {
1593 Ok(Arc::new(ConsoleLogger))
1594 }
1595
1596 fn into_interface(cap: Arc<ConsoleLogger>) -> Arc<dyn Logger> {
1597 cap
1598 }
1599 }
1600
1601 struct FileLoggerModule;
1603
1604 impl ModuleMeta for FileLoggerModule {
1605 const NAME: &'static str = "file-logger";
1606 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1607 &[]
1608 }
1609 }
1610
1611 impl InterfaceBuilder for FileLoggerModule {
1612 type Interface = dyn Logger;
1613 type Capability = Arc<FileLogger>;
1614 type Error = InterfaceTestError;
1615
1616 fn build(_kit: &Kit) -> Result<Arc<FileLogger>, InterfaceTestError> {
1617 Ok(Arc::new(FileLogger))
1618 }
1619
1620 fn into_interface(cap: Arc<FileLogger>) -> Arc<dyn Logger> {
1621 cap
1622 }
1623 }
1624
1625 #[test]
1628 fn register_as_then_resolve_returns_arc_dyn_trait() {
1629 let mut kit = Kit::new();
1630 kit.register_as::<ConsoleLoggerModule>()
1631 .expect("register_as succeeds");
1632 let built = kit.build().expect("build succeeds");
1633
1634 let logger: Arc<dyn Logger> = built.resolve::<dyn Logger>().expect("resolve succeeds");
1635 assert_eq!(logger.log("hello"), "[console] hello");
1636 }
1637
1638 #[test]
1639 fn register_as_twice_same_interface_returns_already_registered() {
1640 let mut kit = Kit::new();
1641 kit.register_as::<ConsoleLoggerModule>()
1642 .expect("first register_as succeeds");
1643 let err = kit.register_as::<FileLoggerModule>().unwrap_err();
1644 assert!(
1645 matches!(err, TraitKitError::AlreadyRegistered { .. }),
1646 "expected AlreadyRegistered, got {err:?}"
1647 );
1648 }
1649
1650 #[test]
1651 fn resolve_before_build_returns_missing_capability() {
1652 let mut kit = Kit::new();
1653 kit.register_as::<ConsoleLoggerModule>()
1654 .expect("register_as succeeds");
1655 assert!(kit.resolve::<dyn Logger>().is_err());
1657 }
1658
1659 #[test]
1660 fn resolve_unregistered_interface_returns_missing_capability() {
1661 let kit = Kit::new();
1662 let built = kit.build().expect("build succeeds");
1663 assert!(built.resolve::<dyn Logger>().is_err());
1664 }
1665
1666 #[test]
1667 fn register_as_builds_during_build() {
1668 let mut kit = Kit::new();
1669 kit.register_as::<ConsoleLoggerModule>()
1670 .expect("register_as succeeds");
1671 let built = kit.build().expect("build succeeds");
1672 let logger = built.resolve::<dyn Logger>().expect("resolve succeeds");
1674 assert_eq!(logger.log("test"), "[console] test");
1675 }
1676
1677 #[test]
1678 fn resolve_returns_callable_trait_object() {
1679 let mut kit = Kit::new();
1680 kit.register_as::<ConsoleLoggerModule>()
1681 .expect("register_as succeeds");
1682 let built = kit.build().expect("build succeeds");
1683
1684 let logger: Arc<dyn Logger> = built.resolve().expect("resolve succeeds");
1685 let result = logger.log("world");
1686 assert_eq!(result, "[console] world");
1687 }
1688
1689 #[test]
1690 fn register_as_coexists_with_register() {
1691 struct RegularModule;
1694 impl ModuleMeta for RegularModule {
1695 const NAME: &'static str = "regular";
1696 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1697 &[]
1698 }
1699 }
1700 impl AutoBuilder for RegularModule {
1701 type Capability = Arc<AtomicUsize>;
1702 type Error = TraitKitError;
1703 fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
1704 Ok(Arc::new(AtomicUsize::new(42)))
1705 }
1706 }
1707
1708 let mut kit = Kit::new();
1709 kit.register::<RegularModule>().expect("register succeeds");
1710 kit.register_as::<ConsoleLoggerModule>()
1711 .expect("register_as succeeds");
1712 let built = kit.build().expect("build succeeds");
1713
1714 let cap = built.require::<RegularModule>().expect("require succeeds");
1716 assert_eq!(cap.load(Ordering::SeqCst), 42);
1717
1718 let logger = built.resolve::<dyn Logger>().expect("resolve succeeds");
1719 assert_eq!(logger.log("coexist"), "[console] coexist");
1720 }
1721
1722 #[test]
1723 fn register_as_same_module_twice_returns_already_registered() {
1724 let mut kit = Kit::new();
1725 kit.register_as::<ConsoleLoggerModule>()
1726 .expect("first register_as succeeds");
1727 let err = kit.register_as::<ConsoleLoggerModule>().unwrap_err();
1729 assert!(
1730 matches!(err, TraitKitError::AlreadyRegistered { .. }),
1731 "expected AlreadyRegistered, got {err:?}"
1732 );
1733 }
1734}