use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::Arc;
use khive_runtime::{BackendId, KhiveRuntime};
use khive_types::SubstrateKind;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BackendRegistrationError {
EmptyServedKinds { backend_id: BackendId },
MissingSearchKinds {
kinds: Vec<SubstrateKind>,
backend_ids: Vec<BackendId>,
},
}
impl fmt::Display for BackendRegistrationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyServedKinds { backend_id } => write!(
formatter,
"backend {backend_id:?}: served kinds must not be empty when declared"
),
Self::MissingSearchKinds { kinds, backend_ids } => write!(
formatter,
"registered backends {backend_ids:?} leave searchable substrate kinds {kinds:?} unserved"
),
}
}
}
impl std::error::Error for BackendRegistrationError {}
#[derive(Clone)]
pub struct BackendEntry {
pub id: BackendId,
pub runtime: Arc<KhiveRuntime>,
pub served_kinds: Option<BTreeSet<SubstrateKind>>,
}
impl BackendEntry {
pub fn serves(&self, kind: SubstrateKind) -> bool {
self.served_kinds
.as_ref()
.is_none_or(|served| served.contains(&kind))
}
}
#[derive(Default)]
pub struct BackendRegistry {
backends: BTreeMap<String, BackendEntry>,
primary: Option<String>,
}
impl BackendRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, id: BackendId, runtime: Arc<KhiveRuntime>) -> bool {
self.register_with_served_kinds(id, runtime, None)
.expect("absent served-kind metadata is always valid")
}
pub fn register_with_served_kinds(
&mut self,
id: BackendId,
runtime: Arc<KhiveRuntime>,
served_kinds: Option<BTreeSet<SubstrateKind>>,
) -> Result<bool, BackendRegistrationError> {
if served_kinds.as_ref().is_some_and(BTreeSet::is_empty) {
return Err(BackendRegistrationError::EmptyServedKinds { backend_id: id });
}
let key = id.as_str().to_string();
if self.backends.contains_key(&key) {
return Ok(false);
}
if self.primary.is_none() {
self.primary = Some(key.clone());
}
self.backends.insert(
key,
BackendEntry {
id,
runtime,
served_kinds,
},
);
Ok(true)
}
pub fn get(&self, id: &BackendId) -> Option<&BackendEntry> {
self.backends.get(id.as_str())
}
pub fn validate_search_coverage(&self) -> Result<(), BackendRegistrationError> {
let kinds: Vec<_> = [SubstrateKind::Note, SubstrateKind::Entity]
.into_iter()
.filter(|kind| !self.iter().any(|entry| entry.serves(*kind)))
.collect();
if kinds.is_empty() {
Ok(())
} else {
Err(BackendRegistrationError::MissingSearchKinds {
kinds,
backend_ids: self.ids(),
})
}
}
pub fn primary(&self) -> Option<&BackendEntry> {
self.primary.as_deref().and_then(|k| self.backends.get(k))
}
pub fn iter(&self) -> impl Iterator<Item = &BackendEntry> {
self.backends.values()
}
pub fn len(&self) -> usize {
self.backends.len()
}
pub fn is_empty(&self) -> bool {
self.backends.is_empty()
}
pub fn ids(&self) -> Vec<BackendId> {
self.backends
.values()
.map(|entry| entry.id.clone())
.collect()
}
}