paramodel_elements/
types.rs1use std::collections::{BTreeMap, BTreeSet};
17
18use crate::{LabelKey, name_type};
19
20name_type! {
21 pub struct TypeId { kind: "TypeId" }
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, bon::Builder)]
28pub struct ElementTypeDescriptor {
29 pub type_id: TypeId,
31
32 #[builder(default)]
34 pub required_labels: BTreeSet<LabelKey>,
35
36 #[builder(default)]
39 pub forbidden_labels: BTreeMap<LabelKey, String>,
40
41 #[builder(default)]
43 pub label_warnings: BTreeMap<LabelKey, String>,
44
45 #[builder(default)]
47 pub provides_infrastructure: bool,
48}
49
50pub trait ElementTypeDescriptorRegistry: Send + Sync + std::fmt::Debug + 'static {
55 fn descriptors(&self) -> Vec<ElementTypeDescriptor>;
57
58 fn type_aliases(&self) -> BTreeMap<TypeId, TypeId> {
60 BTreeMap::new()
61 }
62
63 fn valid_type_ids(&self) -> BTreeSet<TypeId> {
65 self.descriptors()
66 .into_iter()
67 .map(|d| d.type_id)
68 .collect()
69 }
70
71 fn descriptor(&self, type_id: &TypeId) -> Option<ElementTypeDescriptor> {
73 self.descriptors()
74 .into_iter()
75 .find(|d| &d.type_id == type_id)
76 }
77
78 fn has_infrastructure_type(&self) -> bool {
81 self.descriptors()
82 .iter()
83 .any(|d| d.provides_infrastructure)
84 }
85}
86
87#[derive(Debug, Default)]
92pub struct OpenRegistry;
93
94impl OpenRegistry {
95 #[must_use]
97 pub const fn new() -> Self {
98 Self
99 }
100}
101
102impl ElementTypeDescriptorRegistry for OpenRegistry {
103 fn descriptors(&self) -> Vec<ElementTypeDescriptor> {
104 Vec::new()
108 }
109
110 fn valid_type_ids(&self) -> BTreeSet<TypeId> {
111 BTreeSet::new()
112 }
113
114 fn descriptor(&self, type_id: &TypeId) -> Option<ElementTypeDescriptor> {
115 Some(
116 ElementTypeDescriptor::builder()
117 .type_id(type_id.clone())
118 .build(),
119 )
120 }
121
122 fn has_infrastructure_type(&self) -> bool {
123 false
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn type_id_validates() {
133 TypeId::new("service").unwrap();
134 assert!(TypeId::new("").is_err());
135 }
136
137 #[test]
138 fn descriptor_builder_default_fields() {
139 let d = ElementTypeDescriptor::builder()
140 .type_id(TypeId::new("service").unwrap())
141 .build();
142 assert!(d.required_labels.is_empty());
143 assert!(d.forbidden_labels.is_empty());
144 assert!(d.label_warnings.is_empty());
145 assert!(!d.provides_infrastructure);
146 }
147
148 #[test]
149 fn open_registry_accepts_any_type() {
150 let r = OpenRegistry::new();
151 let id = TypeId::new("whatever").unwrap();
152 let d = r.descriptor(&id).unwrap();
153 assert_eq!(d.type_id, id);
154 assert!(!r.has_infrastructure_type());
155 }
156
157 #[derive(Debug)]
158 struct FixedRegistry {
159 descriptors: Vec<ElementTypeDescriptor>,
160 }
161
162 impl ElementTypeDescriptorRegistry for FixedRegistry {
163 fn descriptors(&self) -> Vec<ElementTypeDescriptor> {
164 self.descriptors.clone()
165 }
166 }
167
168 #[test]
169 fn custom_registry_dispatches_by_type_id() {
170 let reg = FixedRegistry {
171 descriptors: vec![
172 ElementTypeDescriptor::builder()
173 .type_id(TypeId::new("service").unwrap())
174 .provides_infrastructure(false)
175 .build(),
176 ElementTypeDescriptor::builder()
177 .type_id(TypeId::new("node").unwrap())
178 .provides_infrastructure(true)
179 .build(),
180 ],
181 };
182 assert!(reg.descriptor(&TypeId::new("service").unwrap()).is_some());
183 assert!(reg.descriptor(&TypeId::new("node").unwrap()).is_some());
184 assert!(reg.descriptor(&TypeId::new("absent").unwrap()).is_none());
185 assert!(reg.has_infrastructure_type());
186 }
187
188 #[test]
189 fn descriptor_serde_roundtrip() {
190 let d = ElementTypeDescriptor::builder()
191 .type_id(TypeId::new("service").unwrap())
192 .required_labels(std::iter::once(LabelKey::new("name").unwrap()).collect())
193 .build();
194 let json = serde_json::to_string(&d).unwrap();
195 let back: ElementTypeDescriptor = serde_json::from_str(&json).unwrap();
196 assert_eq!(d, back);
197 }
198}