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