railway-core-0.1.0/.cargo_vcs_info.json0000644000000001520000000000100134260ustar { "git": { "sha1": "0333950f69474deb120b3ba39113766a949cb0f8" }, "path_in_vcs": "railway-core" }railway-core-0.1.0/Cargo.toml0000644000000031650000000000100114330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "railway-core" version = "0.1.0" authors = ["Julian Schmidhuber "] description = "Core type definitions of the Railway backend" readme = "README.md" keywords = [ "railway-backend", "train", "public-transport", ] license = "AGPL-3.0-or-later OR EUPL-1.2" repository = "https://gitlab.com/schmiddi-on-mobile/railway-backend" [dependencies.async-trait] version = "0.1" [dependencies.chrono] version = "0.4" [dependencies.chrono-tz] version = "0.8" [dependencies.geojson] version = "0.24" optional = true [dependencies.hyper] version = "0.14" optional = true [dependencies.hyper-rustls] version = "0.24" features = ["http1"] optional = true [dependencies.log] version = "0.4" [dependencies.rustls] version = "0.21" optional = true [dependencies.rustls-pemfile] version = "2.1" optional = true [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.url] version = "2.5" [features] hyper-requester = [ "hyper-rustls", "hyper", "rustls", "rustls-pemfile", ] polylines = ["geojson"] rt-multi-thread = [] serde = [ "dep:serde", "chrono/serde", ] railway-core-0.1.0/Cargo.toml.orig000064400000000000000000000022551046102023000151130ustar 00000000000000[package] name = "railway-core" version = "0.1.0" authors = ["Julian Schmidhuber "] edition = "2021" description = "Core type definitions of the Railway backend" repository = "https://gitlab.com/schmiddi-on-mobile/railway-backend" license = "AGPL-3.0-or-later OR EUPL-1.2" keywords = ["railway-backend", "train", "public-transport"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] async-trait = "0.1" log = "0.4" chrono = { version = "0.4" } chrono-tz = "0.8" url = "2.5" geojson = { version = "0.24", optional = true } # Hyper Requester hyper = { version = "0.14", optional = true } hyper-rustls = { version = "0.24", optional = true, features = [ "http1" ] } # Note: A newer version is already available, but conflicts with the one usef for hyper rustls = { version = "0.21", optional = true } rustls-pemfile = { version = "2.1", optional = true } serde = { version = "1.0", features = [ "derive" ], optional = true } [features] hyper-requester = [ "hyper-rustls", "hyper", "rustls", "rustls-pemfile" ] rt-multi-thread = [ ] polylines = [ "geojson" ] serde = [ "dep:serde", "chrono/serde" ] railway-core-0.1.0/README.md000064400000000000000000000002371046102023000135010ustar 00000000000000# Railway Core Core type definitions of the Railway backend. This crate is part of [railway-backend](https://gitlab.com/schmiddi-on-mobile/railway-backend). railway-core-0.1.0/src/api/journeys.rs000064400000000000000000000066641046102023000160200ustar 00000000000000use chrono::{DateTime, Duration}; use chrono_tz::Tz; use crate::{Accessibility, Age, Journey, LoyaltyCard, Place, ProductsSelection, TariffClass}; #[derive(Debug, Clone)] /// The options for [`Provider::journeys`](crate::Provider::journeys) /// /// A provider can also ignore some of the options if this is not supported by the API. pub struct JourneysOptions { /// Intermediate place to route with. pub via: Vec, /// Specify to route earlier than, from [`JourneysResponse::earlier_ref`]. pub earlier_than: Option, /// Specify to route earlierthan , from [`JourneysResponse::later_ref`]. pub later_than: Option, /// How many results to include. pub results: u64, /// Whether to include stopovers. pub stopovers: bool, /// Whether to include polylines. #[cfg(feature = "polylines")] pub polylines: bool, /// Route in a bike-friendly way. pub bike_friendly: bool, /// Include tickets. pub tickets: bool, /// Allow to walk to the initial station. pub start_with_walking: bool, /// How accessible the journey must be. pub accessibility: Accessibility, /// How often it is allowed to transfer. pub transfers: TransferOptions, /// How long must the transfers be. pub transfer_time: Duration, /// When should the journey arrive, or earlier. pub arrival: Option>, /// When should the journey depart, or later. pub departure: Option>, /// What products to route with. pub products: ProductsSelection, /// What class to use. pub tariff_class: TariffClass, /// What language to query with. pub language: Option, /// What loyalty cards does the passenger have. pub loyalty_card: Option, /// What age does the passenger have. pub passenger_age: Option, } /// How often is a journey allowed to transfer. #[derive(Debug, Clone, Default)] pub enum TransferOptions { /// Allow unlimited transfers. #[default] Unlimited, /// Allow transfers only to a limited number. Limited(u64), } impl Default for JourneysOptions { fn default() -> Self { Self { via: Default::default(), earlier_than: Default::default(), later_than: Default::default(), results: 5, stopovers: Default::default(), #[cfg(feature = "polylines")] polylines: Default::default(), bike_friendly: Default::default(), tickets: true, start_with_walking: true, accessibility: Default::default(), transfers: TransferOptions::default(), transfer_time: Duration::zero(), arrival: Default::default(), departure: Default::default(), products: Default::default(), tariff_class: TariffClass::Second, language: Default::default(), loyalty_card: Default::default(), passenger_age: Default::default(), } } } /// The response for [`Provider::journeys`](crate::Provider::journeys) #[derive(Debug, Clone)] pub struct JourneysResponse { /// Reference to query earlier, used in [`JourneysOptions::earlier_than`]. pub earlier_ref: Option, /// Reference to query later, used in [`JourneysOptions::later_than`]. pub later_ref: Option, /// The list of journeys which is the result of the request. pub journeys: Vec, } railway-core-0.1.0/src/api/locations.rs000064400000000000000000000012001046102023000161120ustar 00000000000000use crate::Place; /// The response given by [`Provider::locations`](crate::Provider::locations) pub type LocationsResponse = Vec; #[derive(Debug)] /// The options for [`Provider::locations`](crate::Provider::locations) pub struct LocationsOptions { /// What to query for. pub query: String, /// How many results to return. pub results: u64, /// What language to query in. pub language: Option, } impl Default for LocationsOptions { fn default() -> Self { Self { query: Default::default(), results: 10, language: Default::default(), } } } railway-core-0.1.0/src/api/mod.rs000064400000000000000000000035211046102023000147060ustar 00000000000000mod journeys; mod locations; mod refresh_journey; pub use journeys::*; pub use locations::*; pub use refresh_journey::*; use crate::{Journey, Place, Requester}; use async_trait::async_trait; /// The core type definition specifying what a provider needs to do. /// /// To implement the required methods, a provider usually queries an external but public API. #[cfg_attr(feature = "rt-multi-thread", async_trait)] #[cfg_attr(not(feature = "rt-multi-thread"), async_trait(?Send))] pub trait Provider { type Error: std::error::Error; /// Query a list of journeys. /// /// Be careful about timezones! /// The types given to you are annotated with an arbitrary timezone, your public API may only understand one specific timezone though. /// Ensure you correctly convert the given timezone to a timezone you require. /// For returning a date and time, you may choose an arbitrary timezone. async fn journeys( &self, from: Place, to: Place, opts: JourneysOptions, ) -> Result>; /// Autocomplete a location. /// /// This takes a query string and should return a list of locations which match the given string. async fn locations( &self, opts: LocationsOptions, ) -> Result>; /// Refresh a journey. /// /// This takes a previously queried journey and refreshes real-time data. /// A naive implementation may call [`Provider::journeys`] again and return the matching journey, this is a valid strategy if there is no API for refreshing a journey. async fn refresh_journey( &self, journey: &Journey, opts: RefreshJourneyOptions, ) -> Result>; } railway-core-0.1.0/src/api/refresh_journey.rs000064400000000000000000000013471046102023000173440ustar 00000000000000use crate::{Journey, TariffClass}; #[derive(Debug, Default)] /// The options for [`Provider::refresh_journey`](crate::Provider::refresh_journey) /// /// A provider can also ignore some of the options if this is not supported by the API. pub struct RefreshJourneyOptions { /// Whether to include stopovers. pub stopovers: bool, #[cfg(feature = "polylines")] /// Whether to include polylines. pub polylines: bool, /// Include tickets. pub tickets: bool, /// What class to use. pub tariff_class: TariffClass, /// What language to query with. pub language: Option, } /// The result for [`Provider::refresh_journey`](crate::Provider::refresh_journey) pub type RefreshJourneyResponse = Journey; railway-core-0.1.0/src/error.rs000064400000000000000000000013761046102023000145150ustar 00000000000000use std::error::Error as StdError; use std::fmt::{Display, Formatter}; /// An error in the API. #[derive(Debug)] pub enum Error { /// Error requesting data using the [`Requester`](crate::Requester). Request(R), /// Any [`Provider`](crate::Provider)-specific error, e.g. failing to parse the response from the API. Provider(P), // TODO: Journey not found, ... } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Self::Request(e) => write!(f, "error requesting data: {}", e), Self::Provider(e) => write!(f, "provider specific error: {}", e), } } } impl StdError for Error {} railway-core-0.1.0/src/lib.rs000064400000000000000000000003121046102023000141170ustar 00000000000000#![doc = include_str!("../README.md")] mod api; mod error; mod requester; #[cfg(feature = "serde")] mod serialize; mod types; pub use api::*; pub use error::*; pub use requester::*; pub use types::*; railway-core-0.1.0/src/requester/hyper.rs000064400000000000000000000122371046102023000165300ustar 00000000000000use crate::{Requester, RequesterBuilder}; use async_trait::async_trait; use hyper::client::HttpConnector; use hyper::{Body, Method, Request}; use hyper_rustls::builderstates::WantsProtocols2; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use std::collections::HashMap; #[derive(Clone)] pub struct HyperRustlsRequester(hyper::Client, Body>); #[cfg_attr(feature = "rt-multi-thread", async_trait)] #[cfg_attr(not(feature = "rt-multi-thread"), async_trait(?Send))] impl Requester for HyperRustlsRequester { type Error = HyperRustlsRequesterError; async fn get( &self, url: &url::Url, body: &[u8], headers: HashMap<&str, &str>, ) -> Result, Self::Error> { self.request(Method::GET, url, body, headers).await } async fn post( &self, url: &url::Url, body: &[u8], headers: HashMap<&str, &str>, ) -> Result, Self::Error> { self.request(Method::POST, url, body, headers).await } } pub struct HyperRustlsRequesterBuilder( hyper::client::Builder, HttpsConnectorBuilder, ); impl RequesterBuilder for HyperRustlsRequesterBuilder { type Requester = HyperRustlsRequester; fn with_pem_bundle(mut self, bytes: &[u8]) -> Self { let mut bytes = &bytes[..]; // TODO: This only allows calling this function once. self.1 = HttpsConnectorBuilder::new() .with_tls_config({ let mut store = rustls::RootCertStore::empty(); let certs = rustls_pemfile::certs(&mut bytes); for cert in certs.into_iter() { if let Ok(cert) = cert { if let Err(e) = store.add(&rustls::Certificate(cert.to_vec())) { log::error!("Failed to add certificate: {}", e); } } else { log::error!("Failed to read certificate"); } } rustls::ClientConfig::builder() .with_safe_defaults() .with_root_certificates(store) .with_no_client_auth() }) .https_or_http() .enable_http1(); self } fn build(self) -> Self::Requester { let https = self.1.build(); let client = self.0.build(https); HyperRustlsRequester(client) } } impl Default for HyperRustlsRequesterBuilder { fn default() -> Self { Self( hyper::Client::builder(), HttpsConnectorBuilder::new() .with_native_roots() .https_or_http() .enable_http1(), ) } } impl HyperRustlsRequester { pub fn new() -> Self { let https = HttpsConnectorBuilder::new() .with_native_roots() .https_or_http() .enable_http1() .build(); let client = hyper::Client::builder().build(https); Self(client) } async fn request( &self, method: hyper::Method, url: &url::Url, body: &[u8], headers: HashMap<&str, &str>, ) -> Result, HyperRustlsRequesterError> { log::trace!( "{}: URL: {}, Body: {}, Headers: {:?}", method, url, String::from_utf8_lossy(body), headers ); let body = body.to_vec(); let mut req = Request::builder().method(method).uri(url.as_str()); for (k, v) in headers { req = req.header(k, v); } let req = req.body(Body::from(body)).unwrap(); let (parts, resp_body) = self.0.request(req).await?.into_parts(); let bytes = hyper::body::to_bytes(resp_body).await?; if parts.status.is_success() { Ok(bytes.to_vec()) } else { Err(HyperRustlsRequesterError::NoSuccessStatusCode( parts.status.as_u16(), parts.status.canonical_reason(), bytes.to_vec(), )) } } } impl Default for HyperRustlsRequester { fn default() -> Self { Self::new() } } #[derive(Debug)] pub enum HyperRustlsRequesterError { /// hyper failed. Hyper(hyper::Error), /// Got a status code which is no success. /// Contains the status code, the "canonical reason" and the body bytes. NoSuccessStatusCode(u16, Option<&'static str>, Vec), } impl std::fmt::Display for HyperRustlsRequesterError { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Self::Hyper(e) => write!(fmt, "hyper error: {}", e), Self::NoSuccessStatusCode(code, Some(reason), _) => { write!(fmt, "unsuccessful status code {} ({})", code, reason) } Self::NoSuccessStatusCode(code, None, _) => { write!(fmt, "unsuccessful status code {}", code) } } } } impl std::error::Error for HyperRustlsRequesterError {} impl From for HyperRustlsRequesterError { fn from(e: hyper::Error) -> HyperRustlsRequesterError { Self::Hyper(e) } } railway-core-0.1.0/src/requester/mod.rs000064400000000000000000000026631046102023000161620ustar 00000000000000#[cfg(feature = "hyper-requester")] mod hyper; #[cfg(feature = "hyper-requester")] pub use hyper::*; use async_trait::async_trait; use std::collections::HashMap; /// A general trait to query data from the internet. /// /// This makes requests to a specified API with a specified body and headers, returning the bytes received. /// It only supports limited HTTP(s) capabilities for now, further capabilities may be added if required. #[cfg_attr(feature = "rt-multi-thread", async_trait)] #[cfg_attr(not(feature = "rt-multi-thread"), async_trait(?Send))] pub trait Requester: Send + Sync { /// Errors this requester may produce. type Error: std::error::Error; /// Make a GET request. async fn get( &self, url: &url::Url, body: &[u8], headers: HashMap<&str, &str>, ) -> Result, Self::Error>; /// Make a POST request. async fn post( &self, url: &url::Url, body: &[u8], headers: HashMap<&str, &str>, ) -> Result, Self::Error>; } /// Build a [`Requester`]. /// /// This allows a provider to specify e.g. custom certificates. pub trait RequesterBuilder { /// The [`Requester`] built by this builder. type Requester: Requester; /// Add a custom certificate, formatted as a PEM-bundle which should be accepted. fn with_pem_bundle(self, bytes: &[u8]) -> Self; /// Build the [`Requester`]. fn build(self) -> Self::Requester; } railway-core-0.1.0/src/serialize.rs000064400000000000000000000030661046102023000153510ustar 00000000000000pub(crate) mod duration { use chrono::Duration; use serde::{Deserialize, Deserializer, Serializer}; pub(crate) fn serialize(v: &Option, s: S) -> Result where S: Serializer, { if let Some(d) = v { s.serialize_some(&d.num_minutes()) } else { s.serialize_none() } } pub(crate) fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: Deserializer<'de>, { let v = Option::::deserialize(d)?; Ok(v.map(Duration::minutes)) } } pub(crate) mod datetime_with_timezone { use chrono::{DateTime, NaiveDateTime}; use chrono_tz::Tz; use serde::{Deserialize, Deserializer, Serializer}; pub(crate) fn serialize(v: &Option>, s: S) -> Result where S: Serializer, { if let Some(d) = v { let tz = d.timezone().to_string(); let naive = d.naive_utc(); s.serialize_some(&(naive, tz)) } else { s.serialize_none() } } pub(crate) fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: Deserializer<'de>, { let v = Option::<(NaiveDateTime, String)>::deserialize(d)?; if let Some((d, tz)) = v { let tz = tz .parse() .map_err(|_| serde::de::Error::custom("Failed to parse timezone"))?; Ok(Some(d.and_utc().with_timezone(&tz))) } else { Ok(None) } } } railway-core-0.1.0/src/types.rs000064400000000000000000000370321046102023000145260ustar 00000000000000use std::borrow::Cow; use std::collections::HashSet; use chrono::DateTime; use chrono::Duration; use chrono_tz::Tz; #[cfg(feature = "polylines")] use geojson::FeatureCollection; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone)] /// A general location. pub enum Location { /// An address. Address { /// The address. address: String, /// The latitude. latitude: f32, /// The longitude. longitude: f32, }, /// Anything else. Point { /// A not further specified ID. id: Option, /// Some name. name: Option, /// Is this a point of interest. poi: Option, /// The latitude. latitude: f32, /// The longitude. longitude: f32, }, } impl PartialEq for Location { fn eq(&self, other: &Self) -> bool { match (&self, other) { (Location::Address { address: a, .. }, Location::Address { address: b, .. }) => a == b, (Location::Point { id: Some(a), .. }, Location::Point { id: Some(b), .. }) => a == b, (_, _) => false, } } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] /// Any place. pub enum Place { /// A [`Station`]. Station(Station), /// A [`Location`]. Location(Location), } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Default, Clone)] /// A station where trains drive from. pub struct Station { /// A provider-specific, unique station id. pub id: String, /// A human-readable name pub name: Option, /// Where the station is located. pub location: Option, /// The products served on the station. pub products: Vec, } impl PartialEq for Station { fn eq(&self, other: &Self) -> bool { self.id == other.id } } #[derive(Debug, Clone, PartialEq, Eq)] /// A selection of modes. pub struct ProductsSelection(HashSet); impl Default for ProductsSelection { fn default() -> Self { Self::all() } } impl ProductsSelection { pub fn all() -> Self { // TODO: Automatically generate from enum? Self(HashSet::from([ Mode::HighSpeedTrain, Mode::RegionalTrain, Mode::SuburbanTrain, Mode::Subway, Mode::Tram, Mode::Bus, Mode::Ferry, Mode::Ferry, Mode::Cablecar, Mode::OnDemand, Mode::Unknown, ])) } pub fn contains(&self, mode: &Mode) -> bool { self.0.contains(mode) } } impl From> for ProductsSelection { fn from(modes: HashSet) -> Self { Self(modes) } } impl From for HashSet { fn from(modes: ProductsSelection) -> Self { modes.0 } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// The product a [`Line`] uses. pub struct Product { /// The mode of transport. pub mode: Mode, /// A name. pub name: Cow<'static, str>, /// A shorter name (usually one character). pub short: Cow<'static, str>, } impl Product { pub const fn unknown() -> Self { Self { mode: Mode::Unknown, name: Cow::Borrowed("Unknown"), short: Cow::Borrowed("Unknown"), } } } /// Different modes of transport. /// /// See also . #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { HighSpeedTrain, RegionalTrain, SuburbanTrain, Subway, Tram, Bus, Ferry, Cablecar, OnDemand, Unknown, // TODO: Walking? } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// Different kinds of loyalty cards supported. pub enum LoyaltyCard { BahnCard25Class1, BahnCard25Class2, BahnCard50Class1, BahnCard50Class2, Vorteilscard, HalbtaxaboRailplus, Halbtaxabo, VoordeelurenaboRailplus, Voordeelurenabo, SHCard, Generalabonnement, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// The age of a person in years. pub struct Age(pub u64); impl LoyaltyCard { /// See . pub fn from_id(value: u8) -> Option { match value { 1 => Some(LoyaltyCard::BahnCard25Class1), 2 => Some(LoyaltyCard::BahnCard25Class2), 3 => Some(LoyaltyCard::BahnCard50Class1), 4 => Some(LoyaltyCard::BahnCard50Class2), 9 => Some(LoyaltyCard::Vorteilscard), 10 => Some(LoyaltyCard::HalbtaxaboRailplus), 11 => Some(LoyaltyCard::Halbtaxabo), 12 => Some(LoyaltyCard::VoordeelurenaboRailplus), 13 => Some(LoyaltyCard::Voordeelurenabo), 14 => Some(LoyaltyCard::SHCard), 15 => Some(LoyaltyCard::Generalabonnement), _ => None, } } pub fn to_id(self) -> u8 { match self { LoyaltyCard::BahnCard25Class1 => 1, LoyaltyCard::BahnCard25Class2 => 2, LoyaltyCard::BahnCard50Class1 => 3, LoyaltyCard::BahnCard50Class2 => 4, LoyaltyCard::Vorteilscard => 9, LoyaltyCard::HalbtaxaboRailplus => 10, LoyaltyCard::Halbtaxabo => 11, LoyaltyCard::VoordeelurenaboRailplus => 12, LoyaltyCard::Voordeelurenabo => 13, LoyaltyCard::SHCard => 14, LoyaltyCard::Generalabonnement => 15, } } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// What class to travel with. pub enum TariffClass { /// First class. First, /// Second class. Second, } impl Default for TariffClass { fn default() -> Self { Self::Second } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// How accessible a trip should be. pub enum Accessibility { /// Accessibility is not required. r#None, /// Partial accessibility is required. Partial, /// Complete accessibility is required. Complete, } impl Default for Accessibility { fn default() -> Self { Self::None } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// How full a [`Leg`] is. pub enum LoadFactor { /// Not full. LowToMedium, /// Full. High, /// Very full. VeryHigh, /// Extremely full. ExceptionallyHigh, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// A line serving a [`Leg`]. pub struct Line { /// The line name. pub name: Option, /// The line number. pub fahrt_nr: Option, /// The mode of transport. pub mode: Mode, /// The product of the transport. pub product: Product, /// The operator of the line. pub operator: Option, /// The nameof the product. pub product_name: Option, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] /// At what frequency the train drives. pub struct Frequency { /// Mimimum duration between the same train. #[cfg_attr(feature = "serde", serde(with = "crate::serialize::duration"))] pub minimum: Option, /// Maximum duration between the same train. #[cfg_attr(feature = "serde", serde(with = "crate::serialize::duration"))] pub maximum: Option, /// How often this iteration occures. pub iterations: Option, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] /// A leg of a journey. pub struct Leg { /// The origin to enter the train from. pub origin: Place, /// The destination to exit the train from. pub destination: Place, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The real-time departure time. pub departure: Option>, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The scheduled departure time. pub planned_departure: Option>, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The real-time arrival time. pub arrival: Option>, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The scheduled arrival time. pub planned_arrival: Option>, /// Whether this leg is reachable from the previous one. pub reachable: bool, /// A unique ID for the trip. pub trip_id: Option, /// The line serving this leg. pub line: Option, /// The direction of the leg. pub direction: Option, /// The real-time arrival platform. pub arrival_platform: Option, /// The scheduled arrival platform. pub planned_arrival_platform: Option, /// The real-time departure platform. pub departure_platform: Option, /// The scheduled departure platform. pub planned_departure_platform: Option, /// The frequency of the leg. pub frequency: Option, /// Whether this leg was cancelled. pub cancelled: bool, /// Intermediate locations for the leg. pub intermediate_locations: Vec, /// The load of the leg. pub load_factor: Option, /// Remarks on the leg. pub remarks: Vec, /// The polyline of the leg. #[cfg(feature = "polylines")] pub polyline: Option, /// Whether this leg needs to be walked. pub walking: bool, /// Whether this leg requires transfer. pub transfer: bool, /// How long this leg is. pub distance: Option, } impl Leg { // An ID of a leg based on attributes that should not change e.g. when refreshed. pub fn id(&self) -> String { format!( "{};{};{};{};{}", self.trip_id.as_ref().map(|s| &s[..]).unwrap_or_default(), self.planned_departure .as_ref() .map(|t| t.to_string()) .unwrap_or_default(), self.planned_arrival .as_ref() .map(|t| t.to_string()) .unwrap_or_default(), self.planned_departure_platform .as_ref() .map(|s| &s[..]) .unwrap_or_default(), self.planned_arrival_platform .as_ref() .map(|s| &s[..]) .unwrap_or_default() ) } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] // Almost always, we have `IntermediateLocation::Stop`. As the documentation for this lint suggests, boxing the larger variant which is almost always used is counterproductive. #[allow(clippy::large_enum_variant)] /// An intermediate locaion of a leg. pub enum IntermediateLocation { /// A place where the train stops to let out passengers. Stop(Stop), /// A named railway track the train is passing over. Railway(Place), } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] /// A place where the train stops. pub struct Stop { /// The place of the stop. pub place: Place, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The real-time departure time. pub departure: Option>, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The scheduled departure time. pub planned_departure: Option>, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The real-time arrival time. pub arrival: Option>, #[cfg_attr( feature = "serde", serde(with = "crate::serialize::datetime_with_timezone") )] /// The scheduled arrival time. pub planned_arrival: Option>, /// The real-time arrival platform. pub arrival_platform: Option, /// The scheduled arrival platform. pub planned_arrival_platform: Option, /// The real-time departure platform. pub departure_platform: Option, /// The real-time departure platform. pub planned_departure_platform: Option, /// Whether this stop is cancelled. pub cancelled: bool, /// Remarks specific to this stop. pub remarks: Vec, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] /// The price of the trip. pub struct Price { /// The amount. pub amount: f64, /// The currency. pub currency: String, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] /// A single journey from source to destination. pub struct Journey { /// A unique journey id. pub id: String, /// The legs that make up the journey. pub legs: Vec, /// The price of the journey. pub price: Option, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// The operator serving a [`Line`]. pub struct Operator { /// An unique id. pub id: String, /// The name. pub name: String, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// What type a remark has. pub enum RemarkType { /// It is a hint, e.g. information that bikes are not allowed. Hint, /// It is a status, e.g. that the trip is cancelled. Status, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// What to associate a [`Remark`] with. pub enum RemarkAssociation { /// E.g. bikes allowed, disallowed, limited. Bike, /// E.g. accessible equipment in the train. Accessibility, /// E.g. one can buy tickets in the train. Ticket, /// There is a power socket in the train. Power, /// There is air conditioning on the train. AirConditioning, /// There is WiFi on the train. WiFi, /// There is no first class on this train. OnlySecondClass, /// The remark code specifies an association, but this could not yet be decoded. Unknown, /// The remark did not specify an association. None, } impl Default for RemarkAssociation { fn default() -> Self { Self::Unknown } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq)] /// A remark on a leg or stopover. pub struct Remark { /// The provider-specific code for it. pub code: String, /// Text to display. pub text: String, /// What kind of remark it has. pub r#type: RemarkType, /// What to associate the remark with. pub association: RemarkAssociation, /// A short summary of the remark. pub summary: Option, /// What trip this remark is about. pub trip_id: Option, }