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