use std::fmt::{Debug, Display}; use anyhow::{Context, Result}; use chrono::{DateTime, Local, TimeZone, Utc}; use serde::{de, Deserialize, Deserializer}; #[derive(Deserialize, Debug)] #[serde(rename_all = "PascalCase")] pub struct Departure { line_name: String, direction: String, #[serde(default, deserialize_with = "deserialize_realtime")] real_time: Option>, #[serde(deserialize_with = "deserialize_timestamp")] scheduled_time: DateTime, } #[derive(Deserialize, Debug)] #[serde(rename_all = "PascalCase")] pub struct Response { pub departures: Vec, } /// Deserialize a timestamp in the form of '/Date(1671713820000-0000)/' fn deserialize_timestamp<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; let timestamp: i64 = s .trim_start_matches("/Date(") .trim_end_matches(")/") .split('-') .next() .ok_or_else(|| de::Error::custom("Invalid timestamp format"))? .parse() .map_err(de::Error::custom)?; let result = Utc.timestamp_millis_opt(timestamp).unwrap(); let local_tz = chrono::offset::Local::now().timezone(); Ok(result.with_timezone(&local_tz)) } fn deserialize_realtime<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { match deserialize_timestamp(deserializer) { Ok(inner) => Ok(Some(inner)), Err(_) => Ok(None), } } impl Display for Departure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&format!("{} {}\n", &self.line_name, &self.direction))?; let scheduled_time = self.scheduled_time.format("%H:%M"); f.write_str(&format!("departure: {}", &scheduled_time))?; if let Some(real_time) = self.real_time { let actual_time = real_time.format("%H:%M"); if real_time != self.scheduled_time { f.write_str(&format!(" | delayed: {actual_time} "))?; } } Ok(()) } } pub fn format_departures(response: Response) -> String { let departures: Vec = response.departures.iter().map(|d| d.to_string()).collect(); departures.join("\n\n") } #[derive(Deserialize, Debug)] #[serde(rename_all = "PascalCase")] pub struct PointFinderResponse { #[serde(deserialize_with = "deserialize_points", rename = "Points")] pub point: Point, } #[derive(Deserialize, Debug)] #[serde(rename_all = "PascalCase")] pub struct Point { pub stopid: String, pub name: String, pub area: String, } impl From<(&str, &str, &str)> for Point { fn from(value: (&str, &str, &str)) -> Self { Self { stopid: value.0.into(), name: value.1.into(), area: value.2.into(), } } } /// Deserialize a Point Response in the form of /// '\"33000742|||Helmholtzstraße|5655904|4621157|0||\"' fn extract_data_from_response(line: &str) -> Result { let mut iter = line.trim_start_matches("\\\"").split_terminator('|'); let stopid = iter.next().context("getting stopid from line")?; let area = iter.nth(1).context("getting area from line")?; let name = iter.next().context("getting stop name from line")?; Ok((stopid, name, area).into()) } fn deserialize_points<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let stops: Vec = Vec::deserialize(deserializer)?; let first_stop_line = stops .first() .ok_or_else(|| de::Error::custom("could not get first stop"))?; let point = extract_data_from_response(first_stop_line).map_err(de::Error::custom)?; Ok(point) }