Skip to main content

morphir_core/metadata/
graph.rs

1//! Set-valued default-graph indexing over separately owned assertions.
2
3use super::{
4    Assertion, AssertionKey, AssertionSource, Carrier, DocumentId, Fact, GraphName, MetadataError,
5    ObjectTerm, SourceRecord,
6};
7use crate::node_address::NodeUri;
8use std::collections::{BTreeMap, BTreeSet};
9
10/// An in-memory default graph with distinct facts and authored assertions.
11///
12/// ```
13/// use morphir_core::metadata::{Assertion, AssertionKey, Carrier, DocumentId,
14///     Fact, GraphIndex, GraphName, ObjectTerm};
15/// use morphir_core::node_address::NodeUri;
16/// use serde_json::json;
17///
18/// let subject = NodeUri::parse(
19///     "morphir://ir/pkg/acme/orders?format=4.0.0#/module/api/value/submit-order"
20/// ).unwrap();
21/// let predicate = NodeUri::parse(
22///     "morphir://ir/pkg/acme/metadata?format=4.0.0#/module/naming/value/aliases"
23/// ).unwrap();
24/// let fact = Fact::new(subject.clone(), predicate, ObjectTerm::value(json!("placeOrder")),
25///     GraphName::Default);
26/// let owner = DocumentId::new("orders/spec.json").unwrap();
27/// let key = AssertionKey::new(owner, Carrier::AttributesFacts(subject.clone()), fact).unwrap();
28/// let mut graph = GraphIndex::new();
29/// graph.insert(Assertion::new(key)).unwrap();
30/// assert_eq!(graph.outgoing(&subject).len(), 1);
31/// ```
32#[derive(Debug, Default)]
33pub struct GraphIndex {
34    facts: Vec<Fact>,
35    assertions: Vec<Assertion>,
36    fact_ids: BTreeMap<String, usize>,
37    assertion_ids: BTreeMap<String, usize>,
38    outgoing: BTreeMap<String, BTreeSet<usize>>,
39    incoming: BTreeMap<String, BTreeSet<usize>>,
40    outgoing_predicate: BTreeMap<(String, String), BTreeSet<usize>>,
41    fact_assertions: BTreeMap<String, BTreeSet<usize>>,
42}
43
44/// A failed graph-wide URI binding; the input graph remains unchanged.
45#[derive(Debug, thiserror::Error)]
46pub enum GraphMapError<E> {
47    #[error("node URI binding failed: {0}")]
48    Map(E),
49    #[error(transparent)]
50    Model(#[from] MetadataError),
51}
52
53impl GraphIndex {
54    /// Create an empty default graph.
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Insert one assertion, coalescing equal facts and repeated same-carrier assertions.
60    ///
61    /// A duplicate key with mixed explicit and implicit source knowledge is
62    /// rejected because merging it could hide an unknown contributor. Named
63    /// graphs remain representable in [`Fact`] but are rejected here.
64    pub fn insert(&mut self, assertion: Assertion) -> Result<(), MetadataError> {
65        if !matches!(assertion.key().fact().graph(), GraphName::Default) {
66            return Err(MetadataError::NamedGraphUnsupported);
67        }
68        let assertion_identity = assertion.key().identity();
69        if let Some(&index) = self.assertion_ids.get(&assertion_identity) {
70            self.assertions[index].merge_sources(&assertion)?;
71            return Ok(());
72        }
73
74        let fact = assertion.key().fact();
75        let fact_identity = fact.identity();
76        if !self.fact_ids.contains_key(&fact_identity) {
77            let index = self.facts.len();
78            self.fact_ids.insert(fact_identity.clone(), index);
79            let subject = fact.subject().to_string();
80            let predicate = fact.predicate().to_string();
81            self.outgoing
82                .entry(subject.clone())
83                .or_default()
84                .insert(index);
85            self.outgoing_predicate
86                .entry((subject, predicate))
87                .or_default()
88                .insert(index);
89            if let ObjectTerm::NodeRef(uri) = fact.object() {
90                self.incoming
91                    .entry(uri.to_string())
92                    .or_default()
93                    .insert(index);
94            }
95            self.facts.push(fact.clone());
96        }
97        let assertion_index = self.assertions.len();
98        self.assertion_ids
99            .insert(assertion_identity, assertion_index);
100        self.fact_assertions
101            .entry(fact_identity)
102            .or_default()
103            .insert(assertion_index);
104        self.assertions.push(assertion);
105        Ok(())
106    }
107
108    /// Distinct facts in first-insertion order.
109    pub fn facts(&self) -> &[Fact] {
110        &self.facts
111    }
112
113    /// Distinct document-and-carrier assertions in first-insertion order.
114    pub fn assertions(&self) -> &[Assertion] {
115        &self.assertions
116    }
117
118    /// Rebind every addressed term and carrier in one transaction. Source
119    /// detail travels with its assertion, so a published selector cannot be
120    /// left referring to the authoring identity. Literal data is never scanned
121    /// for URI-looking strings.
122    pub fn try_map_node_uris<E>(
123        &self,
124        mut map: impl FnMut(&NodeUri) -> Result<NodeUri, E>,
125    ) -> Result<Self, GraphMapError<E>> {
126        let mut output = Self::new();
127        for assertion in &self.assertions {
128            let fact = assertion.key().fact();
129            let object = match fact.object() {
130                ObjectTerm::NodeRef(uri) => {
131                    ObjectTerm::NodeRef(map(uri).map_err(GraphMapError::Map)?)
132                }
133                ObjectTerm::Value(value) => match value.datatype() {
134                    Some(datatype) => ObjectTerm::typed_json(
135                        value.value().clone(),
136                        map(datatype).map_err(GraphMapError::Map)?,
137                    ),
138                    None => ObjectTerm::value(value.value().clone()),
139                },
140            };
141            let graph = match fact.graph() {
142                GraphName::Default => GraphName::Default,
143                GraphName::Named(uri) => GraphName::Named(map(uri).map_err(GraphMapError::Map)?),
144            };
145            let mapped_fact = Fact::new(
146                map(fact.subject()).map_err(GraphMapError::Map)?,
147                map(fact.predicate()).map_err(GraphMapError::Map)?,
148                object,
149                graph,
150            );
151            let carrier = match assertion.key().carrier() {
152                Carrier::AttributesFacts(uri) => {
153                    Carrier::AttributesFacts(map(uri).map_err(GraphMapError::Map)?)
154                }
155                Carrier::AnnotationsFacts(uri) => {
156                    Carrier::AnnotationsFacts(map(uri).map_err(GraphMapError::Map)?)
157                }
158                Carrier::DocumentGraph => Carrier::DocumentGraph,
159                Carrier::Sidecar {
160                    target,
161                    entry_point,
162                } => Carrier::Sidecar {
163                    target: map(target).map_err(GraphMapError::Map)?,
164                    entry_point: map(entry_point).map_err(GraphMapError::Map)?,
165                },
166            };
167            let key = AssertionKey::new(assertion.key().owner().clone(), carrier, mapped_fact)
168                .map_err(GraphMapError::Model)?;
169            output
170                .insert(assertion.clone().with_key(key))
171                .map_err(GraphMapError::Model)?;
172        }
173        Ok(output)
174    }
175
176    /// Apply one document's optional detailed source table after all selectors validate.
177    ///
178    /// Selectors use the already-expanded assertion identity. Any invalid row
179    /// rejects the entire table without changing source ownership.
180    pub fn apply_source_records(
181        &mut self,
182        owner: &DocumentId,
183        records: &[SourceRecord],
184    ) -> Result<(), MetadataError> {
185        let mut seen = BTreeSet::new();
186        let mut matches = Vec::with_capacity(records.len());
187        for record in records {
188            if record.selector().owner() != owner {
189                return Err(MetadataError::SourceSelectorOwnerMismatch(Box::new(
190                    record.selector().clone(),
191                )));
192            }
193            if matches!(record.selector().carrier(), Carrier::Sidecar { .. }) {
194                return Err(MetadataError::SourceSelectorCarrierUnsupported(Box::new(
195                    record.selector().clone(),
196                )));
197            }
198            let identity = record.selector().identity();
199            if !seen.insert(identity.clone()) {
200                return Err(MetadataError::DuplicateSourceSelector(Box::new(
201                    record.selector().clone(),
202                )));
203            }
204            let Some(&index) = self.assertion_ids.get(&identity) else {
205                return Err(MetadataError::UnmatchedSourceSelector(Box::new(
206                    record.selector().clone(),
207                )));
208            };
209            matches.push((index, record.sources()));
210        }
211        for (index, sources) in matches {
212            self.assertions[index].set_source_override(sources.to_vec())?;
213        }
214        Ok(())
215    }
216
217    /// Replace one authored fact and its selector in one graph update.
218    ///
219    /// Existing explicit sources move with the assertion. A failed replacement
220    /// leaves both the graph and its source table association untouched.
221    pub fn rewrite_assertion(
222        &mut self,
223        old: &AssertionKey,
224        replacement: Fact,
225    ) -> Result<(), MetadataError> {
226        let Some(&index) = self.assertion_ids.get(&old.identity()) else {
227            return Err(MetadataError::AssertionNotFound);
228        };
229        let key = AssertionKey::new(old.owner().clone(), old.carrier().clone(), replacement)?;
230        if key.identity() != old.identity() && self.assertion_ids.contains_key(&key.identity()) {
231            return Err(MetadataError::AssertionCollision(Box::new(key)));
232        }
233        let mut assertions = self.assertions.clone();
234        assertions[index] = assertions[index].clone().with_key(key);
235        *self = Self::from_assertions(assertions)?;
236        Ok(())
237    }
238
239    /// Remove a known source's contribution to one document's assertions.
240    ///
241    /// An assertion with only the implicit document source has unknown detailed
242    /// ownership, so source-dependent removal fails before changing anything.
243    /// Assertions without remaining sources are removed from the graph.
244    pub fn remove_source_from_owner(
245        &mut self,
246        owner: &DocumentId,
247        source: &AssertionSource,
248    ) -> Result<usize, MetadataError> {
249        if self.assertions.iter().any(|assertion| {
250            assertion.key().owner() == owner && assertion.source_override().is_none()
251        }) {
252            return Err(MetadataError::UnknownSourceOwnership);
253        }
254        let mut removed = 0;
255        let assertions = self
256            .assertions
257            .iter()
258            .filter_map(|assertion| {
259                if assertion.key().owner() != owner || !assertion.sources().contains(source) {
260                    return Some(assertion.clone());
261                }
262                removed += 1;
263                let remaining = assertion
264                    .sources()
265                    .into_iter()
266                    .filter(|candidate| candidate != source)
267                    .collect::<Vec<_>>();
268                if remaining.is_empty() {
269                    None
270                } else {
271                    let mut next = assertion.clone();
272                    next.set_source_override(remaining)
273                        .expect("removing one known source keeps valid owner sources");
274                    Some(next)
275                }
276            })
277            .collect();
278        *self = Self::from_assertions(assertions)?;
279        Ok(removed)
280    }
281
282    fn from_assertions(assertions: Vec<Assertion>) -> Result<Self, MetadataError> {
283        let mut graph = Self::new();
284        for assertion in assertions {
285            graph.insert(assertion)?;
286        }
287        Ok(graph)
288    }
289
290    /// All document-and-carrier assertions for one expanded fact.
291    pub fn assertions_for_fact(&self, fact: &Fact) -> Vec<&Assertion> {
292        self.fact_assertions
293            .get(&fact.identity())
294            .into_iter()
295            .flat_map(|ids| ids.iter())
296            .map(|&index| &self.assertions[index])
297            .collect()
298    }
299
300    /// All distinct default-graph facts whose subject is this node.
301    pub fn outgoing(&self, subject: &NodeUri) -> Vec<&Fact> {
302        self.get(self.outgoing.get(&subject.to_string()))
303    }
304
305    /// Outgoing facts for one expanded predicate declaration.
306    pub fn outgoing_with_predicate(&self, subject: &NodeUri, predicate: &NodeUri) -> Vec<&Fact> {
307        self.get(
308            self.outgoing_predicate
309                .get(&(subject.to_string(), predicate.to_string())),
310        )
311    }
312
313    /// All distinct default-graph facts that link to this node.
314    ///
315    /// Strings inside typed data are never interpreted as incoming links.
316    pub fn incoming(&self, object: &NodeUri) -> Vec<&Fact> {
317        self.get(self.incoming.get(&object.to_string()))
318    }
319
320    fn get(&self, ids: Option<&BTreeSet<usize>>) -> Vec<&Fact> {
321        ids.into_iter()
322            .flat_map(|ids| ids.iter())
323            .map(|&index| &self.facts[index])
324            .collect()
325    }
326}