use std::collections::{BTreeMap, BTreeSet};
use crate::{LabelKey, name_type};
name_type! {
pub struct TypeId { kind: "TypeId" }
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, bon::Builder)]
pub struct ElementTypeDescriptor {
pub type_id: TypeId,
#[builder(default)]
pub required_labels: BTreeSet<LabelKey>,
#[builder(default)]
pub forbidden_labels: BTreeMap<LabelKey, String>,
#[builder(default)]
pub label_warnings: BTreeMap<LabelKey, String>,
#[builder(default)]
pub provides_infrastructure: bool,
}
pub trait ElementTypeDescriptorRegistry: Send + Sync + std::fmt::Debug + 'static {
fn descriptors(&self) -> Vec<ElementTypeDescriptor>;
fn type_aliases(&self) -> BTreeMap<TypeId, TypeId> {
BTreeMap::new()
}
fn valid_type_ids(&self) -> BTreeSet<TypeId> {
self.descriptors()
.into_iter()
.map(|d| d.type_id)
.collect()
}
fn descriptor(&self, type_id: &TypeId) -> Option<ElementTypeDescriptor> {
self.descriptors()
.into_iter()
.find(|d| &d.type_id == type_id)
}
fn has_infrastructure_type(&self) -> bool {
self.descriptors()
.iter()
.any(|d| d.provides_infrastructure)
}
}
#[derive(Debug, Default)]
pub struct OpenRegistry;
impl OpenRegistry {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl ElementTypeDescriptorRegistry for OpenRegistry {
fn descriptors(&self) -> Vec<ElementTypeDescriptor> {
Vec::new()
}
fn valid_type_ids(&self) -> BTreeSet<TypeId> {
BTreeSet::new()
}
fn descriptor(&self, type_id: &TypeId) -> Option<ElementTypeDescriptor> {
Some(
ElementTypeDescriptor::builder()
.type_id(type_id.clone())
.build(),
)
}
fn has_infrastructure_type(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_id_validates() {
TypeId::new("service").unwrap();
assert!(TypeId::new("").is_err());
}
#[test]
fn descriptor_builder_default_fields() {
let d = ElementTypeDescriptor::builder()
.type_id(TypeId::new("service").unwrap())
.build();
assert!(d.required_labels.is_empty());
assert!(d.forbidden_labels.is_empty());
assert!(d.label_warnings.is_empty());
assert!(!d.provides_infrastructure);
}
#[test]
fn open_registry_accepts_any_type() {
let r = OpenRegistry::new();
let id = TypeId::new("whatever").unwrap();
let d = r.descriptor(&id).unwrap();
assert_eq!(d.type_id, id);
assert!(!r.has_infrastructure_type());
}
#[derive(Debug)]
struct FixedRegistry {
descriptors: Vec<ElementTypeDescriptor>,
}
impl ElementTypeDescriptorRegistry for FixedRegistry {
fn descriptors(&self) -> Vec<ElementTypeDescriptor> {
self.descriptors.clone()
}
}
#[test]
fn custom_registry_dispatches_by_type_id() {
let reg = FixedRegistry {
descriptors: vec![
ElementTypeDescriptor::builder()
.type_id(TypeId::new("service").unwrap())
.provides_infrastructure(false)
.build(),
ElementTypeDescriptor::builder()
.type_id(TypeId::new("node").unwrap())
.provides_infrastructure(true)
.build(),
],
};
assert!(reg.descriptor(&TypeId::new("service").unwrap()).is_some());
assert!(reg.descriptor(&TypeId::new("node").unwrap()).is_some());
assert!(reg.descriptor(&TypeId::new("absent").unwrap()).is_none());
assert!(reg.has_infrastructure_type());
}
#[test]
fn descriptor_serde_roundtrip() {
let d = ElementTypeDescriptor::builder()
.type_id(TypeId::new("service").unwrap())
.required_labels(std::iter::once(LabelKey::new("name").unwrap()).collect())
.build();
let json = serde_json::to_string(&d).unwrap();
let back: ElementTypeDescriptor = serde_json::from_str(&json).unwrap();
assert_eq!(d, back);
}
}