1use crate::door_leaves::{SweptDoor, tidy_swept};
3use crate::{LengthInterval, ServiceRegistry, ServiceRegistryError};
4use axioval_ir::{Evidence, ObjectId};
5use std::{
6 collections::{BTreeMap, BTreeSet, VecDeque},
7 sync::Arc,
8};
9use thiserror::Error;
10#[derive(Clone, Debug, Error, PartialEq)]
11pub enum WalkabilityError {
12 #[error("minimum width must be finite and positive")]
13 InvalidMinimumWidth,
14 #[error("walkability region identifier is blank")]
15 InvalidRegionId,
16 #[error("passage joins a region to itself")]
17 SelfPassage,
18 #[error("passage evidence is not exact and reviewable")]
19 InexactPassage,
20 #[error("walkability evidence is incomplete")]
21 IncompleteEvidence,
22 #[error("duplicate walkability region")]
23 DuplicateRegion,
24 #[error("passage names an unknown region")]
25 UnknownRegion,
26 #[error("duplicate walkable passage")]
27 DuplicatePassage,
28 #[error("region maps an object outside the request universe")]
29 UnexpectedMappedObject,
30 #[error("portal passage violates the request portal policy")]
31 ForbiddenPortalPassage,
32 #[error("walkability object is not mapped to a region")]
33 ObjectUnavailable,
34 #[error("backend returned another request")]
35 ResponseRequestMismatch,
36 #[error("vertical connector is declared twice with different kinds")]
37 ConflictingConnector,
38 #[error("passage is both a portal and a vertical connector")]
39 PortalConnectorPassage,
40 #[error("connector passage violates the request connector declaration")]
41 ForbiddenConnectorPassage,
42 #[error("stated clear width is invalid or names no requested entrance")]
45 InvalidStatedClearWidth,
46 #[error("a swept door is given twice with different sectors")]
48 ConflictingSweptDoors,
49 #[error("a walkability tolerance must be finite and non-negative")]
51 InvalidTolerance,
52 #[error("a narrow stretch does not fit its passage or the request")]
55 InvalidStretch,
56 #[error("walkability unavailable: {0}")]
59 Unavailable(String),
60}
61#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
68pub enum VerticalConnectorKind {
69 Lift,
70 Ramp,
71 Stair,
72}
73impl VerticalConnectorKind {
74 pub fn as_str(self) -> &'static str {
75 match self {
76 Self::Lift => "lift",
77 Self::Ramp => "ramp",
78 Self::Stair => "stair",
79 }
80 }
81}
82#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
84pub struct VerticalConnector {
85 object: ObjectId,
86 kind: VerticalConnectorKind,
87}
88impl VerticalConnector {
89 pub fn new(object: ObjectId, kind: VerticalConnectorKind) -> Self {
90 Self { object, kind }
91 }
92 pub fn object(&self) -> &ObjectId {
93 &self.object
94 }
95 pub fn kind(&self) -> VerticalConnectorKind {
96 self.kind
97 }
98}
99#[derive(Clone, Debug, PartialEq)]
100pub struct WalkabilityRequest {
101 surfaces: Vec<ObjectId>,
102 entrances: Vec<ObjectId>,
103 obstacles: Vec<ObjectId>,
104 minimum_width: f64,
105 elevation_band: Option<LengthInterval>,
106 traverse_verified_portals: bool,
107 include_motion_envelopes: bool,
108 connectors: Vec<VerticalConnector>,
109 stated_clear_widths: BTreeMap<ObjectId, f64>,
110 swept: Vec<SweptDoor>,
111 obstruction_depth: f64,
112 surface_gap: f64,
113}
114impl WalkabilityRequest {
115 pub fn try_new(
116 mut surfaces: Vec<ObjectId>,
117 mut entrances: Vec<ObjectId>,
118 mut obstacles: Vec<ObjectId>,
119 minimum_width: f64,
120 elevation_band: Option<LengthInterval>,
121 traverse_verified_portals: bool,
122 include_motion_envelopes: bool,
123 ) -> Result<Self, WalkabilityError> {
124 if !minimum_width.is_finite() || minimum_width <= 0.0 {
125 return Err(WalkabilityError::InvalidMinimumWidth);
126 }
127 surfaces.sort();
128 surfaces.dedup();
129 entrances.sort();
130 entrances.dedup();
131 obstacles.sort();
132 obstacles.dedup();
133 Ok(Self {
134 surfaces,
135 entrances,
136 obstacles,
137 minimum_width,
138 elevation_band,
139 traverse_verified_portals,
140 include_motion_envelopes,
141 connectors: Vec::new(),
142 stated_clear_widths: BTreeMap::new(),
143 swept: Vec::new(),
144 obstruction_depth: 0.0,
145 surface_gap: 0.0,
146 })
147 }
148 pub fn with_obstruction_depth(mut self, metres: f64) -> Result<Self, WalkabilityError> {
158 self.obstruction_depth = tolerance(metres)?;
159 Ok(self)
160 }
161 pub fn obstruction_depth_metres(&self) -> f64 {
163 self.obstruction_depth
164 }
165 pub fn with_surface_gap(mut self, metres: f64) -> Result<Self, WalkabilityError> {
176 self.surface_gap = tolerance(metres)?;
177 Ok(self)
178 }
179 pub fn surface_gap_metres(&self) -> f64 {
181 self.surface_gap
182 }
183 pub fn with_swept_doors(mut self, swept: Vec<SweptDoor>) -> Result<Self, WalkabilityError> {
195 self.swept = tidy_swept(swept).ok_or(WalkabilityError::ConflictingSweptDoors)?;
196 Ok(self)
197 }
198 pub fn swept_doors(&self) -> &[SweptDoor] {
200 &self.swept
201 }
202 pub fn with_stated_clear_widths(
214 mut self,
215 widths: impl IntoIterator<Item = (ObjectId, f64)>,
216 ) -> Result<Self, WalkabilityError> {
217 let mut stated = BTreeMap::new();
218 for (object, metres) in widths {
219 if !metres.is_finite()
220 || metres <= 0.0
221 || self.entrances.binary_search(&object).is_err()
222 || stated.insert(object, metres).is_some()
223 {
224 return Err(WalkabilityError::InvalidStatedClearWidth);
225 }
226 }
227 self.stated_clear_widths = stated;
228 Ok(self)
229 }
230 pub fn stated_clear_width(&self, entrance: &ObjectId) -> Option<f64> {
232 self.stated_clear_widths.get(entrance).copied()
233 }
234 pub fn stated_clear_widths(&self) -> &BTreeMap<ObjectId, f64> {
236 &self.stated_clear_widths
237 }
238 pub fn with_connectors(
245 mut self,
246 mut connectors: Vec<VerticalConnector>,
247 ) -> Result<Self, WalkabilityError> {
248 connectors.sort();
249 connectors.dedup();
250 if connectors
251 .windows(2)
252 .any(|pair| pair[0].object == pair[1].object)
253 {
254 return Err(WalkabilityError::ConflictingConnector);
255 }
256 self.connectors = connectors;
257 Ok(self)
258 }
259 pub fn connectors(&self) -> &[VerticalConnector] {
260 &self.connectors
261 }
262 pub fn surfaces(&self) -> &[ObjectId] {
263 &self.surfaces
264 }
265 pub fn entrances(&self) -> &[ObjectId] {
266 &self.entrances
267 }
268 pub fn obstacles(&self) -> &[ObjectId] {
269 &self.obstacles
270 }
271 pub fn minimum_width_metres(&self) -> f64 {
272 self.minimum_width
273 }
274 pub fn elevation_band(&self) -> Option<LengthInterval> {
275 self.elevation_band
276 }
277 pub fn traverses_verified_portals(&self) -> bool {
278 self.traverse_verified_portals
279 }
280 pub fn includes_motion_envelopes(&self) -> bool {
281 self.include_motion_envelopes
282 }
283}
284fn tolerance(metres: f64) -> Result<f64, WalkabilityError> {
285 if metres.is_finite() && metres >= 0.0 {
286 Ok(metres)
287 } else {
288 Err(WalkabilityError::InvalidTolerance)
289 }
290}
291#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
293pub enum StretchLimit {
294 Narrow,
297 Obstructed,
300 Low,
303}
304impl StretchLimit {
305 pub fn as_str(self) -> &'static str {
306 match self {
307 Self::Narrow => "narrow",
308 Self::Obstructed => "obstructed",
309 Self::Low => "low",
310 }
311 }
312}
313#[derive(Clone, Debug, PartialEq)]
322pub struct WalkableStretch {
323 surface: ObjectId,
324 limit: StretchLimit,
325 at: [f64; 3],
326 obstacles: Vec<ObjectId>,
327 headroom: Option<LengthInterval>,
328}
329impl WalkableStretch {
330 pub fn try_new(
336 surface: ObjectId,
337 limit: StretchLimit,
338 at: [f64; 3],
339 mut obstacles: Vec<ObjectId>,
340 ) -> Result<Self, WalkabilityError> {
341 if at.iter().any(|value| !value.is_finite()) {
342 return Err(WalkabilityError::InvalidStretch);
343 }
344 obstacles.sort();
345 obstacles.dedup();
346 Ok(Self {
347 surface,
348 limit,
349 at,
350 obstacles,
351 headroom: None,
352 })
353 }
354 pub fn with_headroom(mut self, headroom: LengthInterval) -> Result<Self, WalkabilityError> {
361 if self.limit != StretchLimit::Low {
362 return Err(WalkabilityError::InvalidStretch);
363 }
364 self.headroom = Some(headroom);
365 Ok(self)
366 }
367 pub fn surface(&self) -> &ObjectId {
368 &self.surface
369 }
370 pub fn limit(&self) -> StretchLimit {
371 self.limit
372 }
373 pub fn at(&self) -> [f64; 3] {
375 self.at
376 }
377 pub fn obstacles(&self) -> &[ObjectId] {
379 &self.obstacles
380 }
381 pub fn headroom(&self) -> Option<LengthInterval> {
382 self.headroom
383 }
384}
385#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
386pub struct WalkabilityRegionId(String);
387impl WalkabilityRegionId {
388 pub fn new(value: impl Into<String>) -> Result<Self, WalkabilityError> {
389 let value = value.into();
390 if value.trim().is_empty() {
391 Err(WalkabilityError::InvalidRegionId)
392 } else {
393 Ok(Self(value))
394 }
395 }
396 pub fn as_str(&self) -> &str {
397 &self.0
398 }
399}
400#[derive(Clone, Debug, PartialEq)]
401pub struct WalkabilityRegion {
402 id: WalkabilityRegionId,
403 objects: Vec<ObjectId>,
404}
405impl WalkabilityRegion {
406 pub fn new(id: WalkabilityRegionId, mut objects: Vec<ObjectId>) -> Self {
407 objects.sort();
408 objects.dedup();
409 Self { id, objects }
410 }
411 pub fn id(&self) -> &WalkabilityRegionId {
412 &self.id
413 }
414 pub fn objects(&self) -> &[ObjectId] {
415 &self.objects
416 }
417}
418#[derive(Clone, Debug, PartialEq)]
419pub struct VerifiedWalkablePassage {
420 a: WalkabilityRegionId,
421 b: WalkabilityRegionId,
422 portal: Option<ObjectId>,
423 connector: Option<VerticalConnector>,
424 stretch: Option<WalkableStretch>,
425 clear_width: LengthInterval,
426 evidence: Evidence,
427}
428impl VerifiedWalkablePassage {
429 pub fn try_new(
430 mut a: WalkabilityRegionId,
431 mut b: WalkabilityRegionId,
432 portal: Option<ObjectId>,
433 clear_width: LengthInterval,
434 evidence: Evidence,
435 ) -> Result<Self, WalkabilityError> {
436 if a == b {
437 return Err(WalkabilityError::SelfPassage);
438 }
439 if !evidence.exact || evidence.locator.trim().is_empty() {
440 return Err(WalkabilityError::InexactPassage);
441 }
442 if b < a {
443 std::mem::swap(&mut a, &mut b);
444 }
445 Ok(Self {
446 a,
447 b,
448 portal,
449 connector: None,
450 stretch: None,
451 clear_width,
452 evidence,
453 })
454 }
455 pub fn with_connector(
462 mut self,
463 connector: VerticalConnector,
464 ) -> Result<Self, WalkabilityError> {
465 if self.portal.is_some() {
466 return Err(WalkabilityError::PortalConnectorPassage);
467 }
468 if self.stretch.is_some() {
469 return Err(WalkabilityError::InvalidStretch);
470 }
471 self.connector = Some(connector);
472 Ok(self)
473 }
474 pub fn with_stretch(mut self, stretch: WalkableStretch) -> Result<Self, WalkabilityError> {
483 if self.portal.is_some() || self.connector.is_some() {
484 return Err(WalkabilityError::InvalidStretch);
485 }
486 self.stretch = Some(stretch);
487 Ok(self)
488 }
489 pub fn stretch(&self) -> Option<&WalkableStretch> {
491 self.stretch.as_ref()
492 }
493 pub fn connector(&self) -> Option<&VerticalConnector> {
494 self.connector.as_ref()
495 }
496 pub fn endpoints(&self) -> (&WalkabilityRegionId, &WalkabilityRegionId) {
497 (&self.a, &self.b)
498 }
499 pub fn portal(&self) -> Option<&ObjectId> {
500 self.portal.as_ref()
501 }
502 pub fn clear_width(&self) -> LengthInterval {
503 self.clear_width
504 }
505 pub fn evidence(&self) -> &Evidence {
506 &self.evidence
507 }
508}
509#[derive(Clone, Debug, PartialEq)]
510pub struct WalkabilitySnapshot {
511 request: WalkabilityRequest,
512 regions: Vec<WalkabilityRegion>,
513 passages: Vec<VerifiedWalkablePassage>,
514 object_regions: BTreeMap<ObjectId, Vec<WalkabilityRegionId>>,
515 evidence: Evidence,
516}
517impl WalkabilitySnapshot {
518 pub fn try_new(
519 request: WalkabilityRequest,
520 mut regions: Vec<WalkabilityRegion>,
521 mut passages: Vec<VerifiedWalkablePassage>,
522 evidence: Evidence,
523 ) -> Result<Self, WalkabilityError> {
524 if !evidence.exact || evidence.locator.trim().is_empty() {
525 return Err(WalkabilityError::IncompleteEvidence);
526 }
527 regions.sort_by(|a, b| a.id.cmp(&b.id));
528 if regions.windows(2).any(|w| w[0].id == w[1].id) {
529 return Err(WalkabilityError::DuplicateRegion);
530 }
531 let ids: BTreeSet<_> = regions.iter().map(|r| r.id.clone()).collect();
532 if passages
533 .iter()
534 .any(|p| !ids.contains(&p.a) || !ids.contains(&p.b))
535 {
536 return Err(WalkabilityError::UnknownRegion);
537 }
538 let universe: BTreeSet<_> = request
539 .surfaces()
540 .iter()
541 .chain(request.entrances())
542 .chain(request.obstacles())
543 .chain(request.connectors().iter().map(VerticalConnector::object))
544 .cloned()
545 .collect();
546 if regions
547 .iter()
548 .flat_map(|region| region.objects.iter())
549 .any(|object| !universe.contains(object))
550 {
551 return Err(WalkabilityError::UnexpectedMappedObject);
552 }
553 if passages.iter().any(|passage| {
554 passage.portal.as_ref().is_some_and(|portal| {
555 !request.traverse_verified_portals
556 || request.entrances.binary_search(portal).is_err()
557 })
558 }) {
559 return Err(WalkabilityError::ForbiddenPortalPassage);
560 }
561 if passages.iter().any(|passage| {
562 passage
563 .connector
564 .as_ref()
565 .is_some_and(|connector| request.connectors.binary_search(connector).is_err())
566 }) {
567 return Err(WalkabilityError::ForbiddenConnectorPassage);
568 }
569 let swept: BTreeSet<&ObjectId> = request.swept.iter().map(SweptDoor::door).collect();
570 if passages.iter().any(|passage| {
571 passage.stretch.as_ref().is_some_and(|stretch| {
572 passage.clear_width.upper_metres() >= request.minimum_width
573 || request.surfaces.binary_search(&stretch.surface).is_err()
574 || stretch.obstacles.iter().any(|object| {
575 request.obstacles.binary_search(object).is_err() && !swept.contains(object)
576 })
577 })
578 }) {
579 return Err(WalkabilityError::InvalidStretch);
580 }
581 let key = |p: &VerifiedWalkablePassage| {
582 (
583 p.a.clone(),
584 p.b.clone(),
585 p.portal.clone(),
586 p.connector.clone(),
587 )
588 };
589 passages.sort_by_key(key);
590 if passages
591 .windows(2)
592 .any(|window| key(&window[0]) == key(&window[1]))
593 {
594 return Err(WalkabilityError::DuplicatePassage);
595 }
596 let mut object_regions: BTreeMap<ObjectId, Vec<WalkabilityRegionId>> = BTreeMap::new();
597 for region in ®ions {
598 for object in ®ion.objects {
599 object_regions
600 .entry(object.clone())
601 .or_default()
602 .push(region.id.clone());
603 }
604 }
605 for mapped in object_regions.values_mut() {
606 mapped.sort();
607 mapped.dedup();
608 }
609 Ok(Self {
610 request,
611 regions,
612 passages,
613 object_regions,
614 evidence,
615 })
616 }
617 pub fn request(&self) -> &WalkabilityRequest {
618 &self.request
619 }
620 pub fn regions(&self) -> &[WalkabilityRegion] {
621 &self.regions
622 }
623 pub fn passages(&self) -> &[VerifiedWalkablePassage] {
624 &self.passages
625 }
626 pub fn evidence(&self) -> &Evidence {
627 &self.evidence
628 }
629 pub fn route_between(
630 &self,
631 from: &ObjectId,
632 to: &ObjectId,
633 ) -> Result<WalkabilityRouteOutcome, WalkabilityError> {
634 self.route_between_avoiding(from, to, &[])
635 }
636 pub fn route_between_avoiding(
646 &self,
647 from: &ObjectId,
648 to: &ObjectId,
649 forbidden: &[VerticalConnectorKind],
650 ) -> Result<WalkabilityRouteOutcome, WalkabilityError> {
651 self.route_between_admitting(from, to, |passage| {
652 if passage
653 .connector
654 .as_ref()
655 .is_some_and(|connector| forbidden.contains(&connector.kind))
656 {
657 PassageAdmission::Refused
658 } else {
659 PassageAdmission::Admitted
660 }
661 })
662 }
663 pub fn route_between_admitting(
675 &self,
676 from: &ObjectId,
677 to: &ObjectId,
678 admit: impl Fn(&VerifiedWalkablePassage) -> PassageAdmission,
679 ) -> Result<WalkabilityRouteOutcome, WalkabilityError> {
680 let (starts, goals) = self.endpoints(from, to)?;
681 if let Some(path) = self.path(starts, goals, false, &admit) {
682 return Ok(WalkabilityRouteOutcome::Reachable(path));
683 }
684 if self.path(starts, goals, true, &admit).is_some() {
685 Ok(WalkabilityRouteOutcome::Indeterminate)
686 } else {
687 Ok(WalkabilityRouteOutcome::Unreachable)
688 }
689 }
690 pub fn blocking_passages(
707 &self,
708 from: &ObjectId,
709 to: &ObjectId,
710 admit: impl Fn(&VerifiedWalkablePassage) -> PassageAdmission,
711 ) -> Result<Vec<&VerifiedWalkablePassage>, WalkabilityError> {
712 let (starts, goals) = self.endpoints(from, to)?;
713 let reached = self.reach(starts, |edge| self.usable(edge, true, &admit));
714 if goals.iter().any(|goal| reached.contains(goal)) {
715 return Ok(Vec::new());
716 }
717 let graph = self.graph(|_| true);
721 let mut leads_to_goal: BTreeSet<WalkabilityRegionId> = goals.iter().cloned().collect();
722 let mut queue: VecDeque<WalkabilityRegionId> = leads_to_goal.iter().cloned().collect();
723 while let Some(node) = queue.pop_front() {
724 for next in graph.get(&node).into_iter().flatten() {
725 if !reached.contains(next) && leads_to_goal.insert(next.clone()) {
726 queue.push_back(next.clone());
727 }
728 }
729 }
730 Ok(self
731 .passages
732 .iter()
733 .filter(|edge| {
734 let (a, b) = (reached.contains(&edge.a), reached.contains(&edge.b));
735 (a && !b && leads_to_goal.contains(&edge.b))
736 || (b && !a && leads_to_goal.contains(&edge.a))
737 })
738 .collect())
739 }
740 fn endpoints(
741 &self,
742 from: &ObjectId,
743 to: &ObjectId,
744 ) -> Result<(&[WalkabilityRegionId], &[WalkabilityRegionId]), WalkabilityError> {
745 let starts = self
746 .object_regions
747 .get(from)
748 .ok_or(WalkabilityError::ObjectUnavailable)?;
749 let goals = self
750 .object_regions
751 .get(to)
752 .ok_or(WalkabilityError::ObjectUnavailable)?;
753 Ok((starts, goals))
754 }
755 fn usable(
757 &self,
758 edge: &VerifiedWalkablePassage,
759 possible: bool,
760 admit: &impl Fn(&VerifiedWalkablePassage) -> PassageAdmission,
761 ) -> bool {
762 let minimum = self.request.minimum_width;
763 if possible {
764 edge.clear_width.upper_metres() >= minimum && admit(edge) != PassageAdmission::Refused
765 } else {
766 edge.clear_width.lower_metres() >= minimum && admit(edge) == PassageAdmission::Admitted
767 }
768 }
769 fn reach(
771 &self,
772 starts: &[WalkabilityRegionId],
773 keep: impl Fn(&VerifiedWalkablePassage) -> bool,
774 ) -> BTreeSet<WalkabilityRegionId> {
775 let graph = self.graph(keep);
776 let mut seen: BTreeSet<WalkabilityRegionId> = starts.iter().cloned().collect();
777 let mut queue: VecDeque<WalkabilityRegionId> = seen.iter().cloned().collect();
778 while let Some(node) = queue.pop_front() {
779 for next in graph.get(&node).into_iter().flatten() {
780 if seen.insert(next.clone()) {
781 queue.push_back(next.clone());
782 }
783 }
784 }
785 seen
786 }
787 fn graph(
788 &self,
789 keep: impl Fn(&VerifiedWalkablePassage) -> bool,
790 ) -> BTreeMap<WalkabilityRegionId, Vec<WalkabilityRegionId>> {
791 let mut graph: BTreeMap<WalkabilityRegionId, Vec<WalkabilityRegionId>> = BTreeMap::new();
792 for edge in self.passages.iter().filter(|edge| keep(edge)) {
793 graph
794 .entry(edge.a.clone())
795 .or_default()
796 .push(edge.b.clone());
797 graph
798 .entry(edge.b.clone())
799 .or_default()
800 .push(edge.a.clone());
801 }
802 for neighbors in graph.values_mut() {
803 neighbors.sort();
804 neighbors.dedup();
805 }
806 graph
807 }
808 fn path(
809 &self,
810 starts: &[WalkabilityRegionId],
811 goals: &[WalkabilityRegionId],
812 possible: bool,
813 admit: &impl Fn(&VerifiedWalkablePassage) -> PassageAdmission,
814 ) -> Option<Vec<WalkabilityRegionId>> {
815 let graph = self.graph(|edge| self.usable(edge, possible, admit));
816 let goal_set: BTreeSet<_> = goals.iter().cloned().collect();
817 let mut queue = VecDeque::new();
818 let mut parent: BTreeMap<WalkabilityRegionId, Option<WalkabilityRegionId>> =
819 BTreeMap::new();
820 for start in starts {
821 if parent.insert(start.clone(), None).is_none() {
822 queue.push_back(start.clone());
823 }
824 }
825 while let Some(node) = queue.pop_front() {
826 if goal_set.contains(&node) {
827 let mut path = vec![node.clone()];
828 let mut cursor = node;
829 while let Some(Some(prev)) = parent.get(&cursor) {
830 path.push(prev.clone());
831 cursor = prev.clone();
832 }
833 path.reverse();
834 return Some(path);
835 }
836 for next in graph.get(&node).into_iter().flatten() {
837 if !parent.contains_key(next) {
838 parent.insert(next.clone(), Some(node.clone()));
839 queue.push_back(next.clone());
840 }
841 }
842 }
843 None
844 }
845}
846#[derive(Clone, Copy, Debug, Eq, PartialEq)]
849pub enum PassageAdmission {
850 Admitted,
852 Undecided,
854 Refused,
856}
857#[derive(Clone, Debug, PartialEq)]
858pub enum WalkabilityRouteOutcome {
859 Reachable(Vec<WalkabilityRegionId>),
860 Unreachable,
861 Indeterminate,
862}
863pub trait WalkabilityService: Send + Sync {
864 fn snapshot(
865 &self,
866 request: &WalkabilityRequest,
867 ) -> Result<WalkabilitySnapshot, WalkabilityError>;
868}
869#[derive(Clone)]
870pub struct WalkabilityServiceHandle(Arc<dyn WalkabilityService>);
871impl WalkabilityServiceHandle {
872 pub fn new(service: Arc<dyn WalkabilityService>) -> Self {
873 Self(service)
874 }
875 pub fn snapshot(
876 &self,
877 request: &WalkabilityRequest,
878 ) -> Result<WalkabilitySnapshot, WalkabilityError> {
879 let snapshot = self.0.snapshot(request)?;
880 if snapshot.request() != request {
881 return Err(WalkabilityError::ResponseRequestMismatch);
882 }
883 Ok(snapshot)
884 }
885 pub fn register(self, services: &mut ServiceRegistry) -> Result<(), ServiceRegistryError> {
886 services.register(self)
887 }
888}