Skip to main content

trait_kit/kit/
kit.rs

1// Copyright (c) 2026 Kirky.X
2// SPDX-License-Identifier: MIT
3//! Kit — the capability and configuration management center.
4//!
5//! Uses typestate pattern: `Kit` (unbuilt) → `Kit<Ready>` (after `build()`).
6
7use 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/// HKDF key-derivation version label bound into every per-field key.
23/// Bumping this rotates all encrypted configs without changing master keys.
24#[cfg(feature = "encryption")]
25const KEY_DERIVATION_VERSION: &str = "v1";
26
27/// Derive a per-field encryption key, mapping HKDF failures to `TraitKitError`.
28#[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
42/// Marker type for the unbuilt state.
43pub struct Unbuilt;
44
45/// Marker type for the ready (built) state.
46pub struct Ready;
47
48/// Type alias for hot-reload subscriber callbacks (single-threaded, `!Sync`).
49#[cfg(feature = "hot-reload")]
50type SubscriberMap = RefCell<HashMap<TypeId, Vec<Rc<dyn Fn()>>>>;
51
52/// Type alias for the encrypted config store (single-threaded, `!Sync`).
53#[cfg(feature = "encryption")]
54type EncryptedConfigMap = RefCell<HashMap<TypeId, EncryptedBlob>>;
55
56/// A lazy construction slot: holds a build_fn and a OnceLock cache cell.
57/// The builder is invoked on first access; the result is cached in the
58/// OnceLock for subsequent accesses. After construction, `builder` is
59/// `None` (consumed) and `cell` holds the built capability.
60struct LazySlot {
61    builder: Option<BuildFn>,
62    cell: OnceLock<Box<dyn Any>>,
63}
64
65/// The capability and configuration management center.
66pub struct Kit<S = Unbuilt> {
67    builders: RefCell<HashMap<TypeId, BuildFn>>,
68    /// Override map for test injection: `TypeId` of module → pre-built capability.
69    /// Populated by `override_module` / `override_module_strict`; consumed by `build()`.
70    overrides: RefCell<HashMap<TypeId, Box<dyn Any>>>,
71    /// Lazy builders (Unbuilt state): modules registered via `register_lazy`.
72    /// Transferred to `lazy_slots` during `build()`.
73    lazy_builders: RefCell<HashMap<TypeId, BuildFn>>,
74    /// Lazy slots (Ready state): build_fn + OnceLock cache. Populated by
75    /// `build()` from `lazy_builders`. Consumed by `require()` on first access.
76    lazy_slots: RefCell<HashMap<TypeId, LazySlot>>,
77    /// Multi-binding builders (Unbuilt state): modules registered via
78    /// `register_multi`. Keyed by `TypeId::of::<M::Capability>()` (not the
79    /// module type) so multiple module types with the same capability type
80    /// aggregate into one Vec. Built into `multi_capabilities` during
81    /// `build()` by T011.
82    multi_builders: RefCell<HashMap<TypeId, Vec<BuildFn>>>,
83    /// Multi-binding capabilities (Ready state): built results from
84    /// `multi_builders`. Keyed by `TypeId::of::<M::Capability>()`.
85    /// Populated by `build()`; consumed by `require_all()`.
86    multi_capabilities: RefCell<HashMap<TypeId, Vec<Box<dyn Any>>>>,
87    /// Interface builders (Unbuilt state): modules registered via
88    /// `register_as`. Keyed by `TypeId::of::<M::Interface>()` (not the
89    /// module type) so `resolve::<I>()` retrieves by interface type.
90    /// Built into `capabilities` during `build()` (T015).
91    #[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    /// Create a new empty Kit.
105    #[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    /// Register a module for construction.
128    ///
129    /// # Errors
130    ///
131    /// Returns `TraitKitError::AlreadyRegistered` if a module with the same `TypeId` was already registered.
132    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    /// Register a module for lazy construction.
156    ///
157    /// The module is added to the dependency graph (for validation) but its
158    /// `build_fn` is **not** invoked during `build()`. Instead, the build_fn
159    /// is stored in `lazy_builders` and transferred to `Kit<Ready>.lazy_slots`
160    /// during `build()`. The capability is constructed on first `require()`
161    /// call and cached via `OnceLock` for subsequent accesses.
162    ///
163    /// This is useful for modules that are expensive to build or may never
164    /// be needed in a particular run.
165    ///
166    /// # Errors
167    ///
168    /// Returns `TraitKitError::AlreadyRegistered` if the module was already
169    /// registered (via `register` or `register_lazy`).
170    /// Returns `TraitKitError::DependencyMissing` if a dependency is not registered.
171    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    /// Register a module for multi-binding construction.
198    ///
199    /// Multiple module types that share the same `M::Capability` type can be
200    /// registered via `register_multi`; their build_fns are appended to a
201    /// `Vec` keyed by `TypeId::of::<M::Capability>()` (the capability type,
202    /// not the module type). The Vec preserves registration order.
203    ///
204    /// The module is also added to the dependency graph for validation, so
205    /// `M` must be distinct from any previously registered module (via
206    /// `register`, `register_lazy`, or `register_multi`). Two registrations
207    /// of the same module type `M` will return `AlreadyRegistered`.
208    ///
209    /// During `build()`, all multi-binding builders are invoked and the
210    /// results are stored in `multi_capabilities` (T011). Use `require_all`
211    /// to retrieve the ordered Vec of capabilities.
212    ///
213    /// # Errors
214    ///
215    /// Returns `TraitKitError::AlreadyRegistered` if `M` was already registered
216    /// (via any `register*` method). Dependency validation is deferred to
217    /// `build()` (via `graph.validate()`).
218    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        // Aggregate by capability type so require_all::<M>() returns all
239        // implementations of the same capability type.
240        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    /// Register a module for interface-based construction.
250    ///
251    /// Unlike `register`, this method stores the build_fn keyed by
252    /// `TypeId::of::<M::Interface>()` (the interface type, not the module
253    /// type). The module's `into_interface` method converts the concrete
254    /// capability into `Arc<M::Interface>` during `build()`, enabling
255    /// type-erased retrieval via `resolve::<I>()`.
256    ///
257    /// Only one implementation per interface type is allowed. For multiple
258    /// implementations of the same capability type, use `register_multi`
259    /// instead.
260    ///
261    /// # Errors
262    ///
263    /// Returns `TraitKitError::AlreadyRegistered` if the interface type was
264    /// already registered via `register_as`, or if the module type `M` was
265    /// already registered via any `register*` method.
266    #[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        // One implementation per interface type.
274        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    /// Override a module's capability with a pre-built value, skipping `build_fn`.
302    ///
303    /// Used for test injection: inject a mock capability without running the
304    /// module's build function. Completely skips dependency checking (pure
305    /// unit testing). The module does **not** need to be registered via
306    /// `register()` first — the override is keyed by `TypeId::of::<M>()`.
307    ///
308    /// If `build()` is called later, the override is consumed and the
309    /// original `build_fn` (if any) is never invoked for this module.
310    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    /// Override a module's capability with a pre-built value, but still
320    /// verify that the module's declared dependencies are registered in the
321    /// dependency graph.
322    ///
323    /// Unlike `override_module`, this method requires `&mut self` (exclusive
324    /// access) and checks `M::dependencies()` against the graph. If any
325    /// dependency is not registered, returns `TraitKitError::DependencyMissing`.
326    ///
327    /// The module does **not** need to be registered via `register()` first.
328    /// Only the dependencies must be present.
329    ///
330    /// # Errors
331    ///
332    /// Returns `TraitKitError::DependencyMissing` if any of `M::dependencies()`
333    /// is not registered in the graph.
334    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    /// Set a configuration value.
356    pub fn set_config<C: Clone + 'static>(&self, config: C) {
357        self.configs.insert(config);
358    }
359
360    /// Load a configuration via its [`Configurable`] implementation and store it.
361    ///
362    /// Requires the `confers` feature. The type must implement `Configurable`,
363    /// typically by delegating to `confers::Config`'s derived `load_sync()`.
364    /// The loaded value overrides any prior `set_config` of the same type.
365    ///
366    /// # Errors
367    ///
368    /// Returns `TraitKitError::BuildFailed` if `Configurable::load` fails.
369    #[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    /// Validate the dependency graph and build all modules in topological order.
380    ///
381    /// After this call, all capabilities are available via `require()`.
382    ///
383    /// # Errors
384    ///
385    /// Returns `TraitKitError::DependencyMissing` if a registered module depends on an unregistered module.
386    /// Returns `TraitKitError::CycleDetected` if a dependency cycle is found.
387    /// Returns `TraitKitError::MissingCapability` if a build function is missing for a sorted module.
388    /// Returns `TraitKitError::BuildFailed` if a module's `build` callback returns an error.
389    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                // [Override] Priority 1: check overrides map first.
407                // If an override exists, use it and skip build_fn entirely.
408                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                // [Lazy] Skip lazy-registered modules — they are not built
414                // during build(). Their build_fn stays in lazy_builders and
415                // will be transferred to Kit<Ready>.lazy_slots (T008).
416                if kit_ref.lazy_builders.borrow().contains_key(type_id) {
417                    continue;
418                }
419
420                // [Build] Priority 2: invoke the registered build_fn.
421                // If not in builders, the module was registered via
422                // `register_multi` — skip here; built in the multi_builders
423                // loop below (T011).
424                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        // [Override] Handle modules that were overridden but NOT registered
445        // (override_module allows injecting unregistered modules). These are
446        // not in the sorted list, so we insert them after the topo loop.
447        {
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        // [Lazy] Transfer lazy_builders to lazy_slots for first-access
456        // construction in Kit<Ready>. Each LazySlot wraps the build_fn with
457        // an empty OnceLock cache cell. The builder is Option::Some until
458        // consumed by the first require() call (T009).
459        {
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        // [Multi] Build all multi-binding modules and store in
473        // multi_capabilities. Keyed by TypeId::of::<M::Capability>() so
474        // require_all::<M>() retrieves all implementations of the same
475        // capability type. Runs after the topo-sorted loop so eager
476        // dependencies are available via `self.capabilities`.
477        {
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        // [Interface] Build all interface-registered modules and store in
494        // capabilities. Keyed by TypeId::of::<M::Interface>() so
495        // resolve::<I>() retrieves by interface type. Runs after the
496        // topo-sorted loop so eager dependencies are available (T015).
497        #[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    /// Retrieve a capability by its module type.
537    ///
538    /// Available on both `Kit<Unbuilt>` (inside `AutoBuilder::build` callbacks)
539    /// and `Kit<Ready>` (after `build()` completes).
540    ///
541    /// On `Kit<Ready>`, if the module was registered via `register_lazy`,
542    /// the first `require()` call triggers lazy construction: the stored
543    /// `build_fn` is invoked, the result is cached in a `OnceLock` cell,
544    /// and subsequent calls return a clone from the cache without re-running
545    /// the builder.
546    ///
547    /// # Errors
548    ///
549    /// Returns `TraitKitError::MissingCapability` if the module has not been built.
550    /// Returns `TraitKitError::BuildFailed` if a lazy module's `build_fn` fails.
551    pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, TraitKitError> {
552        let type_id = TypeId::of::<M>();
553
554        // 1. Eager capabilities (already-built modules + overrides + previously-built lazy)
555        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        // 2. Lazy slots — check OnceLock cache first (previously-built lazy modules)
563        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        // 3. Lazy slots — first-access construction (cell empty, builder exists)
576        // Take the builder out to release the RefCell borrow before calling it,
577        // allowing the builder to re-enter require() for its own dependencies.
578        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            // SAFETY: `Kit<S>` has the same memory layout as `Kit<Unbuilt>`
586            // because `S` only appears in `PhantomData<S>` (zero-sized, same
587            // representation as `()`). `BuildFn` expects `&Kit<Unbuilt>`; we
588            // hold `&Kit<S>`. The cast is sound for any `S` since the field
589            // layout is identical. In practice, this code path is only reached
590            // on `Kit<Ready>` (lazy_slots is only populated after `build()`),
591            // but the cast is valid regardless.
592            #[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            // Cache in OnceLock for future require() / require_ref() calls
601            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        // 4. Not found
614        Err(TraitKitError::MissingCapability { key: M::NAME })
615    }
616
617    /// Retrieve all capabilities registered via `register_multi` for the
618    /// given module type, in registration order.
619    ///
620    /// Available on both `Kit<Unbuilt>` and `Kit<Ready>`, but
621    /// `multi_capabilities` is only populated after `build()`. Calling
622    /// `require_all` before `build()` returns `MissingCapability`.
623    ///
624    /// # Errors
625    ///
626    /// Returns `TraitKitError::MissingCapability` if no multi-binding
627    /// capabilities were registered for `M::Capability`.
628    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    /// Get a configuration value.
650    ///
651    /// # Errors
652    ///
653    /// Returns `TraitKitError::MissingConfig` if no value of type `C` was set.
654    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    /// Subscribe a callback to be invoked when config of type `C` is reloaded.
663    ///
664    /// Requires the `hot-reload` feature. The callback receives no
665    /// arguments; use `Kit::config::<C>()` inside it to read the new value.
666    /// Callbacks are stored in a `RefCell` (single-threaded, `!Sync`).
667    ///
668    /// Layer 2 of the inheritance system: cargo feature chain
669    /// `hot-reload` → `confers-macros` → `confers`.
670    #[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    /// Reload a configuration via its [`Configurable`] implementation and
681    /// notify all subscribers of type `C`.
682    ///
683    /// Requires the `hot-reload` feature. Calls `C::load()`, stores
684    /// the result via `set_config`, then invokes every `subscribe::<C>`
685    /// callback. Errors from `load()` are mapped to `TraitKitError::BuildFailed`.
686    ///
687    /// # Panics
688    ///
689    /// The new config is stored *before* invoking callbacks. If a callback
690    /// panics, the config has already been updated but remaining subscribers
691    /// in the chain are skipped (panic unwinds through `reload_config`).
692    /// Use `std::panic::catch_unwind` inside callbacks if you need to
693    /// guarantee notification of all subscribers.
694    ///
695    /// # Errors
696    ///
697    /// Returns `TraitKitError::BuildFailed` if `Configurable::load` fails.
698    #[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        // Clone the Rc list out to avoid holding the RefCell borrow across
706        // user callbacks (which may re-enter subscribe).
707        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    /// Resolve a capability by its interface type.
720    ///
721    /// Retrieves an `Arc<I>` previously stored via `register_as<M>()`.
722    /// The interface type `I` must be `?Sized + 'static` (e.g.,
723    /// `dyn Logger`).
724    ///
725    /// Available on both `Kit<Unbuilt>` (inside `InterfaceBuilder::build`
726    /// callbacks) and `Kit<Ready>` (after `build()` completes).
727    ///
728    /// # Errors
729    ///
730    /// Returns `TraitKitError::MissingCapability` if the interface has not
731    /// been registered or built.
732    #[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    /// Encrypt and store a configuration value.
746    ///
747    /// Requires the `encryption` feature. Serializes `value` to JSON,
748    /// derives a per-field key from `master_key` and `C::PATH` via HKDF, then
749    /// encrypts with XChaCha20-Poly1305. The resulting nonce + ciphertext is
750    /// stored in `encrypted_configs`, separate from the plaintext `TypeMap`.
751    ///
752    /// Layer 3 of the inheritance system: the encryption key is bound to
753    /// `ModuleConfig::PATH`, so the same master key produces different field
754    /// keys for different modules.
755    ///
756    /// # Errors
757    ///
758    /// Returns `TraitKitError::BuildFailed` if serialization, key derivation, or
759    /// encryption fails.
760    #[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    /// Check if an encrypted config of type `C` is registered.
788    #[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    /// Load a configuration via `Configurable::load`, falling back to
796    /// `ModuleConfig::default_value` if loading fails.
797    ///
798    /// Requires the `confers-macros` feature. Stores the resulting value
799    /// via `set_config`, overriding any prior value of the same type.
800    ///
801    /// # Errors
802    ///
803    /// Never returns an error: load failures are silently replaced by the
804    /// module's declared default. Inspect the stored value via `config::<C>()`
805    /// if you need to distinguish "loaded" from "defaulted".
806    #[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    /// Retrieve an optional capability. Returns `None` if not built.
822    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    /// Retrieve a capability by reference, avoiding `Clone`.
829    ///
830    /// Unlike `require()`, this returns a `Ref` borrowing the stored value
831    /// directly, with no clone overhead. The `Ref` holds a read lock on the
832    /// interior `RefCell` — while it is alive, calling `reload_config` or
833    /// any mutating method will panic (`borrow_mut` conflict). Keep the
834    /// `Ref` lifetime short.
835    ///
836    /// # Errors
837    ///
838    /// Returns `TraitKitError::MissingCapability` if the module has not been built.
839    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    /// Check if a capability has been built.
856    pub fn contains<M: AutoBuilder>(&self) -> bool {
857        self.capabilities.contains_by_type_id(TypeId::of::<M>())
858    }
859
860    /// Check if a config is registered.
861    pub fn contains_config<C: Clone + 'static>(&self) -> bool {
862        self.configs.contains::<C>()
863    }
864
865    /// Retrieve and decrypt a configuration value.
866    ///
867    /// Requires the `encryption` feature. Looks up the encrypted
868    /// blob for type `C`, derives the per-field key from `master_key` and
869    /// `C::PATH`, decrypts with XChaCha20-Poly1305, then deserializes from
870    /// JSON. The `master_key` must match the one passed to `set_encrypted`.
871    ///
872    /// # Errors
873    ///
874    /// Returns `TraitKitError::MissingConfig` if no encrypted blob for `C` exists.
875    /// Returns `TraitKitError::BuildFailed` if key derivation, decryption, or
876    /// deserialization fails (e.g. wrong master key, tampered ciphertext).
877    #[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    // === Test fixtures ===
941
942    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    // === T002 tests ===
975
976    #[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    // === T003 tests ===
989
990    #[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        // Register the dependency first
1002        kit.register::<MockCapability>().unwrap();
1003        // Now strict override of the dependent module should succeed
1004        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        // Do NOT register MockCapability first
1013        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        // Override should not have been inserted
1019        assert_eq!(kit.overrides.borrow().len(), 0);
1020    }
1021
1022    // === T004 tests ===
1023
1024    /// Module whose build_fn increments a counter, to verify override skips it.
1025    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            // Return a counter that starts at 0; the test checks the counter
1037            // value to distinguish "build_fn ran" from "override used".
1038            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        // Register the module (so it's in the graph and gets sorted)
1046        let mut kit = kit;
1047        kit.register::<CountingModule>().unwrap();
1048        // Override with a capability value of 42
1049        kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(42)));
1050        // Build
1051        let built = kit.build().unwrap();
1052        // require() should return the override value (42), not the build_fn value (0)
1053        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        // No override — build_fn should run and produce value 0
1062        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        // override_module allows injecting a module that was NOT registered.
1070        // build() should still make it available via require().
1071        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    // === T005 tests ===
1079
1080    #[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        // build_fn returns Arc<AtomicUsize::new(0)>
1087        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    // === T007 tests ===
1112
1113    #[test]
1114    fn register_lazy_does_not_build_during_build() {
1115        let mut kit = Kit::new();
1116        kit.register_lazy::<CountingModule>().unwrap();
1117        // build() should succeed without triggering CountingModule's build_fn
1118        let built = kit.build().unwrap();
1119        // The capability should NOT be available (lazy not yet triggered)
1120        assert!(!built.contains::<CountingModule>());
1121    }
1122
1123    #[test]
1124    fn register_lazy_adds_to_dependency_graph() {
1125        let mut kit = Kit::new();
1126        // Register dependency first
1127        kit.register::<MockCapability>().unwrap();
1128        // Register lazy module that depends on MockCapability
1129        kit.register_lazy::<DependentModule>().unwrap();
1130        // build() should succeed (graph validation passes)
1131        let built = kit.build().unwrap();
1132        // MockCapability should be built (eager), DependentModule should NOT (lazy)
1133        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    // === T008 tests ===
1149
1150    #[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        // After build(): lazy_builders drained, lazy_slots populated
1166        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        // The OnceLock cell should be empty (not yet constructed) — first
1181        // access via require() (T009) will populate it.
1182        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    // === T009 tests ===
1210
1211    #[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        // Before require: capability not in capabilities map
1218        assert!(!built.contains::<CountingModule>());
1219
1220        // First require should trigger lazy construction
1221        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        // Local static counter — each test function has its own COUNT
1228        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        // Both calls should return the same value (builder called once)
1255        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        // A lazy module that calls kit.require() for its dependency in build()
1263        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                // Verify the eager dependency is accessible during lazy build
1277                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        // Register MockCapability (adds to dependency graph) then override
1284        // with value 42 to verify it's accessible during lazy build
1285        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        // First require triggers lazy build, which calls require::<MockCapability>()
1291        let cap = built.require::<LazyDependentModule>().unwrap();
1292        assert_eq!(cap.load(Ordering::SeqCst), 142, "lazy build accessed eager dep (42 + 100)");
1293    }
1294
1295    // === T010 tests ===
1296
1297    /// Multi-binding module A (capability = Arc<AtomicUsize>).
1298    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    /// Multi-binding module B (same capability type as MultiModuleA).
1314    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    /// Multi-binding module C (same capability type as MultiModuleA).
1330    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        // Keyed by TypeId::of::<M::Capability>() = TypeId::of::<Arc<AtomicUsize>>()
1359        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        // The module type_id (not cap_id) should be in the graph
1388        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        // MockCapability in builders, MultiModuleA/B in multi_builders
1423        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    // === T011 tests ===
1432
1433    #[test]
1434    fn require_all_returns_empty_for_unregistered_capability() {
1435        let mut kit = Kit::new();
1436        // Register MockCapability (eager, not multi)
1437        kit.register::<MockCapability>().unwrap();
1438        let built = kit.build().unwrap();
1439
1440        // require_all for a capability with no multi-binding registrations
1441        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(); // builds value 10
1464        kit.register_multi::<MultiModuleB>().unwrap(); // builds value 20
1465        kit.register_multi::<MultiModuleC>().unwrap(); // builds value 30
1466        let built = kit.build().unwrap();
1467
1468        let caps = built.require_all::<MultiModuleA>().unwrap();
1469        assert_eq!(caps.len(), 3);
1470        // Verify order matches registration: 10, 20, 30
1471        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        // Don't call build() — multi_capabilities is empty
1481
1482        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        // Before build: multi_builders has entries, multi_capabilities is empty
1496        assert_eq!(kit.multi_builders.borrow().len(), 1); // one cap_id key
1497        assert_eq!(kit.multi_capabilities.borrow().len(), 0);
1498
1499        let built = kit.build().unwrap();
1500
1501        // After build: multi_builders is drained, multi_capabilities is populated
1502        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        // Single binding: MockCapability (eager)
1515        kit.register::<MockCapability>().unwrap();
1516        // Multi-binding: MultiModuleA, MultiModuleB
1517        kit.register_multi::<MultiModuleA>().unwrap();
1518        kit.register_multi::<MultiModuleB>().unwrap();
1519        let built = kit.build().unwrap();
1520
1521        // require gets the single binding
1522        let single = built.require::<MockCapability>().unwrap();
1523        assert_eq!(single.load(Ordering::SeqCst), 0);
1524
1525        // require_all gets the multi-binding (returns MultiModuleA's cap type)
1526        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    // === Test fixtures ===
1541
1542    /// Test interface trait.
1543    trait Logger: 'static {
1544        fn log(&self, msg: &str) -> String;
1545    }
1546
1547    /// First Logger implementation.
1548    struct ConsoleLogger;
1549
1550    impl Logger for ConsoleLogger {
1551        fn log(&self, msg: &str) -> String {
1552            format!("[console] {msg}")
1553        }
1554    }
1555
1556    /// Second Logger implementation (for duplicate interface test).
1557    struct FileLogger;
1558
1559    impl Logger for FileLogger {
1560        fn log(&self, msg: &str) -> String {
1561            format!("[file] {msg}")
1562        }
1563    }
1564
1565    /// Test error type.
1566    #[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    /// Module providing ConsoleLogger behind dyn Logger.
1578    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    /// Module providing FileLogger behind dyn Logger (same interface).
1602    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    // === Tests ===
1626
1627    #[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        // resolve on unbuilt kit — capabilities is empty
1656        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        // After build, resolve should return the built capability
1673        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        // register (AutoBuilder) + register_as (InterfaceBuilder) for
1692        // different modules should coexist.
1693        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        // Both retrieve correctly
1715        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        // Same module type — graph.add() rejects duplicate
1728        let err = kit.register_as::<ConsoleLoggerModule>().unwrap_err();
1729        assert!(
1730            matches!(err, TraitKitError::AlreadyRegistered { .. }),
1731            "expected AlreadyRegistered, got {err:?}"
1732        );
1733    }
1734}