1use std::any::{Any, TypeId};
8use std::cell::RefCell;
9use std::collections::HashMap;
10#[cfg(feature = "hot-reload")]
11use std::rc::Rc;
12
13use crate::core::{AutoBuilder, BuildFn};
14use crate::error::TraitKitError;
15
16#[cfg(feature = "encryption")]
17use super::EncryptedBlob;
18use super::TypeMap;
19use super::{DependencyGraph, GraphError, ModuleEntry};
20
21#[cfg(feature = "encryption")]
24const KEY_DERIVATION_VERSION: &str = "v1";
25
26#[cfg(feature = "encryption")]
28fn derive_kit_field_key(
29 master_key: &[u8],
30 path: &'static str,
31 context: &'static str,
32) -> Result<[u8; 32], TraitKitError> {
33 super::config::derive_field_key(master_key, path, KEY_DERIVATION_VERSION).map_err(|e| {
34 TraitKitError::BuildFailed {
35 context,
36 source: Box::new(e),
37 }
38 })
39}
40
41pub struct Unbuilt;
43
44pub struct Ready;
46
47#[cfg(feature = "hot-reload")]
49type SubscriberMap = RefCell<HashMap<TypeId, Vec<Rc<dyn Fn()>>>>;
50
51#[cfg(feature = "encryption")]
53type EncryptedConfigMap = RefCell<HashMap<TypeId, EncryptedBlob>>;
54
55pub struct Kit<S = Unbuilt> {
57 builders: RefCell<HashMap<TypeId, BuildFn>>,
58 graph: DependencyGraph,
59 configs: TypeMap,
60 capabilities: TypeMap,
61 #[cfg(feature = "hot-reload")]
62 subscribers: SubscriberMap,
63 #[cfg(feature = "encryption")]
64 encrypted_configs: EncryptedConfigMap,
65 _state: std::marker::PhantomData<S>,
66}
67
68impl Kit {
69 #[must_use]
71 pub fn new() -> Self {
72 Kit {
73 builders: RefCell::new(HashMap::new()),
74 graph: DependencyGraph::new(),
75 configs: TypeMap::new(),
76 capabilities: TypeMap::new(),
77 #[cfg(feature = "hot-reload")]
78 subscribers: RefCell::new(HashMap::new()),
79 #[cfg(feature = "encryption")]
80 encrypted_configs: RefCell::new(HashMap::new()),
81 _state: std::marker::PhantomData,
82 }
83 }
84
85 pub fn register<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError> {
91 let entry = ModuleEntry {
92 type_id: TypeId::of::<M>(),
93 name: M::NAME,
94 dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
95 };
96
97 self.graph
98 .add(entry)
99 .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
100
101 let build_fn: BuildFn = Box::new(|kit| {
102 let capability = M::build(kit)
103 .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
104 Ok(Box::new(capability) as Box<dyn Any>)
105 });
106
107 self.builders
108 .borrow_mut()
109 .insert(TypeId::of::<M>(), build_fn);
110 Ok(())
111 }
112
113 pub fn set_config<C: Clone + 'static>(&self, config: C) {
115 self.configs.insert(config);
116 }
117
118 #[cfg(feature = "confers")]
128 pub fn load_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
129 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
130 context: "load_config",
131 source: e,
132 })?;
133 self.set_config(config);
134 Ok(())
135 }
136
137 pub fn build(self) -> Result<Kit<Ready>, TraitKitError> {
148 let sorted = match self.graph.validate() {
149 Ok(sorted) => sorted,
150 Err(GraphError::DependencyMissing { module, missing }) => {
151 return Err(TraitKitError::DependencyMissing { module, missing });
152 }
153 Err(GraphError::CycleDetected { cycle }) => {
154 return Err(TraitKitError::CycleDetected { cycle });
155 }
156 };
157
158 {
159 let kit_ref: &Self = &self;
160
161 for type_id in &sorted {
162 let build_fn = kit_ref.builders.borrow_mut().remove(type_id).ok_or(
163 TraitKitError::MissingCapability {
164 key: kit_ref.module_name(*type_id),
165 },
166 )?;
167
168 let module_name = kit_ref.module_name(*type_id);
169
170 let result = (build_fn)(kit_ref);
171 match result {
172 Ok(boxed) => {
173 kit_ref.capabilities.insert_boxed(*type_id, boxed);
174 }
175 Err(e) => {
176 return Err(TraitKitError::BuildFailed {
177 context: module_name,
178 source: e,
179 });
180 }
181 }
182 }
183 }
184
185 Ok(Kit {
186 builders: self.builders,
187 graph: self.graph,
188 configs: self.configs,
189 capabilities: self.capabilities,
190 #[cfg(feature = "hot-reload")]
191 subscribers: self.subscribers,
192 #[cfg(feature = "encryption")]
193 encrypted_configs: self.encrypted_configs,
194 _state: std::marker::PhantomData,
195 })
196 }
197
198 fn module_name(&self, type_id: TypeId) -> &'static str {
199 self.graph.name_of(type_id).unwrap_or("<unknown>")
200 }
201}
202
203impl<S> Kit<S> {
204 pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, TraitKitError> {
213 let type_id = TypeId::of::<M>();
214 self.capabilities
215 .get_cloned_by_type_id::<M::Capability>(type_id)
216 .ok_or(TraitKitError::MissingCapability { key: M::NAME })
217 }
218
219 pub fn config<C: Clone + 'static>(&self) -> Result<C, TraitKitError> {
225 self.configs
226 .get_cloned::<C>()
227 .ok_or(TraitKitError::MissingConfig {
228 key: std::any::type_name::<C>(),
229 })
230 }
231
232 #[cfg(feature = "hot-reload")]
241 pub fn subscribe<C: 'static>(&self, callback: impl Fn() + 'static) {
242 let callback: Rc<dyn Fn()> = Rc::new(callback);
243 self.subscribers
244 .borrow_mut()
245 .entry(TypeId::of::<C>())
246 .or_default()
247 .push(callback);
248 }
249
250 #[cfg(feature = "hot-reload")]
269 pub fn reload_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
270 let config = C::load().map_err(|e| TraitKitError::BuildFailed {
271 context: "reload_config",
272 source: e,
273 })?;
274 self.configs.insert(config);
275 let callbacks: Vec<Rc<dyn Fn()>> = self
278 .subscribers
279 .borrow()
280 .get(&TypeId::of::<C>())
281 .cloned()
282 .unwrap_or_default();
283 for cb in &callbacks {
284 cb();
285 }
286 Ok(())
287 }
288}
289
290impl Kit {
291 #[cfg(feature = "encryption")]
307 pub fn set_encrypted<C>(&self, value: &C, master_key: &[u8]) -> Result<(), TraitKitError>
308 where
309 C: super::ModuleConfig + serde::Serialize,
310 {
311 use super::XChaCha20Crypto;
312
313 let plaintext = serde_json::to_vec(value).map_err(|e| TraitKitError::BuildFailed {
314 context: "set_encrypted",
315 source: Box::new(e),
316 })?;
317
318 let field_key = derive_kit_field_key(master_key, C::PATH, "set_encrypted")?;
319
320 let (nonce, ciphertext) = XChaCha20Crypto::new()
321 .encrypt(&plaintext, &field_key)
322 .map_err(|e| TraitKitError::BuildFailed {
323 context: "set_encrypted",
324 source: Box::new(e),
325 })?;
326
327 self.encrypted_configs
328 .borrow_mut()
329 .insert(TypeId::of::<C>(), EncryptedBlob { nonce, ciphertext });
330 Ok(())
331 }
332
333 #[cfg(feature = "encryption")]
335 pub fn contains_encrypted<C: super::ModuleConfig>(&self) -> bool {
336 self.encrypted_configs
337 .borrow()
338 .contains_key(&TypeId::of::<C>())
339 }
340
341 #[cfg(feature = "confers-macros")]
353 pub fn load_config_or_default<C>(&self) -> Result<(), TraitKitError>
354 where
355 C: super::Configurable + super::ModuleConfig,
356 {
357 let config = match C::load() {
358 Ok(value) => value,
359 Err(_) => C::default_value(),
360 };
361 self.set_config(config);
362 Ok(())
363 }
364}
365
366impl Kit<Ready> {
367 pub fn optional<M: AutoBuilder>(&self) -> Option<M::Capability> {
369 let type_id = TypeId::of::<M>();
370 self.capabilities
371 .get_cloned_by_type_id::<M::Capability>(type_id)
372 }
373
374 pub fn contains<M: AutoBuilder>(&self) -> bool {
376 self.capabilities.contains_by_type_id(TypeId::of::<M>())
377 }
378
379 pub fn contains_config<C: Clone + 'static>(&self) -> bool {
381 self.configs.contains::<C>()
382 }
383
384 #[cfg(feature = "encryption")]
397 pub fn get_encrypted<C>(&self, master_key: &[u8]) -> Result<C, TraitKitError>
398 where
399 C: super::ModuleConfig + serde::de::DeserializeOwned,
400 {
401 use super::XChaCha20Crypto;
402
403 let blob = self
404 .encrypted_configs
405 .borrow()
406 .get(&TypeId::of::<C>())
407 .cloned()
408 .ok_or(TraitKitError::MissingConfig {
409 key: std::any::type_name::<C>(),
410 })?;
411
412 let field_key = derive_kit_field_key(master_key, C::PATH, "get_encrypted")?;
413
414 let plaintext = XChaCha20Crypto::new()
415 .decrypt(&blob.nonce, &blob.ciphertext, &field_key)
416 .map_err(|e| TraitKitError::BuildFailed {
417 context: "get_encrypted",
418 source: Box::new(e),
419 })?;
420
421 serde_json::from_slice(&plaintext).map_err(|e| TraitKitError::BuildFailed {
422 context: "get_encrypted",
423 source: Box::new(e),
424 })
425 }
426}
427
428impl Default for Kit {
429 fn default() -> Self {
430 Self::new()
431 }
432}
433
434impl std::fmt::Debug for Kit {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 f.debug_struct("Kit<Unbuilt>")
437 .field("modules", &self.graph.entries().len())
438 .field("configs", &self.configs.len())
439 .finish()
440 }
441}
442
443impl std::fmt::Debug for Kit<Ready> {
444 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445 f.debug_struct("Kit<Ready>")
446 .field("modules", &self.graph.entries().len())
447 .field("configs", &self.configs.len())
448 .finish()
449 }
450}