1use std::{
14 collections::{BTreeMap, BTreeSet},
15 sync::Arc,
16};
17
18use axioval_ir::SourceId;
19use axioval_ir::contract::ExternalName;
20use thiserror::Error;
21
22use crate::session::{SnapshotBoundService, SourceSnapshot};
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
26pub enum ConceptKind {
27 ObjectType,
29 Property,
31 PropertySet,
33}
34
35impl std::fmt::Display for ConceptKind {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.write_str(match self {
38 Self::ObjectType => "object-type",
39 Self::Property => "property",
40 Self::PropertySet => "property-set",
41 })
42 }
43}
44
45#[derive(Clone, Debug, Error, Eq, PartialEq)]
47pub enum BindingError {
48 #[error("unknown {kind} concept `{concept}`")]
50 UnknownConcept {
51 kind: ConceptKind,
53 concept: String,
55 },
56 #[error("source `{0}` declares no type system, so package concepts cannot bind to it")]
58 UndeclaredTypeSystem(SourceId),
59 #[error("{kind} concept `{concept}` has no external name in type systems {type_systems:?}")]
61 Unbound {
62 kind: ConceptKind,
64 concept: String,
66 type_systems: Vec<String>,
68 },
69 #[error("{kind} concept `{concept}` binds ambiguously to {names:?}")]
74 Ambiguous {
75 kind: ConceptKind,
77 concept: String,
79 names: Vec<String>,
81 },
82}
83
84#[derive(Clone, Debug, Default, PartialEq, Eq)]
86pub struct ConceptCatalog {
87 entries: BTreeMap<(ConceptKind, String), Vec<ExternalName>>,
88 classifications: BTreeSet<String>,
90}
91
92impl ConceptCatalog {
93 pub(crate) fn insert(
95 &mut self,
96 kind: ConceptKind,
97 concept: &str,
98 names: &[ExternalName],
99 ) -> Result<(), ()> {
100 let key = (kind, concept.to_owned());
101 if self.entries.contains_key(&key) {
102 return Err(());
103 }
104 self.entries.insert(key, names.to_vec());
105 Ok(())
106 }
107
108 pub(crate) fn declare_classifications<'a>(&mut self, ids: impl IntoIterator<Item = &'a str>) {
111 self.classifications
112 .extend(ids.into_iter().map(ToOwned::to_owned));
113 }
114
115 #[must_use]
118 pub fn derives(&self, set: &str, name: &str) -> Option<bool> {
119 if set == axioval_ir::MEASURED_SET {
120 return Some(crate::measured::parse(name).is_ok());
121 }
122 (set == axioval_ir::CLASSIFICATION_SET).then(|| self.classifications.contains(name))
123 }
124
125 #[must_use]
127 pub fn contains(&self, kind: ConceptKind, concept: &str) -> bool {
128 self.entries.contains_key(&(kind, concept.to_owned()))
129 }
130
131 fn names(&self, kind: ConceptKind, concept: &str) -> Result<&[ExternalName], BindingError> {
132 self.entries
133 .get(&(kind, concept.to_owned()))
134 .map(Vec::as_slice)
135 .ok_or_else(|| BindingError::UnknownConcept {
136 kind,
137 concept: concept.to_owned(),
138 })
139 }
140}
141
142#[derive(Clone, Debug)]
149pub struct ConceptBindings {
150 catalog: Arc<ConceptCatalog>,
151 type_systems: BTreeMap<SourceId, Vec<Arc<str>>>,
152}
153
154impl ConceptBindings {
155 pub(crate) fn new(
156 catalog: Arc<ConceptCatalog>,
157 type_systems: BTreeMap<SourceId, Vec<Arc<str>>>,
158 ) -> Self {
159 Self {
160 catalog,
161 type_systems,
162 }
163 }
164
165 pub fn object_type(&self, concept: &str, source: &SourceId) -> Result<&str, BindingError> {
167 self.bind(ConceptKind::ObjectType, concept, source)
168 }
169
170 pub fn property(&self, concept: &str, source: &SourceId) -> Result<&str, BindingError> {
172 self.bind(ConceptKind::Property, concept, source)
173 }
174
175 pub fn property_set(&self, concept: &str, source: &SourceId) -> Result<&str, BindingError> {
177 self.bind(ConceptKind::PropertySet, concept, source)
178 }
179
180 fn bind(
181 &self,
182 kind: ConceptKind,
183 concept: &str,
184 source: &SourceId,
185 ) -> Result<&str, BindingError> {
186 let names = self.catalog.names(kind, concept)?;
187 let declared = self
188 .type_systems
189 .get(source)
190 .filter(|systems| !systems.is_empty())
191 .ok_or_else(|| BindingError::UndeclaredTypeSystem(source.clone()))?;
192 let mut matched: Vec<&str> = names
195 .iter()
196 .filter(|name| declared.iter().any(|system| **system == *name.type_system))
197 .map(|name| name.name.as_str())
198 .collect();
199 matched.sort_unstable();
200 matched.dedup();
201 match matched.as_slice() {
202 [name] => Ok(name),
203 [] => Err(BindingError::Unbound {
204 kind,
205 concept: concept.to_owned(),
206 type_systems: declared.iter().map(ToString::to_string).collect(),
207 }),
208 _ => Err(BindingError::Ambiguous {
209 kind,
210 concept: concept.to_owned(),
211 names: matched.iter().map(ToString::to_string).collect(),
212 }),
213 }
214 }
215}
216
217#[derive(Clone, Debug, Error, Eq, PartialEq)]
219pub enum TypeHierarchyError {
220 #[error("type hierarchy does not cover source `{0}`")]
222 UncoveredSource(SourceId),
223 #[error("type `{0}` is not declared by the source schema")]
225 UnknownType(String),
226}
227
228pub trait TypeHierarchyService: Send + Sync {
230 fn source_snapshots(&self) -> &[SourceSnapshot];
232 fn is_a(&self, kind: &str, ancestor: &str) -> Result<bool, TypeHierarchyError>;
234}
235
236#[derive(Clone)]
241pub struct TypeHierarchyServiceHandle {
242 members: Arc<[Arc<dyn TypeHierarchyService>]>,
243 snapshots: Arc<[SourceSnapshot]>,
244}
245
246impl TypeHierarchyServiceHandle {
247 #[must_use]
249 pub fn new(service: Arc<dyn TypeHierarchyService>) -> Self {
250 let snapshots = service.source_snapshots().into();
251 Self {
252 members: Arc::from([service]),
253 snapshots,
254 }
255 }
256
257 pub(crate) fn federated(handles: &[&Self]) -> Self {
259 Self {
260 members: handles
261 .iter()
262 .flat_map(|handle| handle.members.iter().cloned())
263 .collect(),
264 snapshots: handles
265 .iter()
266 .flat_map(|handle| handle.snapshots.iter().cloned())
267 .collect(),
268 }
269 }
270
271 pub fn is_a(
273 &self,
274 source: &SourceId,
275 kind: &str,
276 ancestor: &str,
277 ) -> Result<bool, TypeHierarchyError> {
278 self.members
279 .iter()
280 .find(|member| {
281 member
282 .source_snapshots()
283 .iter()
284 .any(|snapshot| snapshot.source() == source)
285 })
286 .ok_or_else(|| TypeHierarchyError::UncoveredSource(source.clone()))?
287 .is_a(kind, ancestor)
288 }
289}
290
291impl SnapshotBoundService for TypeHierarchyServiceHandle {
292 fn source_snapshots(&self) -> &[SourceSnapshot] {
293 &self.snapshots
294 }
295}