Skip to main content

everymap_core/domains/
traffic.rs

1use crate::error::EveryMapResult;
2use crate::types::Coordinate;
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5
6/// Options for traffic data retrieval.
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8pub struct TrafficOptions {
9    /// Search radius in meters from the location
10    pub radius: Option<f64>,
11    /// Preferred response language (BCP 47 language tag)
12    pub language: Option<String>,
13    /// Whether to include traffic incidents in the response
14    pub include_incidents: Option<bool>,
15    /// Provider-specific options (HERE: min_jam_factor, functional_classes; TomTom: thickness)
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub provider_extra: Option<serde_json::Value>,
18}
19
20/// Severity of a traffic incident.
21#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
22pub enum IncidentSeverity {
23    Low,
24    Minor,
25    Moderate,
26    Major,
27    Critical,
28    Unknown,
29}
30
31/// A traffic incident (accident, construction, road closure, etc.).
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct TrafficIncident {
34    /// Unique identifier for this incident
35    pub id: Option<String>,
36    /// Type of incident (e.g., "accident", "construction", "roadClosure")
37    pub incident_type: Option<String>,
38    /// Severity level
39    pub severity: Option<IncidentSeverity>,
40    /// Human-readable description
41    pub description: Option<String>,
42    /// Road name or location description
43    pub road_name: Option<String>,
44    /// Start time (ISO 8601 string)
45    pub start_time: Option<String>,
46    /// End time (ISO 8601 string)
47    pub end_time: Option<String>,
48}
49
50/// A traffic flow measurement for a road segment.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct TrafficFlow {
53    /// Current speed in km/h or mph (depends on provider region)
54    pub speed: Option<f64>,
55    /// Free-flow speed (speed without traffic)
56    pub free_flow_speed: Option<f64>,
57    /// Jam factor (0.0 = no traffic, 10.0 = gridlock)
58    pub jam_factor: Option<f64>,
59    /// Confidence in the measurement (0.0-1.0)
60    pub confidence: Option<f64>,
61    /// Road name (if available)
62    pub road_name: Option<String>,
63}
64
65/// A unified traffic response from the core trait.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct TrafficResponse {
68    /// Traffic flow data for the requested area
69    pub flows: Vec<TrafficFlow>,
70    /// Active traffic incidents in the area
71    pub incidents: Vec<TrafficIncident>,
72    /// Provider-specific raw data for advanced use cases
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub raw: Option<serde_json::Value>,
75}
76
77#[async_trait]
78pub trait TrafficProvider: Send + Sync {
79    async fn get_traffic(
80        &self,
81        location: &Coordinate,
82        options: &TrafficOptions,
83    ) -> EveryMapResult<TrafficResponse>;
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_traffic_options_default() {
92        let options = TrafficOptions::default();
93        assert!(options.radius.is_none());
94        assert!(options.language.is_none());
95        assert!(options.include_incidents.is_none());
96        assert!(options.provider_extra.is_none());
97    }
98
99    #[test]
100    fn test_incident_severity_serialization() {
101        assert_eq!(
102            serde_json::to_string(&IncidentSeverity::Critical).unwrap(),
103            "\"Critical\""
104        );
105        let sev: IncidentSeverity = serde_json::from_str("\"Minor\"").unwrap();
106        assert_eq!(sev, IncidentSeverity::Minor);
107        assert_eq!(
108            serde_json::to_string(&IncidentSeverity::Moderate).unwrap(),
109            "\"Moderate\""
110        );
111        let moderate: IncidentSeverity = serde_json::from_str("\"Moderate\"").unwrap();
112        assert_eq!(moderate, IncidentSeverity::Moderate);
113    }
114
115    #[test]
116    fn test_traffic_flow_construction() {
117        let flow = TrafficFlow {
118            speed: Some(80.0),
119            free_flow_speed: Some(100.0),
120            jam_factor: Some(3.5),
121            confidence: Some(0.9),
122            road_name: Some("A100".to_string()),
123        };
124        assert_eq!(flow.speed, Some(80.0));
125        assert_eq!(flow.jam_factor, Some(3.5));
126        assert_eq!(flow.road_name.as_deref(), Some("A100"));
127    }
128
129    #[test]
130    fn test_traffic_response_construction() {
131        let response = TrafficResponse {
132            flows: vec![TrafficFlow {
133                speed: Some(60.0),
134                free_flow_speed: Some(100.0),
135                jam_factor: Some(5.0),
136                confidence: Some(0.85),
137                road_name: None,
138            }],
139            incidents: vec![],
140            raw: None,
141        };
142        assert_eq!(response.flows.len(), 1);
143        assert!(response.incidents.is_empty());
144    }
145
146    // --- IncidentSeverity all 5 variants serde roundtrip ---
147
148    #[test]
149    fn test_incident_severity_all_variants_serde() {
150        let variants = [
151            IncidentSeverity::Low,
152            IncidentSeverity::Minor,
153            IncidentSeverity::Major,
154            IncidentSeverity::Critical,
155            IncidentSeverity::Unknown,
156        ];
157        for v in &variants {
158            let json = serde_json::to_string(v).unwrap();
159            let back: IncidentSeverity = serde_json::from_str(&json).unwrap();
160            assert_eq!(*v, back, "Failed roundtrip for {:?}", v);
161        }
162    }
163
164    #[test]
165    fn test_incident_severity_all_variants_distinct() {
166        let variants = [
167            IncidentSeverity::Low,
168            IncidentSeverity::Minor,
169            IncidentSeverity::Major,
170            IncidentSeverity::Critical,
171            IncidentSeverity::Unknown,
172        ];
173        for i in 0..variants.len() {
174            for j in 0..variants.len() {
175                if i != j {
176                    assert_ne!(variants[i], variants[j]);
177                }
178            }
179        }
180    }
181
182    // --- TrafficResponse serde roundtrip ---
183
184    #[test]
185    fn test_traffic_response_serde_roundtrip() {
186        let response = TrafficResponse {
187            flows: vec![TrafficFlow {
188                speed: Some(80.0),
189                free_flow_speed: Some(120.0),
190                jam_factor: Some(2.5),
191                confidence: Some(0.9),
192                road_name: Some("A9".to_string()),
193            }],
194            incidents: vec![TrafficIncident {
195                id: Some("inc-1".to_string()),
196                incident_type: Some("accident".to_string()),
197                severity: Some(IncidentSeverity::Major),
198                description: Some("Multi-vehicle accident".to_string()),
199                road_name: Some("A9".to_string()),
200                start_time: Some("2024-01-01T08:00:00".to_string()),
201                end_time: Some("2024-01-01T12:00:00".to_string()),
202            }],
203            raw: Some(serde_json::json!({"source": "here"})),
204        };
205        let json = serde_json::to_string(&response).unwrap();
206        let back: TrafficResponse = serde_json::from_str(&json).unwrap();
207        assert_eq!(back.flows.len(), 1);
208        assert_eq!(back.incidents.len(), 1);
209        assert_eq!(back.incidents[0].severity, Some(IncidentSeverity::Major));
210        assert!(back.raw.is_some());
211    }
212
213    #[test]
214    fn test_traffic_response_empty() {
215        let response = TrafficResponse {
216            flows: vec![],
217            incidents: vec![],
218            raw: None,
219        };
220        let json = serde_json::to_string(&response).unwrap();
221        let back: TrafficResponse = serde_json::from_str(&json).unwrap();
222        assert!(back.flows.is_empty());
223        assert!(back.incidents.is_empty());
224    }
225
226    // --- TrafficOptions serde roundtrip ---
227
228    #[test]
229    fn test_traffic_options_serde_roundtrip() {
230        let options = TrafficOptions {
231            radius: Some(5000.0),
232            language: Some("de".to_string()),
233            include_incidents: Some(true),
234            provider_extra: Some(serde_json::json!({"min_jam_factor": 4.0})),
235        };
236        let json = serde_json::to_string(&options).unwrap();
237        let back: TrafficOptions = serde_json::from_str(&json).unwrap();
238        assert_eq!(back.radius, Some(5000.0));
239        assert_eq!(back.include_incidents, Some(true));
240        assert!(back.provider_extra.is_some());
241    }
242
243    // --- TrafficIncident serde roundtrip ---
244
245    #[test]
246    fn test_traffic_incident_serde_roundtrip() {
247        let incident = TrafficIncident {
248            id: Some("inc-42".to_string()),
249            incident_type: Some("construction".to_string()),
250            severity: Some(IncidentSeverity::Minor),
251            description: Some("Road work, lane closed".to_string()),
252            road_name: Some("B96".to_string()),
253            start_time: Some("2024-03-01T06:00:00".to_string()),
254            end_time: Some("2024-09-01T18:00:00".to_string()),
255        };
256        let json = serde_json::to_string(&incident).unwrap();
257        let back: TrafficIncident = serde_json::from_str(&json).unwrap();
258        assert_eq!(back.id, incident.id);
259        assert_eq!(back.severity, incident.severity);
260        assert_eq!(back.description, incident.description);
261    }
262
263    // --- TrafficFlow serde roundtrip ---
264
265    #[test]
266    fn test_traffic_flow_serde_roundtrip() {
267        let flow = TrafficFlow {
268            speed: Some(0.0),
269            free_flow_speed: Some(100.0),
270            jam_factor: Some(10.0),
271            confidence: Some(0.5),
272            road_name: Some("A100".to_string()),
273        };
274        let json = serde_json::to_string(&flow).unwrap();
275        let back: TrafficFlow = serde_json::from_str(&json).unwrap();
276        assert_eq!(back.speed, Some(0.0));
277        assert_eq!(back.jam_factor, Some(10.0));
278    }
279
280    // --- Edge cases ---
281
282    #[test]
283    fn test_traffic_flow_gridlock() {
284        let flow = TrafficFlow {
285            speed: Some(0.0),
286            free_flow_speed: Some(100.0),
287            jam_factor: Some(10.0),
288            confidence: Some(1.0),
289            road_name: None,
290        };
291        assert_eq!(flow.speed, Some(0.0));
292        assert_eq!(flow.jam_factor, Some(10.0));
293    }
294
295    #[test]
296    fn test_traffic_incident_all_none_optional_fields() {
297        let incident = TrafficIncident {
298            id: None,
299            incident_type: None,
300            severity: None,
301            description: None,
302            road_name: None,
303            start_time: None,
304            end_time: None,
305        };
306        assert!(incident.id.is_none());
307        assert!(incident.severity.is_none());
308    }
309}