use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::collections::HashMap;
#[cfg(feature = "hot-reload")]
use std::rc::Rc;
use crate::core::error::KitError;
use crate::core::meta::{AutoBuilder, BuildFn};
#[cfg(feature = "encryption")]
use super::config::EncryptedBlob;
use super::graph::{DependencyGraph, GraphError, ModuleEntry};
use super::typemap::TypeMap;
#[cfg(feature = "encryption")]
const KEY_DERIVATION_VERSION: &str = "v1";
#[cfg(feature = "encryption")]
fn derive_kit_field_key(
master_key: &[u8],
path: &'static str,
context: &'static str,
) -> Result<[u8; 32], KitError> {
super::config::derive_field_key(master_key, path, KEY_DERIVATION_VERSION).map_err(|e| {
KitError::BuildFailed {
context,
source: Box::new(e),
}
})
}
pub struct Unbuilt;
pub struct Ready;
#[cfg(feature = "hot-reload")]
type SubscriberMap = RefCell<HashMap<TypeId, Vec<Rc<dyn Fn()>>>>;
#[cfg(feature = "encryption")]
type EncryptedConfigMap = RefCell<HashMap<TypeId, EncryptedBlob>>;
pub struct Kit<S = Unbuilt> {
builders: RefCell<HashMap<TypeId, BuildFn>>,
graph: DependencyGraph,
configs: TypeMap,
capabilities: TypeMap,
#[cfg(feature = "hot-reload")]
subscribers: SubscriberMap,
#[cfg(feature = "encryption")]
encrypted_configs: EncryptedConfigMap,
_state: std::marker::PhantomData<S>,
}
impl Kit {
#[must_use]
pub fn new() -> Self {
Kit {
builders: RefCell::new(HashMap::new()),
graph: DependencyGraph::new(),
configs: TypeMap::new(),
capabilities: TypeMap::new(),
#[cfg(feature = "hot-reload")]
subscribers: RefCell::new(HashMap::new()),
#[cfg(feature = "encryption")]
encrypted_configs: RefCell::new(HashMap::new()),
_state: std::marker::PhantomData,
}
}
pub fn register<M: AutoBuilder>(&mut self) -> Result<(), KitError> {
let entry = ModuleEntry {
type_id: TypeId::of::<M>(),
name: M::NAME,
dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
};
self.graph
.add(entry)
.map_err(|name| KitError::AlreadyRegistered { module: name })?;
let build_fn: BuildFn = Box::new(|kit| {
let capability = M::build(kit)
.map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
Ok(Box::new(capability) as Box<dyn Any>)
});
self.builders
.borrow_mut()
.insert(TypeId::of::<M>(), build_fn);
Ok(())
}
pub fn set_config<C: Clone + 'static>(&self, config: C) {
self.configs.insert(config);
}
#[cfg(feature = "confers")]
pub fn load_config<C: super::config::Configurable>(&self) -> Result<(), KitError> {
let config = C::load().map_err(|e| KitError::BuildFailed {
context: "load_config",
source: e,
})?;
self.set_config(config);
Ok(())
}
pub fn build(self) -> Result<Kit<Ready>, KitError> {
let sorted = match self.graph.validate() {
Ok(sorted) => sorted,
Err(GraphError::DependencyMissing { module, missing }) => {
return Err(KitError::DependencyMissing { module, missing });
}
Err(GraphError::CycleDetected { cycle }) => {
return Err(KitError::CycleDetected { cycle });
}
};
{
let kit_ref: &Self = &self;
for type_id in &sorted {
let build_fn = kit_ref.builders.borrow_mut().remove(type_id).ok_or(
KitError::MissingCapability {
key: kit_ref.module_name(*type_id),
},
)?;
let module_name = kit_ref.module_name(*type_id);
let result = (build_fn)(kit_ref);
match result {
Ok(boxed) => {
kit_ref.capabilities.insert_boxed(*type_id, boxed);
}
Err(e) => {
return Err(KitError::BuildFailed {
context: module_name,
source: e,
});
}
}
}
}
Ok(Kit {
builders: self.builders,
graph: self.graph,
configs: self.configs,
capabilities: self.capabilities,
#[cfg(feature = "hot-reload")]
subscribers: self.subscribers,
#[cfg(feature = "encryption")]
encrypted_configs: self.encrypted_configs,
_state: std::marker::PhantomData,
})
}
fn module_name(&self, type_id: TypeId) -> &'static str {
self.graph.name_of(type_id).unwrap_or("<unknown>")
}
}
impl<S> Kit<S> {
pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, KitError> {
let type_id = TypeId::of::<M>();
self.capabilities
.get_cloned_by_type_id::<M::Capability>(type_id)
.ok_or(KitError::MissingCapability { key: M::NAME })
}
pub fn config<C: Clone + 'static>(&self) -> Result<C, KitError> {
self.configs
.get_cloned::<C>()
.ok_or(KitError::MissingConfig {
key: std::any::type_name::<C>(),
})
}
#[cfg(feature = "hot-reload")]
pub fn subscribe<C: 'static>(&self, callback: impl Fn() + 'static) {
let callback: Rc<dyn Fn()> = Rc::new(callback);
self.subscribers
.borrow_mut()
.entry(TypeId::of::<C>())
.or_default()
.push(callback);
}
#[cfg(feature = "hot-reload")]
pub fn reload_config<C: super::config::Configurable>(&self) -> Result<(), KitError> {
let config = C::load().map_err(|e| KitError::BuildFailed {
context: "reload_config",
source: e,
})?;
self.configs.insert(config);
let callbacks: Vec<Rc<dyn Fn()>> = self
.subscribers
.borrow()
.get(&TypeId::of::<C>())
.cloned()
.unwrap_or_default();
for cb in &callbacks {
cb();
}
Ok(())
}
}
impl Kit {
#[cfg(feature = "encryption")]
pub fn set_encrypted<C>(&self, value: &C, master_key: &[u8]) -> Result<(), KitError>
where
C: super::config::ModuleConfig + serde::Serialize,
{
use super::config::XChaCha20Crypto;
let plaintext = serde_json::to_vec(value).map_err(|e| KitError::BuildFailed {
context: "set_encrypted",
source: Box::new(e),
})?;
let field_key = derive_kit_field_key(master_key, C::PATH, "set_encrypted")?;
let (nonce, ciphertext) = XChaCha20Crypto::new()
.encrypt(&plaintext, &field_key)
.map_err(|e| KitError::BuildFailed {
context: "set_encrypted",
source: Box::new(e),
})?;
self.encrypted_configs
.borrow_mut()
.insert(TypeId::of::<C>(), EncryptedBlob { nonce, ciphertext });
Ok(())
}
#[cfg(feature = "encryption")]
pub fn contains_encrypted<C: super::config::ModuleConfig>(&self) -> bool {
self.encrypted_configs
.borrow()
.contains_key(&TypeId::of::<C>())
}
#[cfg(feature = "confers-macros")]
pub fn load_config_or_default<C>(&self) -> Result<(), KitError>
where
C: super::config::Configurable + super::config::ModuleConfig,
{
let config = match C::load() {
Ok(value) => value,
Err(_) => C::default_value(),
};
self.set_config(config);
Ok(())
}
}
impl Kit<Ready> {
pub fn optional<M: AutoBuilder>(&self) -> Option<M::Capability> {
let type_id = TypeId::of::<M>();
self.capabilities
.get_cloned_by_type_id::<M::Capability>(type_id)
}
pub fn contains<M: AutoBuilder>(&self) -> bool {
self.capabilities.contains_by_type_id(TypeId::of::<M>())
}
pub fn contains_config<C: Clone + 'static>(&self) -> bool {
self.configs.contains::<C>()
}
#[cfg(feature = "encryption")]
pub fn get_encrypted<C>(&self, master_key: &[u8]) -> Result<C, KitError>
where
C: super::config::ModuleConfig + serde::de::DeserializeOwned,
{
use super::config::XChaCha20Crypto;
let blob = self
.encrypted_configs
.borrow()
.get(&TypeId::of::<C>())
.cloned()
.ok_or(KitError::MissingConfig {
key: std::any::type_name::<C>(),
})?;
let field_key = derive_kit_field_key(master_key, C::PATH, "get_encrypted")?;
let plaintext = XChaCha20Crypto::new()
.decrypt(&blob.nonce, &blob.ciphertext, &field_key)
.map_err(|e| KitError::BuildFailed {
context: "get_encrypted",
source: Box::new(e),
})?;
serde_json::from_slice(&plaintext).map_err(|e| KitError::BuildFailed {
context: "get_encrypted",
source: Box::new(e),
})
}
}
impl Default for Kit {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Kit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Kit<Unbuilt>")
.field("modules", &self.graph.entries().len())
.field("configs", &self.configs.len())
.finish()
}
}
impl std::fmt::Debug for Kit<Ready> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Kit<Ready>")
.field("modules", &self.graph.entries().len())
.field("configs", &self.configs.len())
.finish()
}
}