gtfs-structures 0.47.0

Read GTFS (public transit timetables) files
Documentation
use chrono::NaiveDate;
use rgb::RGB8;
use serde::de::{self, Deserialize, Deserializer};
use serde::ser::Serializer;

pub fn deserialize_date<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
where
    D: Deserializer<'de>,
{
    let s: &str = Deserialize::deserialize(deserializer)?;
    NaiveDate::parse_from_str(s, "%Y%m%d").map_err(serde::de::Error::custom)
}

pub fn serialize_date<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_str(&date.format("%Y%m%d").to_string())
}

pub fn deserialize_option_date<'de, D>(deserializer: D) -> Result<Option<NaiveDate>, D::Error>
where
    D: Deserializer<'de>,
{
    let s = Option::<&str>::deserialize(deserializer)?
        .map(|s| NaiveDate::parse_from_str(s, "%Y%m%d").map_err(serde::de::Error::custom));
    match s {
        Some(Ok(s)) => Ok(Some(s)),
        Some(Err(e)) => Err(e),
        None => Ok(None),
    }
}

pub fn serialize_option_date<S>(date: &Option<NaiveDate>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match date {
        None => serializer.serialize_none(),
        Some(d) => serialize_date(d, serializer),
    }
}

pub fn parse_time_impl(h: &str, m: &str, s: &str) -> Result<u32, std::num::ParseIntError> {
    let hours: u32 = h.parse()?;
    let minutes: u32 = m.parse()?;
    let seconds: u32 = s.parse()?;
    Ok(hours * 3600 + minutes * 60 + seconds)
}

pub fn parse_time(s: &str) -> Result<u32, crate::Error> {
    let mk_err = || crate::Error::InvalidTime(s.to_owned());

    if s.len() < 7 {
        Err(mk_err())
    } else {
        let mut parts = s.split(':');

        let hour = parts.next().ok_or_else(mk_err)?;
        let min = parts.next().ok_or_else(mk_err)?;
        let sec = parts.next().ok_or_else(mk_err)?;
        if parts.next().is_some() {
            return Err(mk_err());
        }

        if min.len() != 2 || sec.len() != 2 {
            return Err(mk_err());
        }

        parse_time_impl(hour, min, sec).map_err(|_| mk_err())
    }
}

pub fn deserialize_time<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
    D: Deserializer<'de>,
{
    let s: &str = Deserialize::deserialize(deserializer)?;
    parse_time(s).map_err(de::Error::custom)
}

pub fn serialize_time<S>(time: &u32, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_str(
        format!(
            "{:02}:{:02}:{:02}",
            time / 3600,
            time % 3600 / 60,
            time % 60
        )
        .as_str(),
    )
}

pub fn deserialize_optional_time<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where
    D: Deserializer<'de>,
{
    let s: Option<&str> = Deserialize::deserialize(deserializer)?;

    match s {
        None => Ok(None),
        Some(t) => parse_time(t).map(Some).map_err(de::Error::custom),
    }
}

pub fn serialize_optional_time<S>(time: &Option<u32>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match time {
        None => serializer.serialize_none(),
        Some(t) => serialize_time(t, serializer),
    }
}

pub fn de_with_optional_float<'de, D>(de: D) -> Result<Option<f64>, D::Error>
where
    D: Deserializer<'de>,
{
    String::deserialize(de).and_then(|s| {
        if s.is_empty() {
            Ok(None)
        } else {
            s.parse().map(Some).map_err(de::Error::custom)
        }
    })
}

pub fn serialize_float_as_str<S>(float: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match float {
        None => serializer.serialize_str(""),
        Some(f) => serializer.serialize_str(&f.to_string()),
    }
}

pub fn parse_color(s: &str) -> Result<RGB8, crate::Error> {
    if s.len() != 6 {
        return Err(crate::Error::InvalidColor(s.to_owned()));
    }
    let r =
        u8::from_str_radix(&s[0..2], 16).map_err(|_| crate::Error::InvalidColor(s.to_owned()))?;
    let g =
        u8::from_str_radix(&s[2..4], 16).map_err(|_| crate::Error::InvalidColor(s.to_owned()))?;
    let b =
        u8::from_str_radix(&s[4..6], 16).map_err(|_| crate::Error::InvalidColor(s.to_owned()))?;
    Ok(RGB8::new(r, g, b))
}

pub fn deserialize_optional_color<'de, D>(de: D) -> Result<Option<RGB8>, D::Error>
where
    D: Deserializer<'de>,
{
    Option::<&str>::deserialize(de)?
        .map(|s| parse_color(s).map_err(de::Error::custom))
        .transpose()
}

pub fn serialize_optional_color<S>(color: &Option<RGB8>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match color {
        Some(color) => serializer
            .serialize_str(format!("{:02X}{:02X}{:02X}", color.r, color.g, color.b).as_str()),
        None => serializer.serialize_none(),
    }
}

pub fn default_route_color() -> RGB8 {
    RGB8::new(255, 255, 255)
}

pub fn de_with_empty_default<'de, T, D>(de: D) -> Result<T, D::Error>
where
    D: Deserializer<'de>,
    T: Deserialize<'de> + Default,
{
    Option::<T>::deserialize(de).map(|opt| opt.unwrap_or_default())
}

pub fn deserialize_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    let s: &str = Deserialize::deserialize(deserializer)?;
    match s {
        "0" => Ok(false),
        "1" => Ok(true),
        &_ => Err(serde::de::Error::custom(format!(
            "Invalid value `{s}`, expected 0 or 1"
        ))),
    }
}

pub fn serialize_bool<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_u8(u8::from(*value))
}

#[test]
fn test_serialize_time() {
    #[derive(Serialize, Deserialize)]
    struct Test {
        #[serde(
            deserialize_with = "deserialize_time",
            serialize_with = "serialize_time"
        )]
        time: u32,
    }
    let data_in = "time\n01:01:01\n";
    let parsed: Test = csv::Reader::from_reader(data_in.as_bytes())
        .deserialize()
        .next()
        .unwrap()
        .unwrap();
    assert_eq!(3600 + 60 + 1, parsed.time);

    let data_in_long_ride = "time\n172:35:42\n";
    let parsed_long_ride: Test = csv::Reader::from_reader(data_in_long_ride.as_bytes())
        .deserialize()
        .next()
        .unwrap()
        .unwrap();
    assert_eq!((172 * 3600) + (35 * 60) + 42, parsed_long_ride.time);

    let mut wtr = csv::Writer::from_writer(vec![]);
    wtr.serialize(parsed).unwrap();
    let data_out = String::from_utf8(wtr.into_inner().unwrap()).unwrap();
    assert_eq!(data_in, data_out);
}