286 lines
8.4 KiB
Rust
286 lines
8.4 KiB
Rust
use deserialize::{DepartureOrArrivalData, Direction, GetDepartureOrArrivalError};
|
|
use reqwest::Url;
|
|
|
|
use crate::bahn_api::transport_modes::TransportMode;
|
|
|
|
use super::{
|
|
basic_types::{
|
|
route_ids::RouteId,
|
|
station_ids::{AsStationId, StationIdRef},
|
|
},
|
|
transport_modes::TransportModesSet,
|
|
};
|
|
|
|
pub async fn departures(
|
|
station: impl AsStationId<'_>,
|
|
transport_modes: TransportModesSet,
|
|
) -> Result<Departures, GetDeparturesError> {
|
|
departures_or_arrivals(station, transport_modes).await
|
|
}
|
|
|
|
pub async fn arrivals(
|
|
station: impl AsStationId<'_>,
|
|
transport_modes: TransportModesSet,
|
|
) -> Result<Arrivals, GetArrivalsError> {
|
|
departures_or_arrivals(station, transport_modes).await
|
|
}
|
|
|
|
pub async fn departures_or_arrivals<T: DepartureOrArrivalData>(
|
|
station: impl AsStationId<'_>,
|
|
transport_modes: TransportModesSet,
|
|
) -> Result<T, T::Err> {
|
|
let StationIdRef {
|
|
eva_number,
|
|
db_station_id,
|
|
} = station.into();
|
|
|
|
let response = reqwest::get(
|
|
Url::parse_with_params(
|
|
match T::DIRECTION {
|
|
Direction::Departure => "https://www.bahn.de/web/api/reiseloesung/abfahrten",
|
|
Direction::Arrival => "https://www.bahn.de/web/api/reiseloesung/ankuenfte",
|
|
},
|
|
[
|
|
("ortExtId", eva_number.as_str()),
|
|
("ortId", db_station_id.as_str()),
|
|
("mitVias", "true"),
|
|
("maxVias", "8"),
|
|
]
|
|
.into_iter()
|
|
.chain(
|
|
transport_modes
|
|
.into_iter()
|
|
.map(|v| ("verkehrsmittel[]", v.as_str_for_bahn_api())),
|
|
),
|
|
)
|
|
.expect("hardcoded url and generated parameters should always be valid"),
|
|
)
|
|
.await
|
|
.map_err(T::Err::network_error)?
|
|
.error_for_status()
|
|
.map_err(T::Err::network_error)?
|
|
.text()
|
|
.await
|
|
.map_err(T::Err::network_error)?;
|
|
|
|
let data: T::De =
|
|
serde_json::from_str(&response).map_err(move |e| T::Err::parse_error(response, e))?;
|
|
|
|
T::convert(data)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Departures {
|
|
pub departures: Vec<Departure>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Arrivals {
|
|
pub arrivals: Vec<Arrival>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Departure {
|
|
pub id: RouteId,
|
|
pub route: String,
|
|
pub category: Option<TransportMode>,
|
|
pub stops: Vec<NamedStop>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Arrival {
|
|
pub id: RouteId,
|
|
pub route: String,
|
|
pub category: Option<TransportMode>,
|
|
pub stops: Vec<NamedStop>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct NamedStop {
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum GetDeparturesError {
|
|
NetworkError(reqwest::Error),
|
|
ParseError(String, serde_json::Error),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum GetArrivalsError {
|
|
NetworkError(reqwest::Error),
|
|
ParseError(String, serde_json::Error),
|
|
}
|
|
|
|
impl GetDepartureOrArrivalError for GetDeparturesError {
|
|
fn network_error(err: reqwest::Error) -> Self {
|
|
Self::NetworkError(err)
|
|
}
|
|
fn parse_error(str: String, err: serde_json::Error) -> Self {
|
|
Self::ParseError(str, err)
|
|
}
|
|
}
|
|
|
|
impl GetDepartureOrArrivalError for GetArrivalsError {
|
|
fn network_error(err: reqwest::Error) -> Self {
|
|
Self::NetworkError(err)
|
|
}
|
|
fn parse_error(str: String, err: serde_json::Error) -> Self {
|
|
Self::ParseError(str, err)
|
|
}
|
|
}
|
|
|
|
pub mod deserialize {
|
|
use serde::Deserialize;
|
|
|
|
use crate::bahn_api::{basic_types::route_ids::RouteId, transport_modes::TransportMode};
|
|
|
|
use super::{
|
|
Arrival, Arrivals, Departure, Departures, GetArrivalsError, GetDeparturesError, NamedStop,
|
|
};
|
|
|
|
pub enum Direction {
|
|
Departure,
|
|
Arrival,
|
|
}
|
|
|
|
pub trait DepartureOrArrivalData: Sized {
|
|
const DIRECTION: Direction;
|
|
type De: for<'de> Deserialize<'de>;
|
|
type Err: GetDepartureOrArrivalError;
|
|
fn convert(data: Self::De) -> Result<Self, Self::Err>;
|
|
}
|
|
|
|
pub trait GetDepartureOrArrivalError {
|
|
fn network_error(err: reqwest::Error) -> Self;
|
|
fn parse_error(str: String, err: serde_json::Error) -> Self;
|
|
}
|
|
|
|
impl DepartureOrArrivalData for Departures {
|
|
const DIRECTION: Direction = Direction::Departure;
|
|
type De = DepData;
|
|
type Err = GetDeparturesError;
|
|
fn convert(mut data: Self::De) -> Result<Self, Self::Err> {
|
|
for departure in &mut data.departures {
|
|
// add terminus to the stops list if it isn't the last stop already
|
|
if let Some(terminus) = departure.terminus.take() {
|
|
if departure.stops.last().is_none_or(|stop| *stop != terminus) {
|
|
departure.stops.push(terminus);
|
|
}
|
|
}
|
|
}
|
|
Ok(Self {
|
|
departures: data
|
|
.departures
|
|
.into_iter()
|
|
.map(|dep| Departure {
|
|
id: RouteId(dep.journey_id),
|
|
route: dep.vehicle.route,
|
|
category: dep
|
|
.vehicle
|
|
.category
|
|
.and_then(|cat| TransportMode::from_str_from_bahn_api(cat.trim())),
|
|
stops: dep
|
|
.stops
|
|
.into_iter()
|
|
.map(|name| NamedStop { name })
|
|
.collect(),
|
|
})
|
|
.collect(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl DepartureOrArrivalData for Arrivals {
|
|
const DIRECTION: Direction = Direction::Arrival;
|
|
type De = ArrData;
|
|
type Err = GetArrivalsError;
|
|
fn convert(mut data: Self::De) -> Result<Self, Self::Err> {
|
|
for departure in &mut data.arrivals {
|
|
// add terminus to the stops list if it isn't the first or last stop already
|
|
// NOTE: the bahn.de api currently sets `terminus` to the *first* station when arrivals are requested
|
|
if let Some(terminus) = departure.terminus.take() {
|
|
if [departure.stops.first(), departure.stops.last()]
|
|
.into_iter()
|
|
.flatten()
|
|
.all(|stop| *stop != terminus)
|
|
{
|
|
departure.stops.insert(0, terminus);
|
|
}
|
|
}
|
|
}
|
|
Ok(Self {
|
|
arrivals: data
|
|
.arrivals
|
|
.into_iter()
|
|
.map(|arr| Arrival {
|
|
id: RouteId(arr.journey_id),
|
|
route: arr.vehicle.route,
|
|
category: arr
|
|
.vehicle
|
|
.category
|
|
.and_then(|cat| TransportMode::from_str_from_bahn_api(cat.trim())),
|
|
stops: arr
|
|
.stops
|
|
.into_iter()
|
|
.map(|name| NamedStop { name })
|
|
.collect(),
|
|
})
|
|
.collect(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct DepData {
|
|
#[serde(rename = "entries")]
|
|
departures: Vec<DepDeparture>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct ArrData {
|
|
#[serde(rename = "entries")]
|
|
arrivals: Vec<ArrArrival>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct DepDeparture {
|
|
#[serde(rename = "ueber")]
|
|
stops: Vec<String>,
|
|
journey_id: String,
|
|
#[serde(default)]
|
|
terminus: Option<String>,
|
|
#[serde(rename = "verkehrmittel")]
|
|
vehicle: DepVehicle,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct ArrArrival {
|
|
#[serde(rename = "ueber")]
|
|
stops: Vec<String>,
|
|
journey_id: String,
|
|
#[serde(default)]
|
|
terminus: Option<String>,
|
|
#[serde(rename = "verkehrmittel")]
|
|
vehicle: ArrVehicle,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct DepVehicle {
|
|
#[serde(rename = "mittelText")]
|
|
route: String,
|
|
#[serde(default, rename = "produktGattung")]
|
|
category: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct ArrVehicle {
|
|
#[serde(rename = "mittelText")]
|
|
route: String,
|
|
#[serde(default, rename = "produktGattung")]
|
|
category: Option<String>,
|
|
}
|
|
}
|