1use crate::domains::routing::{DepartureTime, TransportMode};
2use crate::error::EveryMapResult;
3use crate::types::Coordinate;
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct MatchingOptions {
10 pub transport_mode: Option<TransportMode>,
12 pub heading: Option<f64>,
14 pub departure_time: Option<DepartureTime>,
16 pub avoid: Vec<crate::domains::routing::AvoidType>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub provider_extra: Option<serde_json::Value>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct MatchedPoint {
26 pub coordinate: Coordinate,
28 pub confidence: Option<f64>,
30 pub road_name: Option<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct TraceResponse {
37 pub matched_points: Vec<MatchedPoint>,
39 pub distance: f64,
41 pub duration: Option<f64>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub raw: Option<serde_json::Value>,
46}
47
48#[async_trait]
49pub trait RouteMatcher: Send + Sync {
50 async fn match_route(
51 &self,
52 points: &[Coordinate],
53 options: &MatchingOptions,
54 ) -> EveryMapResult<TraceResponse>;
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn test_matching_options_default() {
63 let options = MatchingOptions::default();
64 assert!(options.transport_mode.is_none());
65 assert!(options.heading.is_none());
66 assert!(options.departure_time.is_none());
67 assert!(options.avoid.is_empty());
68 assert!(options.provider_extra.is_none());
69 }
70
71 #[test]
72 fn test_matching_options_with_fields() {
73 let options = MatchingOptions {
74 transport_mode: Some(crate::domains::routing::TransportMode::Car),
75 heading: Some(180.0),
76 departure_time: Some(DepartureTime::Iso8601("2024-01-01T08:00:00".to_string())),
77 avoid: vec![crate::domains::routing::AvoidType::Tolls],
78 provider_extra: Some(serde_json::json!({"map_match_radius": 50})),
79 };
80 assert_eq!(options.heading, Some(180.0));
81 assert!(options.avoid.len() == 1);
82 }
83
84 #[test]
85 fn test_matched_point_construction() {
86 let coordinate = Coordinate::new(52.5, 13.4).unwrap();
87 let point = MatchedPoint {
88 coordinate,
89 confidence: Some(0.95),
90 road_name: Some("Main Street".to_string()),
91 };
92 assert_eq!(point.confidence, Some(0.95));
93 assert_eq!(point.road_name, Some("Main Street".to_string()));
94 }
95
96 #[test]
97 fn test_trace_response_construction() {
98 let response = TraceResponse {
99 matched_points: vec![],
100 distance: 5000.0,
101 duration: Some(600.0),
102 raw: None,
103 };
104 assert_eq!(response.distance, 5000.0);
105 assert!(response.matched_points.is_empty());
106 }
107
108 #[test]
111 fn test_trace_response_serde_roundtrip() {
112 let response = TraceResponse {
113 matched_points: vec![MatchedPoint {
114 coordinate: Coordinate::new(52.5, 13.4).unwrap(),
115 confidence: Some(0.95),
116 road_name: Some("Friedrichstr".to_string()),
117 }],
118 distance: 12000.5,
119 duration: Some(900.0),
120 raw: Some(serde_json::json!({"trace_id": "t1"})),
121 };
122 let json = serde_json::to_string(&response).unwrap();
123 let back: TraceResponse = serde_json::from_str(&json).unwrap();
124 assert_eq!(back.matched_points.len(), 1);
125 assert_eq!(back.distance, 12000.5);
126 assert_eq!(back.duration, Some(900.0));
127 assert!(back.raw.is_some());
128 }
129
130 #[test]
131 fn test_trace_response_empty_serde_roundtrip() {
132 let response = TraceResponse {
133 matched_points: vec![],
134 distance: 0.0,
135 duration: None,
136 raw: None,
137 };
138 let json = serde_json::to_string(&response).unwrap();
139 let back: TraceResponse = serde_json::from_str(&json).unwrap();
140 assert!(back.matched_points.is_empty());
141 assert_eq!(back.distance, 0.0);
142 assert!(back.duration.is_none());
143 }
144
145 #[test]
148 fn test_matching_options_serde_roundtrip() {
149 let options = MatchingOptions {
150 transport_mode: Some(crate::domains::routing::TransportMode::Bicycle),
151 heading: Some(270.0),
152 departure_time: Some(DepartureTime::Iso8601("2024-03-15T10:00:00".to_string())),
153 avoid: vec![crate::domains::routing::AvoidType::Highways],
154 provider_extra: Some(serde_json::json!({"map_match_radius": 30})),
155 };
156 let json = serde_json::to_string(&options).unwrap();
157 let back: MatchingOptions = serde_json::from_str(&json).unwrap();
158 assert_eq!(
159 back.transport_mode,
160 Some(crate::domains::routing::TransportMode::Bicycle)
161 );
162 assert_eq!(back.heading, Some(270.0));
163 assert_eq!(back.avoid.len(), 1);
164 assert!(back.provider_extra.is_some());
165 }
166
167 #[test]
170 fn test_matched_point_serde_roundtrip() {
171 let point = MatchedPoint {
172 coordinate: Coordinate::new(48.8566, 2.3522).unwrap(),
173 confidence: Some(0.88),
174 road_name: Some("Champs-Elysees".to_string()),
175 };
176 let json = serde_json::to_string(&point).unwrap();
177 let back: MatchedPoint = serde_json::from_str(&json).unwrap();
178 assert_eq!(back.coordinate, point.coordinate);
179 assert_eq!(back.confidence, point.confidence);
180 assert_eq!(back.road_name, point.road_name);
181 }
182
183 #[test]
186 fn test_matched_point_zero_confidence() {
187 let point = MatchedPoint {
188 coordinate: Coordinate::ORIGIN,
189 confidence: Some(0.0),
190 road_name: None,
191 };
192 assert_eq!(point.confidence, Some(0.0));
193 assert!(point.road_name.is_none());
194 }
195
196 #[test]
197 fn test_trace_response_zero_distance() {
198 let response = TraceResponse {
199 matched_points: vec![],
200 distance: 0.0,
201 duration: Some(0.0),
202 raw: None,
203 };
204 assert_eq!(response.distance, 0.0);
205 assert_eq!(response.duration, Some(0.0));
206 }
207}