Skip to main content

bc_envelope/extension/edge/
edge_impl.rs

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