mbta_rs/models/
service.rs

1//! Data models for MBTA services.
2
3use chrono::{Date, FixedOffset};
4use serde::{Deserialize, Serialize};
5
6use super::*;
7
8/// Multiple services.
9pub type Services = Vec<Service>;
10
11/// A set of dates on which trips run.
12pub type Service = Resource<ServiceAttributes>;
13
14/// Attributes for service.
15#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
16pub struct ServiceAttributes {
17    /// Days of the week.
18    pub valid_days: Vec<Day>,
19    /// Earliest date which is valid for this service.
20    #[serde(with = "mbta_date_format")]
21    pub start_date: Date<FixedOffset>,
22    /// Describes how well this schedule represents typical service for the listed schedule type.
23    pub schedule_typicality: ScheduleTypicality,
24    /// Description of the schedule type the service can be applied.
25    pub schedule_type: Option<String>,
26    /// Description of when the service is in effect.
27    pub schedule_name: Option<String>,
28    /// Extra information about exceptional dates (e.g. holiday name).
29    pub removed_dates_notes: Vec<Option<String>>,
30    /// Exceptional dates when the service is not valid.
31    #[serde(with = "vec_mbta_date_format")]
32    pub removed_dates: Vec<Date<FixedOffset>>,
33    /// Earliest date which is a part of the rating (season) which contains this service.
34    #[serde(with = "optional_mbta_date_format")]
35    pub rating_start_date: Option<Date<FixedOffset>>,
36    /// Latest date which is a part of the rating (season) which contains this service.
37    #[serde(with = "optional_mbta_date_format")]
38    pub rating_end_date: Option<Date<FixedOffset>>,
39    /// Human-readable description of the rating (season), as it should appear on public-facing websites and applications.
40    pub rating_description: Option<String>,
41    /// Latest date which is valid for this service.
42    #[serde(with = "mbta_date_format")]
43    pub end_date: Date<FixedOffset>,
44    /// Human-readable description of the service, as it should appear on public-facing websites and applications.
45    pub description: Option<String>,
46    /// Extra information about additional dates (e.g. holiday name).
47    pub added_dates_notes: Vec<Option<String>>,
48    /// Additional dates when the service is valid.
49    #[serde(with = "vec_mbta_date_format")]
50    pub added_dates: Vec<Date<FixedOffset>>,
51}
52
53/// Represents how well a schedule represents typical service for a listed schedule type.
54#[derive(Debug, PartialEq, Clone, Copy, Deserialize, Serialize)]
55#[serde(try_from = "u8")]
56#[serde(into = "u8")]
57pub enum ScheduleTypicality {
58    /// Not defined.
59    Undefined,
60    /// Typical service with perhaps minor modifications.
61    Typical,
62    /// Extra service supplements typical schedules.
63    Extra,
64    /// Reduced holiday service is provided by typical Saturday or Sunday schedule.
65    Reduced,
66    /// Major changes in service due to a planned disruption, such as construction.
67    Disrupted,
68    /// Major reductions in service for weather events or other atypical situations.
69    Atypical,
70}
71
72impl TryFrom<u8> for ScheduleTypicality {
73    type Error = String;
74
75    fn try_from(value: u8) -> Result<Self, Self::Error> {
76        match value {
77            0 => Ok(Self::Undefined),
78            1 => Ok(Self::Typical),
79            2 => Ok(Self::Extra),
80            3 => Ok(Self::Reduced),
81            4 => Ok(Self::Disrupted),
82            5 => Ok(Self::Atypical),
83            _ => Err(format!("invalid schedule typicality value: {}", value)),
84        }
85    }
86}
87
88impl From<ScheduleTypicality> for u8 {
89    fn from(value: ScheduleTypicality) -> Self {
90        match value {
91            ScheduleTypicality::Undefined => 0,
92            ScheduleTypicality::Typical => 1,
93            ScheduleTypicality::Extra => 2,
94            ScheduleTypicality::Reduced => 3,
95            ScheduleTypicality::Disrupted => 4,
96            ScheduleTypicality::Atypical => 5,
97        }
98    }
99}
100
101/// Represents a day of the week.
102#[derive(Debug, PartialEq, Clone, Copy, Deserialize, Serialize)]
103#[serde(try_from = "u8")]
104#[serde(into = "u8")]
105pub enum Day {
106    /// Monday.
107    Monday,
108    /// Tuesday.
109    Tuesday,
110    /// Wednesday.
111    Wednesday,
112    /// Thursday.
113    Thursday,
114    /// Friday.
115    Friday,
116    /// Saturday.
117    Saturday,
118    /// Sunday.
119    Sunday,
120}
121
122impl TryFrom<u8> for Day {
123    type Error = String;
124
125    fn try_from(value: u8) -> Result<Self, Self::Error> {
126        match value {
127            1 => Ok(Self::Monday),
128            2 => Ok(Self::Tuesday),
129            3 => Ok(Self::Wednesday),
130            4 => Ok(Self::Thursday),
131            5 => Ok(Self::Friday),
132            6 => Ok(Self::Saturday),
133            7 => Ok(Self::Sunday),
134            _ => Err(format!("invalid day value: {}", value)),
135        }
136    }
137}
138
139impl From<Day> for u8 {
140    fn from(value: Day) -> Self {
141        match value {
142            Day::Monday => 1,
143            Day::Tuesday => 2,
144            Day::Wednesday => 3,
145            Day::Thursday => 4,
146            Day::Friday => 5,
147            Day::Saturday => 6,
148            Day::Sunday => 7,
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    use rstest::*;
158
159    #[rstest]
160    #[case::zero(0, Ok(ScheduleTypicality::Undefined))]
161    #[case::one(1, Ok(ScheduleTypicality::Typical))]
162    #[case::two(2, Ok(ScheduleTypicality::Extra))]
163    #[case::three(3, Ok(ScheduleTypicality::Reduced))]
164    #[case::four(4, Ok(ScheduleTypicality::Disrupted))]
165    #[case::five(5, Ok(ScheduleTypicality::Atypical))]
166    #[case::invalid(6, Err("invalid schedule typicality value: 6".into()))]
167    fn test_schedule_typicality_try_from_u8(#[case] input: u8, #[case] expected: Result<ScheduleTypicality, String>) {
168        assert_eq!(ScheduleTypicality::try_from(input), expected);
169    }
170
171    #[rstest]
172    #[case::undefined(ScheduleTypicality::Undefined, 0)]
173    #[case::typical(ScheduleTypicality::Typical, 1)]
174    #[case::extra(ScheduleTypicality::Extra, 2)]
175    #[case::reduced(ScheduleTypicality::Reduced, 3)]
176    #[case::disrupted(ScheduleTypicality::Disrupted, 4)]
177    #[case::atypical(ScheduleTypicality::Atypical, 5)]
178    fn test_u8_from_schedule_typicality(#[case] input: ScheduleTypicality, #[case] expected: u8) {
179        assert_eq!(u8::from(input), expected);
180    }
181
182    #[rstest]
183    #[case::one(1, Ok(Day::Monday))]
184    #[case::two(2, Ok(Day::Tuesday))]
185    #[case::three(3, Ok(Day::Wednesday))]
186    #[case::four(4, Ok(Day::Thursday))]
187    #[case::five(5, Ok(Day::Friday))]
188    #[case::six(6, Ok(Day::Saturday))]
189    #[case::seven(7, Ok(Day::Sunday))]
190    #[case::invalid(8, Err("invalid day value: 8".into()))]
191    fn test_day_try_from_u8(#[case] input: u8, #[case] expected: Result<Day, String>) {
192        assert_eq!(Day::try_from(input), expected);
193    }
194
195    #[rstest]
196    #[case::monday(Day::Monday, 1)]
197    #[case::tuesday(Day::Tuesday, 2)]
198    #[case::wednesday(Day::Wednesday, 3)]
199    #[case::thursday(Day::Thursday, 4)]
200    #[case::friday(Day::Friday, 5)]
201    #[case::saturday(Day::Saturday, 6)]
202    #[case::sunday(Day::Sunday, 7)]
203    fn test_u8_from_day(#[case] input: Day, #[case] expected: u8) {
204        assert_eq!(u8::from(input), expected);
205    }
206}