use crate::error::EveryMapResult;
use crate::types::{BoundingBox, Coordinate, Polyline};
use async_trait::async_trait;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
impl fmt::Display for DepartureTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DepartureTime::Now => f.write_str("now"),
DepartureTime::Timestamp(ts) => write!(f, "{}", ts),
DepartureTime::Iso8601(s) => f.write_str(s),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DepartureTime {
Now,
Timestamp(i64),
Iso8601(String),
}
impl Serialize for DepartureTime {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let s = match self {
DepartureTime::Now => "now".to_string(),
DepartureTime::Timestamp(ts) => ts.to_string(),
DepartureTime::Iso8601(s) => s.clone(),
};
serializer.serialize_str(&s)
}
}
impl<'de> Deserialize<'de> for DepartureTime {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
if s == "now" {
Ok(DepartureTime::Now)
} else if let Ok(ts) = s.parse::<i64>() {
Ok(DepartureTime::Timestamp(ts))
} else {
Ok(DepartureTime::Iso8601(s))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum AvoidType {
Tolls,
Ferries,
Tunnels,
Highways,
DirtRoads,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RouteOptions {
pub transport_mode: Option<TransportMode>,
pub alternatives: Option<u32>,
pub avoid: Vec<AvoidType>,
pub departure_time: Option<DepartureTime>,
pub arrival_time: Option<DepartureTime>,
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_extra: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum TransportMode {
Car,
Truck,
Pedestrian,
Bicycle,
Scooter,
Bus,
Taxi,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteStep {
pub instruction: Option<String>,
pub distance: Option<f64>,
pub duration: Option<f64>,
pub start_coordinate: Option<Coordinate>,
pub end_coordinate: Option<Coordinate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteResult {
pub distance: f64,
pub duration: f64,
pub geometry: Polyline,
pub transport_mode: Option<TransportMode>,
pub steps: Vec<RouteStep>,
pub bounding_box: Option<BoundingBox>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteResponse {
pub routes: Vec<RouteResult>,
}
#[async_trait]
pub trait Router: Send + Sync {
async fn calculate_route(
&self,
start: &Coordinate,
end: &Coordinate,
options: &RouteOptions,
) -> EveryMapResult<RouteResponse>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_route_options_default() {
let options = RouteOptions::default();
assert!(options.transport_mode.is_none());
assert!(options.alternatives.is_none());
assert!(options.avoid.is_empty());
assert!(options.departure_time.is_none());
assert!(options.arrival_time.is_none());
assert!(options.language.is_none());
assert!(options.provider_extra.is_none());
}
#[test]
fn test_route_options_with_fields() {
let options = RouteOptions {
transport_mode: Some(TransportMode::Car),
alternatives: Some(3),
avoid: vec![AvoidType::Tolls, AvoidType::Ferries],
language: Some("de-DE".to_string()),
provider_extra: Some(serde_json::json!({"routing_mode": "fast"})),
..Default::default()
};
assert_eq!(options.transport_mode, Some(TransportMode::Car));
assert_eq!(options.alternatives, Some(3));
assert_eq!(options.avoid.len(), 2);
}
#[test]
fn test_transport_mode_serialization() {
assert_eq!(
serde_json::to_string(&TransportMode::Car).unwrap(),
"\"Car\""
);
let mode: TransportMode = serde_json::from_str("\"Truck\"").unwrap();
assert_eq!(mode, TransportMode::Truck);
}
#[test]
fn test_avoid_type_serialization() {
assert_eq!(
serde_json::to_string(&AvoidType::Tolls).unwrap(),
"\"Tolls\""
);
let avoid: AvoidType = serde_json::from_str("\"Highways\"").unwrap();
assert_eq!(avoid, AvoidType::Highways);
}
#[test]
fn test_route_result_construction() {
let result = RouteResult {
distance: 15000.0,
duration: 1800.0,
geometry: Polyline::new(vec![]),
transport_mode: Some(TransportMode::Car),
steps: vec![],
bounding_box: None,
raw: None,
};
assert_eq!(result.distance, 15000.0);
assert_eq!(result.duration, 1800.0);
assert_eq!(result.transport_mode, Some(TransportMode::Car));
}
#[test]
fn test_avoid_type_all_variants_serde() {
let variants = [
AvoidType::Tolls,
AvoidType::Ferries,
AvoidType::Tunnels,
AvoidType::Highways,
AvoidType::DirtRoads,
];
for v in &variants {
let json = serde_json::to_string(v).unwrap();
let back: AvoidType = serde_json::from_str(&json).unwrap();
assert_eq!(*v, back, "Failed roundtrip for {:?}", v);
}
}
#[test]
fn test_avoid_type_all_variants_distinct() {
let variants = [
AvoidType::Tolls,
AvoidType::Ferries,
AvoidType::Tunnels,
AvoidType::Highways,
AvoidType::DirtRoads,
];
for i in 0..variants.len() {
for j in 0..variants.len() {
if i != j {
assert_ne!(variants[i], variants[j]);
}
}
}
}
#[test]
fn test_transport_mode_all_variants_serde() {
let variants = [
TransportMode::Car,
TransportMode::Truck,
TransportMode::Pedestrian,
TransportMode::Bicycle,
TransportMode::Scooter,
TransportMode::Bus,
TransportMode::Taxi,
TransportMode::Unknown,
];
for v in &variants {
let json = serde_json::to_string(v).unwrap();
let back: TransportMode = serde_json::from_str(&json).unwrap();
assert_eq!(*v, back, "Failed roundtrip for {:?}", v);
}
}
#[test]
fn test_transport_mode_all_variants_distinct() {
let variants = [
TransportMode::Car,
TransportMode::Truck,
TransportMode::Pedestrian,
TransportMode::Bicycle,
TransportMode::Scooter,
TransportMode::Bus,
TransportMode::Taxi,
TransportMode::Unknown,
];
for i in 0..variants.len() {
for j in 0..variants.len() {
if i != j {
assert_ne!(variants[i], variants[j]);
}
}
}
}
#[test]
fn test_route_response_serde_roundtrip() {
let response = RouteResponse {
routes: vec![RouteResult {
distance: 15000.0,
duration: 1800.0,
geometry: Polyline::new(vec![]),
transport_mode: Some(TransportMode::Car),
steps: vec![],
bounding_box: None,
raw: None,
}],
};
let json = serde_json::to_string(&response).unwrap();
let back: RouteResponse = serde_json::from_str(&json).unwrap();
assert_eq!(back.routes.len(), 1);
assert_eq!(back.routes[0].distance, 15000.0);
assert_eq!(back.routes[0].transport_mode, Some(TransportMode::Car));
}
#[test]
fn test_route_response_empty_routes() {
let response = RouteResponse { routes: vec![] };
let json = serde_json::to_string(&response).unwrap();
let back: RouteResponse = serde_json::from_str(&json).unwrap();
assert!(back.routes.is_empty());
}
#[test]
fn test_route_options_serde_roundtrip() {
let options = RouteOptions {
transport_mode: Some(TransportMode::Truck),
alternatives: Some(2),
avoid: vec![AvoidType::Tolls, AvoidType::Ferries],
departure_time: Some(DepartureTime::Iso8601("2024-06-01T08:00:00".to_string())),
arrival_time: None,
language: Some("en".to_string()),
provider_extra: Some(
serde_json::json!({"routing_mode": "fast", "truck": {"weight": 18}}),
),
};
let json = serde_json::to_string(&options).unwrap();
let back: RouteOptions = serde_json::from_str(&json).unwrap();
assert_eq!(back.transport_mode, Some(TransportMode::Truck));
assert_eq!(back.alternatives, Some(2));
assert_eq!(back.avoid.len(), 2);
assert!(back.provider_extra.is_some());
}
#[test]
fn test_route_step_serde_roundtrip() {
let step = RouteStep {
instruction: Some("Turn right onto Main St".to_string()),
distance: Some(500.0),
duration: Some(60.0),
start_coordinate: Some(Coordinate::new(52.5, 13.4).unwrap()),
end_coordinate: Some(Coordinate::new(52.51, 13.41).unwrap()),
};
let json = serde_json::to_string(&step).unwrap();
let back: RouteStep = serde_json::from_str(&json).unwrap();
assert_eq!(back.instruction.as_deref(), Some("Turn right onto Main St"));
assert_eq!(back.distance, Some(500.0));
assert_eq!(back.duration, Some(60.0));
}
#[test]
fn test_route_result_full_serde_roundtrip() {
let result = RouteResult {
distance: 0.0,
duration: 0.0,
geometry: Polyline::new(vec![Coordinate::new(52.5, 13.4).unwrap()]),
transport_mode: Some(TransportMode::Pedestrian),
steps: vec![RouteStep {
instruction: Some("Walk north".to_string()),
distance: Some(100.0),
duration: Some(120.0),
start_coordinate: None,
end_coordinate: None,
}],
bounding_box: Some(BoundingBox::new(
Coordinate::new(52.6, 13.5).unwrap(),
Coordinate::new(52.4, 13.3).unwrap(),
)),
raw: Some(serde_json::json!({"legs": []})),
};
let json = serde_json::to_string(&result).unwrap();
let back: RouteResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.distance, 0.0);
assert_eq!(back.steps.len(), 1);
assert!(back.bounding_box.is_some());
assert!(back.raw.is_some());
}
#[test]
fn test_route_result_zero_distance_duration() {
let result = RouteResult {
distance: 0.0,
duration: 0.0,
geometry: Polyline::new(vec![]),
transport_mode: None,
steps: vec![],
bounding_box: None,
raw: None,
};
assert_eq!(result.distance, 0.0);
assert_eq!(result.duration, 0.0);
}
#[test]
fn test_route_options_all_avoid_types() {
let options = RouteOptions {
avoid: vec![
AvoidType::Tolls,
AvoidType::Ferries,
AvoidType::Tunnels,
AvoidType::Highways,
AvoidType::DirtRoads,
],
..Default::default()
};
assert_eq!(options.avoid.len(), 5);
}
#[test]
fn test_departure_time_now_serde() {
let json = serde_json::to_string(&DepartureTime::Now).unwrap();
assert_eq!(json, "\"now\"");
let back: DepartureTime = serde_json::from_str(&json).unwrap();
assert_eq!(back, DepartureTime::Now);
}
#[test]
fn test_departure_time_timestamp_serde() {
let dt = DepartureTime::Timestamp(1717200000);
let json = serde_json::to_string(&dt).unwrap();
assert_eq!(json, "\"1717200000\"");
let back: DepartureTime = serde_json::from_str(&json).unwrap();
assert_eq!(back, DepartureTime::Timestamp(1717200000));
}
#[test]
fn test_departure_time_iso8601_serde() {
let dt = DepartureTime::Iso8601("2024-06-01T08:00:00Z".to_string());
let json = serde_json::to_string(&dt).unwrap();
assert_eq!(json, "\"2024-06-01T08:00:00Z\"");
let back: DepartureTime = serde_json::from_str(&json).unwrap();
assert_eq!(
back,
DepartureTime::Iso8601("2024-06-01T08:00:00Z".to_string())
);
}
}