Skip to main content

bc_envelope/extension/edge/
edge_impl.rs

1use crate::{Envelope, Error, Result, known_values};
2
3/// Methods for working with edge envelopes on documents.
4impl Envelope {
5    /// Returns a new envelope with an added `'edge': <edge>` assertion.
6    pub fn add_edge_envelope(&self, edge: Self) -> Self {
7        self.add_assertion(known_values::EDGE, edge)
8    }
9
10    /// Returns all edge object envelopes (assertions with predicate `'edge'`).
11    pub fn edges(&self) -> Result<Vec<Self>> {
12        Ok(self.objects_for_predicate(known_values::EDGE))
13    }
14
15    /// Validates an edge envelope's structure per BCR-2026-003.
16    ///
17    /// An edge may be wrapped (signed) or unwrapped. The inner envelope
18    /// must have exactly three assertion predicates: `'isA'`, `'source'`,
19    /// and `'target'`.
20    pub fn validate_edge(&self) -> Result<()> {
21        let inner = if self.subject().is_wrapped() {
22            self.subject().try_unwrap()?
23        } else {
24            self.clone()
25        };
26
27        let is_a_count =
28            inner.assertions_with_predicate(known_values::IS_A).len();
29        let source_count =
30            inner.assertions_with_predicate(known_values::SOURCE).len();
31        let target_count =
32            inner.assertions_with_predicate(known_values::TARGET).len();
33
34        if is_a_count == 0 {
35            return Err(Error::EdgeMissingIsA);
36        }
37        if source_count == 0 {
38            return Err(Error::EdgeMissingSource);
39        }
40        if target_count == 0 {
41            return Err(Error::EdgeMissingTarget);
42        }
43        if is_a_count > 1 {
44            return Err(Error::EdgeDuplicateIsA);
45        }
46        if source_count > 1 {
47            return Err(Error::EdgeDuplicateSource);
48        }
49        if target_count > 1 {
50            return Err(Error::EdgeDuplicateTarget);
51        }
52
53        Ok(())
54    }
55
56    /// Extracts the `'isA'` assertion object from an edge envelope.
57    pub fn edge_is_a(&self) -> Result<Self> {
58        let inner = if self.subject().is_wrapped() {
59            self.subject().try_unwrap()?
60        } else {
61            self.clone()
62        };
63        inner.object_for_predicate(known_values::IS_A)
64    }
65
66    /// Extracts the `'source'` assertion object from an edge envelope.
67    pub fn edge_source(&self) -> Result<Self> {
68        let inner = if self.subject().is_wrapped() {
69            self.subject().try_unwrap()?
70        } else {
71            self.clone()
72        };
73        inner.object_for_predicate(known_values::SOURCE)
74    }
75
76    /// Extracts the `'target'` assertion object from an edge envelope.
77    pub fn edge_target(&self) -> Result<Self> {
78        let inner = if self.subject().is_wrapped() {
79            self.subject().try_unwrap()?
80        } else {
81            self.clone()
82        };
83        inner.object_for_predicate(known_values::TARGET)
84    }
85
86    /// Extracts the edge's subject identifier (the inner envelope's subject).
87    pub fn edge_subject(&self) -> Result<Self> {
88        let inner = if self.subject().is_wrapped() {
89            self.subject().try_unwrap()?
90        } else {
91            self.clone()
92        };
93        Ok(inner.subject())
94    }
95
96    /// Filters edges by optional criteria.
97    ///
98    /// Each parameter is optional. When provided, only edges matching
99    /// all specified criteria are returned.
100    pub fn edges_matching(
101        &self,
102        is_a: Option<&Self>,
103        source: Option<&Self>,
104        target: Option<&Self>,
105        subject: Option<&Self>,
106    ) -> Result<Vec<Self>> {
107        let all_edges = self.edges()?;
108        let mut matching = Vec::new();
109
110        for edge in all_edges {
111            if let Some(is_a_filter) = is_a {
112                if let Ok(edge_is_a) = edge.edge_is_a() {
113                    if !edge_is_a.is_equivalent_to(is_a_filter) {
114                        continue;
115                    }
116                } else {
117                    continue;
118                }
119            }
120
121            if let Some(source_filter) = source {
122                if let Ok(edge_source) = edge.edge_source() {
123                    if !edge_source.is_equivalent_to(source_filter) {
124                        continue;
125                    }
126                } else {
127                    continue;
128                }
129            }
130
131            if let Some(target_filter) = target {
132                if let Ok(edge_target) = edge.edge_target() {
133                    if !edge_target.is_equivalent_to(target_filter) {
134                        continue;
135                    }
136                } else {
137                    continue;
138                }
139            }
140
141            if let Some(subject_filter) = subject {
142                if let Ok(edge_subject) = edge.edge_subject() {
143                    if !edge_subject.is_equivalent_to(subject_filter) {
144                        continue;
145                    }
146                } else {
147                    continue;
148                }
149            }
150
151            matching.push(edge);
152        }
153
154        Ok(matching)
155    }
156}