1use std::sync::Arc;
23
24use axioval_ir::{Evidence, ObjectId, SourceId};
25use thiserror::Error;
26
27use crate::services::reviewable_exact_evidence;
28use crate::{
29 DoorLeaves, DoorLeavesError, MetricDirection, MetricFrame, SnapshotBoundService, SourceSnapshot,
30};
31
32#[derive(Clone, Debug, Error, PartialEq, Eq)]
34pub enum ObjectFrameError {
35 #[error("object-frame service does not cover source `{0}`")]
37 UncoveredSource(SourceId),
38 #[error("object `{0}` is not in the source")]
40 UnknownObject(ObjectId),
41 #[error("the source states no placement for `{0}`")]
44 NotPlaced(ObjectId),
45 #[error("object placement unsupported: {0}")]
48 Unsupported(String),
49 #[error("object placement cannot be read exactly: {0}")]
52 Unreadable(String),
53 #[error("object frame is invalid")]
56 InvalidFrame,
57 #[error("object-frame evidence is not exact and reviewable")]
60 InexactEvidence,
61 #[error("object-frame service answered for another object")]
63 ResponseRequestMismatch,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq)]
68pub enum ObjectFront {
69 Stated(MetricDirection),
72 NotStated,
75}
76
77#[derive(Clone, Debug, PartialEq)]
79pub struct ObjectFrame {
80 object: ObjectId,
81 frame: MetricFrame,
82 front: ObjectFront,
83 evidence: Evidence,
84}
85
86impl ObjectFrame {
87 pub fn try_new(
93 object: ObjectId,
94 frame: MetricFrame,
95 front: ObjectFront,
96 evidence: Evidence,
97 ) -> Result<Self, ObjectFrameError> {
98 if frame.origin().subject() != &object {
99 return Err(ObjectFrameError::InvalidFrame);
100 }
101 if !reviewable_exact_evidence(&evidence) || evidence.source != object.source {
102 return Err(ObjectFrameError::InexactEvidence);
103 }
104 Ok(Self {
105 object,
106 frame,
107 front,
108 evidence,
109 })
110 }
111
112 #[must_use]
114 pub fn object(&self) -> &ObjectId {
115 &self.object
116 }
117
118 #[must_use]
121 pub fn frame(&self) -> &MetricFrame {
122 &self.frame
123 }
124
125 #[must_use]
127 pub fn front(&self) -> ObjectFront {
128 self.front
129 }
130
131 #[must_use]
133 pub fn evidence(&self) -> &Evidence {
134 &self.evidence
135 }
136}
137
138pub trait ObjectFrameService: Send + Sync + 'static {
140 fn source_snapshots(&self) -> &[SourceSnapshot];
142 fn object_frame(&self, object: &ObjectId) -> Result<ObjectFrame, ObjectFrameError>;
144 fn leaves(&self, door: &ObjectId) -> Result<DoorLeaves, DoorLeavesError> {
151 let _ = door;
152 Err(DoorLeavesError::Unsupported)
153 }
154}
155
156#[derive(Clone)]
158pub struct ObjectFrameServiceHandle(Arc<dyn ObjectFrameService>);
159
160impl ObjectFrameServiceHandle {
161 #[must_use]
163 pub fn new(service: Arc<dyn ObjectFrameService>) -> Self {
164 Self(service)
165 }
166
167 pub fn object_frame(&self, object: &ObjectId) -> Result<ObjectFrame, ObjectFrameError> {
170 if !self
171 .0
172 .source_snapshots()
173 .iter()
174 .any(|snapshot| *snapshot.source() == object.source)
175 {
176 return Err(ObjectFrameError::UncoveredSource(object.source.clone()));
177 }
178 let frame = self.0.object_frame(object)?;
179 if frame.object() != object {
180 return Err(ObjectFrameError::ResponseRequestMismatch);
181 }
182 Ok(frame)
183 }
184
185 pub fn leaves(&self, door: &ObjectId) -> Result<DoorLeaves, DoorLeavesError> {
188 if !self
189 .0
190 .source_snapshots()
191 .iter()
192 .any(|snapshot| *snapshot.source() == door.source)
193 {
194 return Err(DoorLeavesError::UncoveredSource(door.source.clone()));
195 }
196 let leaves = self.0.leaves(door)?;
197 if leaves.door() != door {
198 return Err(DoorLeavesError::ResponseRequestMismatch);
199 }
200 Ok(leaves)
201 }
202}
203
204impl SnapshotBoundService for ObjectFrameServiceHandle {
205 fn source_snapshots(&self) -> &[SourceSnapshot] {
206 self.0.source_snapshots()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::MetricPoint;
214
215 fn source() -> SourceId {
216 SourceId::new("cad", "m").unwrap()
217 }
218
219 fn id(local: &str) -> ObjectId {
220 ObjectId::new(source(), local).unwrap()
221 }
222
223 fn direction(vector: [f64; 3]) -> MetricDirection {
224 MetricDirection::try_new(vector).unwrap()
225 }
226
227 fn frame(subject: &str) -> MetricFrame {
228 MetricFrame::try_new(
229 MetricPoint::try_new(id(subject), [1.0, 2.0, 0.0]).unwrap(),
230 direction([1.0, 0.0, 0.0]),
231 direction([0.0, 1.0, 0.0]),
232 direction([0.0, 0.0, 1.0]),
233 )
234 .unwrap()
235 }
236
237 fn exact() -> Evidence {
238 Evidence::exact(source(), "placement:a")
239 }
240
241 #[test]
242 fn a_frame_is_grounded_on_its_object() {
243 assert!(ObjectFrame::try_new(id("a"), frame("a"), ObjectFront::NotStated, exact()).is_ok());
244 assert_eq!(
245 ObjectFrame::try_new(id("a"), frame("b"), ObjectFront::NotStated, exact()),
246 Err(ObjectFrameError::InvalidFrame)
247 );
248 }
249
250 #[test]
251 fn a_frame_needs_exact_reviewable_evidence_from_its_source() {
252 let mut approximate = exact();
253 approximate.exact = false;
254 let unlocated = Evidence::exact(source(), " ");
255 let foreign = Evidence::exact(SourceId::new("cad", "other").unwrap(), "placement:a");
256 for evidence in [approximate, unlocated, foreign] {
257 assert_eq!(
258 ObjectFrame::try_new(id("a"), frame("a"), ObjectFront::NotStated, evidence),
259 Err(ObjectFrameError::InexactEvidence)
260 );
261 }
262 }
263
264 #[test]
265 fn a_left_handed_frame_cannot_be_formed() {
266 let mirrored = MetricFrame::try_new(
267 MetricPoint::try_new(id("a"), [0.0; 3]).unwrap(),
268 direction([-1.0, 0.0, 0.0]),
269 direction([0.0, 1.0, 0.0]),
270 direction([0.0, 0.0, 1.0]),
271 );
272 assert!(mirrored.is_err());
273 }
274
275 struct Fixed(Vec<SourceSnapshot>, ObjectFrame);
276 impl ObjectFrameService for Fixed {
277 fn source_snapshots(&self) -> &[SourceSnapshot] {
278 &self.0
279 }
280 fn object_frame(&self, _: &ObjectId) -> Result<ObjectFrame, ObjectFrameError> {
281 Ok(self.1.clone())
282 }
283 }
284
285 fn handle() -> ObjectFrameServiceHandle {
286 let snapshot = SourceSnapshot::try_new(source(), "r1", "sha256:1").unwrap();
287 let answer = ObjectFrame::try_new(
288 id("b"),
289 frame("b"),
290 ObjectFront::Stated(direction([0.0, -1.0, 0.0])),
291 exact(),
292 )
293 .unwrap();
294 ObjectFrameServiceHandle::new(Arc::new(Fixed(vec![snapshot], answer)))
295 }
296
297 #[test]
298 fn the_handle_binds_answers_to_the_request() {
299 let handle = handle();
300 let frame = handle.object_frame(&id("b")).unwrap();
301 assert_eq!(
302 frame.front(),
303 ObjectFront::Stated(direction([0.0, -1.0, 0.0]))
304 );
305 assert_eq!(
306 handle.object_frame(&id("a")),
307 Err(ObjectFrameError::ResponseRequestMismatch)
308 );
309 let foreign = ObjectId::new(SourceId::new("cad", "other").unwrap(), "b").unwrap();
310 assert!(matches!(
311 handle.object_frame(&foreign),
312 Err(ObjectFrameError::UncoveredSource(_))
313 ));
314 }
315
316 #[test]
317 fn door_leaves_are_refused_by_default() {
318 let handle = handle();
319 assert_eq!(handle.leaves(&id("b")), Err(DoorLeavesError::Unsupported));
320 let foreign = ObjectId::new(SourceId::new("cad", "other").unwrap(), "b").unwrap();
321 assert!(matches!(
322 handle.leaves(&foreign),
323 Err(DoorLeavesError::UncoveredSource(_))
324 ));
325 }
326}