mbta_rs/models/
shared.rs

1//! Data models for shared/common data.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7/// MBTA V3 API response object.
8#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
9pub struct Response<D> {
10    /// Data payload of the response.
11    pub data: D,
12    /// JSON API version.
13    pub jsonapi: APIVersion,
14    /// Links to different pages of the endpoint.
15    #[serde(default)]
16    pub links: Option<Links>,
17}
18
19/// Version of the JSON API.
20#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
21pub struct APIVersion {
22    /// Version as a string.
23    pub version: String,
24}
25
26/// Page limit and offset numbers to the first, next, and last pages of the endpoint.
27#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
28pub struct Links {
29    /// HTTP link to the first page of the endpoint.
30    #[serde(default)]
31    pub first: Option<String>,
32    /// HTTP link to the next page of the endpoint.
33    #[serde(default)]
34    pub next: Option<String>,
35    /// HTTP link to the last page of the endpoint.
36    #[serde(default)]
37    pub last: Option<String>,
38}
39
40/// Some MBTA resource, bundling common metadata with the actual model attributes.
41#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
42pub struct Resource<Attribute> {
43    /// The JSON API resource type.
44    #[serde(rename = "type")]
45    pub resource_type: String,
46    /// The JSON API resource id.
47    pub id: String,
48    /// Related endpoint links. *This field could use some more documentation.*
49    #[serde(default)]
50    pub links: Option<HashMap<String, String>>,
51    /// Model attributes.
52    pub attributes: Attribute,
53    /// Relationships to other data models.
54    #[serde(default)]
55    pub relationships: Option<HashMap<String, Relationships>>,
56}
57
58/// A model's relationships to other data models.
59#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
60pub struct Relationships {
61    /// Another model that is related to this data model.
62    pub data: Option<RelationshipAtom>,
63}
64
65/// Atomic data for relationships between data models.
66#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
67pub struct RelationshipAtom {
68    /// The type of the related model.
69    #[serde(rename = "type")]
70    pub relationship_type: String,
71    /// The ID of the related model.
72    pub id: String,
73}
74
75/// The type of transportation something can support.
76#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)]
77#[serde(try_from = "u8")]
78#[serde(into = "u8")]
79pub enum RouteType {
80    /// Light rail transportation.
81    LightRail,
82    /// Heavy rail transportation.
83    HeavyRail,
84    /// Commuter rail transportation.
85    CommuterRail,
86    /// Bus transportation.
87    Bus,
88    /// Ferry transportation.
89    Ferry,
90}
91
92impl TryFrom<u8> for RouteType {
93    type Error = String;
94
95    fn try_from(value: u8) -> Result<Self, Self::Error> {
96        match value {
97            0 => Ok(Self::LightRail),
98            1 => Ok(Self::HeavyRail),
99            2 => Ok(Self::CommuterRail),
100            3 => Ok(Self::Bus),
101            4 => Ok(Self::Ferry),
102            _ => Err(format!("invalid route type value: {}", value)),
103        }
104    }
105}
106
107impl From<RouteType> for u8 {
108    fn from(value: RouteType) -> u8 {
109        match value {
110            RouteType::LightRail => 0,
111            RouteType::HeavyRail => 1,
112            RouteType::CommuterRail => 2,
113            RouteType::Bus => 3,
114            RouteType::Ferry => 4,
115        }
116    }
117}
118
119/// Whether something is wheelchair accessible.
120#[derive(Debug, PartialEq, Clone, Copy, Deserialize, Serialize)]
121#[serde(try_from = "u8")]
122#[serde(into = "u8")]
123pub enum WheelchairAccessible {
124    /// No information.
125    NoInfo,
126    /// Accessible.
127    Accessible,
128    /// Inaccessible.
129    Inaccessible,
130}
131
132impl TryFrom<u8> for WheelchairAccessible {
133    type Error = String;
134
135    fn try_from(value: u8) -> Result<Self, Self::Error> {
136        match value {
137            0 => Ok(Self::NoInfo),
138            1 => Ok(Self::Accessible),
139            2 => Ok(Self::Inaccessible),
140            _ => Err(format!("invalid wheelchair accessibility value: {}", value)),
141        }
142    }
143}
144
145impl From<WheelchairAccessible> for u8 {
146    fn from(value: WheelchairAccessible) -> Self {
147        match value {
148            WheelchairAccessible::NoInfo => 0,
149            WheelchairAccessible::Accessible => 1,
150            WheelchairAccessible::Inaccessible => 2,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    use rstest::*;
160
161    #[rstest]
162    #[case::zero(0, Ok(RouteType::LightRail))]
163    #[case::one(1, Ok(RouteType::HeavyRail))]
164    #[case::two(2, Ok(RouteType::CommuterRail))]
165    #[case::three(3, Ok(RouteType::Bus))]
166    #[case::four(4, Ok(RouteType::Ferry))]
167    #[case::invalid(5, Err("invalid route type value: 5".into()))]
168    fn test_route_type_try_from_u8(#[case] input: u8, #[case] expected: Result<RouteType, String>) {
169        assert_eq!(RouteType::try_from(input), expected);
170    }
171
172    #[rstest]
173    #[case::light_rail(RouteType::LightRail, 0)]
174    #[case::heavy_rail(RouteType::HeavyRail, 1)]
175    #[case::commuter_rail(RouteType::CommuterRail, 2)]
176    #[case::bus(RouteType::Bus, 3)]
177    #[case::ferry(RouteType::Ferry, 4)]
178    fn test_u8_from_route_type(#[case] input: RouteType, #[case] expected: u8) {
179        assert_eq!(u8::from(input), expected);
180    }
181
182    #[rstest]
183    #[case::zero(0, Ok(WheelchairAccessible::NoInfo))]
184    #[case::one(1, Ok(WheelchairAccessible::Accessible))]
185    #[case::two(2, Ok(WheelchairAccessible::Inaccessible))]
186    #[case::invalid(3, Err("invalid wheelchair accessibility value: 3".into()))]
187    fn test_wheelchair_accessible_try_from_u8(#[case] input: u8, #[case] expected: Result<WheelchairAccessible, String>) {
188        assert_eq!(WheelchairAccessible::try_from(input), expected);
189    }
190
191    #[rstest]
192    #[case::no_info(WheelchairAccessible::NoInfo, 0)]
193    #[case::accessible(WheelchairAccessible::Accessible, 1)]
194    #[case::inaccessible(WheelchairAccessible::Inaccessible, 2)]
195    fn test_u8_from_wheelchair_accessible(#[case] input: WheelchairAccessible, #[case] expected: u8) {
196        assert_eq!(u8::from(input), expected);
197    }
198}