Skip to main content

ruvector_robotics/bridge/
search.rs

1//! Search result types used by the bridge and perception layers.
2
3use serde::{Deserialize, Serialize};
4
5/// Severity level for obstacle proximity alerts.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum AlertSeverity {
8    /// Immediate collision risk.
9    Critical,
10    /// Object is approaching but not imminent.
11    Warning,
12    /// Informational -- object detected at moderate range.
13    Info,
14}
15
16/// A single nearest-neighbour result.
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Neighbor {
19    /// Unique identifier of the indexed point.
20    pub id: u64,
21    /// Distance from the query point to this neighbour.
22    pub distance: f32,
23    /// 3-D position of the neighbour.
24    pub position: [f32; 3],
25}
26
27/// Result of a spatial search query.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct SearchResult {
30    /// Identifier of the query that produced this result.
31    pub query_id: u64,
32    /// Nearest neighbours, sorted by ascending distance.
33    pub neighbors: Vec<Neighbor>,
34    /// Wall-clock latency of the search in microseconds.
35    pub latency_us: u64,
36}
37
38/// An alert generated when an obstacle is within a safety threshold.
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct ObstacleAlert {
41    /// Identifier of the obstacle that triggered the alert.
42    pub obstacle_id: u64,
43    /// Distance to the obstacle in metres.
44    pub distance: f32,
45    /// Unit direction vector pointing from the robot towards the obstacle.
46    pub direction: [f32; 3],
47    /// Severity of this alert.
48    pub severity: AlertSeverity,
49}
50
51// ---------------------------------------------------------------------------
52// Tests
53// ---------------------------------------------------------------------------
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_neighbor_serde_roundtrip() {
61        let n = Neighbor {
62            id: 42,
63            distance: 1.5,
64            position: [1.0, 2.0, 3.0],
65        };
66        let json = serde_json::to_string(&n).unwrap();
67        let restored: Neighbor = serde_json::from_str(&json).unwrap();
68        assert_eq!(n, restored);
69    }
70
71    #[test]
72    fn test_search_result_serde_roundtrip() {
73        let sr = SearchResult {
74            query_id: 7,
75            neighbors: vec![
76                Neighbor {
77                    id: 1,
78                    distance: 0.5,
79                    position: [0.0, 0.0, 0.0],
80                },
81                Neighbor {
82                    id: 2,
83                    distance: 1.2,
84                    position: [1.0, 1.0, 1.0],
85                },
86            ],
87            latency_us: 150,
88        };
89        let json = serde_json::to_string(&sr).unwrap();
90        let restored: SearchResult = serde_json::from_str(&json).unwrap();
91        assert_eq!(sr, restored);
92    }
93
94    #[test]
95    fn test_obstacle_alert_serde_roundtrip() {
96        let alert = ObstacleAlert {
97            obstacle_id: 99,
98            distance: 0.3,
99            direction: [1.0, 0.0, 0.0],
100            severity: AlertSeverity::Critical,
101        };
102        let json = serde_json::to_string(&alert).unwrap();
103        let restored: ObstacleAlert = serde_json::from_str(&json).unwrap();
104        assert_eq!(alert, restored);
105    }
106
107    #[test]
108    fn test_alert_severity_all_variants() {
109        for severity in [
110            AlertSeverity::Critical,
111            AlertSeverity::Warning,
112            AlertSeverity::Info,
113        ] {
114            let json = serde_json::to_string(&severity).unwrap();
115            let restored: AlertSeverity = serde_json::from_str(&json).unwrap();
116            assert_eq!(severity, restored);
117        }
118    }
119
120    #[test]
121    fn test_search_result_empty_neighbors() {
122        let sr = SearchResult {
123            query_id: 0,
124            neighbors: vec![],
125            latency_us: 0,
126        };
127        let json = serde_json::to_string(&sr).unwrap();
128        let restored: SearchResult = serde_json::from_str(&json).unwrap();
129        assert!(restored.neighbors.is_empty());
130    }
131
132    #[test]
133    fn test_neighbor_debug_format() {
134        let n = Neighbor {
135            id: 1,
136            distance: 0.0,
137            position: [0.0, 0.0, 0.0],
138        };
139        let dbg = format!("{:?}", n);
140        assert!(dbg.contains("Neighbor"));
141    }
142
143    #[test]
144    fn test_obstacle_alert_direction_preserved() {
145        let alert = ObstacleAlert {
146            obstacle_id: 1,
147            distance: 5.0,
148            direction: [0.577, 0.577, 0.577],
149            severity: AlertSeverity::Warning,
150        };
151        let json = serde_json::to_string(&alert).unwrap();
152        let restored: ObstacleAlert = serde_json::from_str(&json).unwrap();
153        for i in 0..3 {
154            assert!((alert.direction[i] - restored.direction[i]).abs() < 1e-6);
155        }
156    }
157
158    #[test]
159    fn test_search_result_json_structure() {
160        let sr = SearchResult {
161            query_id: 10,
162            neighbors: vec![Neighbor {
163                id: 5,
164                distance: 2.5,
165                position: [3.0, 4.0, 5.0],
166            }],
167            latency_us: 42,
168        };
169        let json = serde_json::to_string_pretty(&sr).unwrap();
170        assert!(json.contains("query_id"));
171        assert!(json.contains("neighbors"));
172        assert!(json.contains("latency_us"));
173    }
174}