Skip to main content

gtfs_structures/
enums.rs

1use serde::de::{Deserialize, Deserializer};
2use serde::ser::{Serialize, Serializer};
3
4/// All the objects type from the GTFS specification that this library reads
5#[derive(Debug, Serialize, Eq, PartialEq, Hash)]
6pub enum ObjectType {
7    /// [Agency] <https://gtfs.org/reference/static/#agencytxt>
8    Agency,
9    /// [Stop] <https://gtfs.org/reference/static/#stopstxt>
10    Stop,
11    /// [Route] <https://gtfs.org/reference/static/#routestxt>
12    Route,
13    /// [Trip] <https://gtfs.org/reference/static/#tripstxt>
14    Trip,
15    /// [Calendar] <https://gtfs.org/reference/static/#calendartxt>
16    Calendar,
17    /// [Shape] <https://gtfs.org/reference/static/#shapestxt>
18    Shape,
19    /// [FareAttribute] <https://gtfs.org/reference/static/#fare_rulestxt>
20    Fare,
21    /// [Pathway] <https://gtfs.org/schedule/reference/#pathwaystxt>
22    Pathway,
23}
24
25/// Describes the kind of [Stop]. See <https://gtfs.org/reference/static/#stopstxt> `location_type`
26#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
27pub enum LocationType {
28    /// Stop (or Platform). A location where passengers board or disembark from a transit vehicle. Is called a platform when defined within a parent_station
29    #[default]
30    StopPoint,
31    /// Station. A physical structure or area that contains one or more platform
32    StopArea,
33    /// A location where passengers can enter or exit a station from the street. If an entrance/exit belongs to multiple stations, it can be linked by pathways to both, but the data provider must pick one of them as parent
34    StationEntrance,
35    /// A location within a station, not matching any other [Stop::location_type], which can be used to link together pathways define in pathways.txt.
36    GenericNode,
37    /// A specific location on a platform, where passengers can board and/or alight vehicles
38    BoardingArea,
39    /// An unknown value
40    Unknown(i16),
41}
42
43fn serialize_i16_as_str<S: Serializer>(s: S, value: i16) -> Result<S::Ok, S::Error> {
44    s.serialize_str(&value.to_string())
45}
46impl<'de> Deserialize<'de> for LocationType {
47    fn deserialize<D>(deserializer: D) -> Result<LocationType, D::Error>
48    where
49        D: Deserializer<'de>,
50    {
51        let s = <&str>::deserialize(deserializer)?;
52        Ok(match s {
53            "" | "0" => LocationType::StopPoint,
54            "1" => LocationType::StopArea,
55            "2" => LocationType::StationEntrance,
56            "3" => LocationType::GenericNode,
57            "4" => LocationType::BoardingArea,
58            s => LocationType::Unknown(s.parse().map_err(|_| {
59                serde::de::Error::custom(format!(
60                    "invalid value for LocationType, must be an integer: {s}"
61                ))
62            })?),
63        })
64    }
65}
66
67impl Serialize for LocationType {
68    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: Serializer,
71    {
72        // Note: for extended route type, we might loose the initial precise route type
73        serialize_i16_as_str(
74            serializer,
75            match self {
76                LocationType::StopPoint => 0,
77                LocationType::StopArea => 1,
78                LocationType::StationEntrance => 2,
79                LocationType::GenericNode => 3,
80                LocationType::BoardingArea => 4,
81                LocationType::Unknown(i) => *i,
82            },
83        )
84    }
85}
86
87/// Describes the kind of [Route]. See <https://gtfs.org/reference/static/#routestxt> `route_type`
88///
89/// -ome route types are extended GTFS (<https://developers.google.com/transit/gtfs/reference/extended-route-types)>
90#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
91pub enum RouteType {
92    /// Tram, Streetcar, Light rail. Any light rail or street level system within a metropolitan area
93    Tramway,
94    /// Tram, Streetcar, Light rail. Any light rail or street level system within a metropolitan area
95    Subway,
96    /// Used for intercity or long-distance travel
97    Rail,
98    /// Used for short- and long-distance bus routes
99    #[default]
100    Bus,
101    /// Used for short- and long-distance boat service
102    Ferry,
103    /// Used for street-level rail cars where the cable runs beneath the vehicle, e.g., cable car in San Francisco
104    CableCar,
105    /// Aerial lift, suspended cable car (e.g., gondola lift, aerial tramway). Cable transport where cabins, cars, gondolas or open chairs are suspended by means of one or more cables
106    Gondola,
107    /// Any rail system designed for steep inclines
108    Funicular,
109    /// (extended) Used for intercity bus services
110    Coach,
111    /// (extended) Airplanes
112    Air,
113    /// (extended) Taxi, Cab
114    Taxi,
115    /// (extended) any other value
116    Other(i16),
117}
118
119impl<'de> Deserialize<'de> for RouteType {
120    fn deserialize<D>(deserializer: D) -> Result<RouteType, D::Error>
121    where
122        D: Deserializer<'de>,
123    {
124        let i = i16::deserialize(deserializer)?;
125
126        let hundreds = i / 100;
127        Ok(match (i, hundreds) {
128            (0, _) | (_, 9) => RouteType::Tramway,
129            (1, _) | (_, 4) => RouteType::Subway,
130            (2, _) | (_, 1) => RouteType::Rail,
131            (3, _) | (_, 7) | (_, 8) => RouteType::Bus,
132            (4, _) | (_, 10) | (_, 12) => RouteType::Ferry,
133            (5, _) => RouteType::CableCar,
134            (6, _) | (_, 13) => RouteType::Gondola,
135            (7, _) | (_, 14) => RouteType::Funicular,
136            (_, 2) => RouteType::Coach,
137            (_, 11) => RouteType::Air,
138            (_, 15) => RouteType::Taxi,
139            _ => RouteType::Other(i),
140        })
141    }
142}
143
144impl Serialize for RouteType {
145    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146    where
147        S: Serializer,
148    {
149        // Note: for extended route type, we might loose the initial precise route type
150        serializer.serialize_i16(match self {
151            RouteType::Tramway => 0,
152            RouteType::Subway => 1,
153            RouteType::Rail => 2,
154            RouteType::Bus => 3,
155            RouteType::Ferry => 4,
156            RouteType::CableCar => 5,
157            RouteType::Gondola => 6,
158            RouteType::Funicular => 7,
159            RouteType::Coach => 200,
160            RouteType::Air => 1100,
161            RouteType::Taxi => 1500,
162            RouteType::Other(i) => *i,
163        })
164    }
165}
166
167/// Describes if and how a traveller can board or alight the vehicle. See <https://gtfs.org/reference/static/#stop_timestxt> `pickup_type` and `dropoff_type`
168#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
169pub enum PickupDropOffType {
170    /// Regularly scheduled pickup or drop off (default when empty).
171    #[default]
172    Regular,
173    /// No pickup or drop off available.
174    NotAvailable,
175    /// Must phone agency to arrange pickup or drop off.
176    ArrangeByPhone,
177    /// Must coordinate with driver to arrange pickup or drop off.
178    CoordinateWithDriver,
179    /// An unknown value not in the specification
180    Unknown(i16),
181}
182
183impl<'de> Deserialize<'de> for PickupDropOffType {
184    fn deserialize<D>(deserializer: D) -> Result<PickupDropOffType, D::Error>
185    where
186        D: Deserializer<'de>,
187    {
188        let s = <&str>::deserialize(deserializer)?;
189        Ok(match s {
190            "" | "0" => PickupDropOffType::Regular,
191            "1" => PickupDropOffType::NotAvailable,
192            "2" => PickupDropOffType::ArrangeByPhone,
193            "3" => PickupDropOffType::CoordinateWithDriver,
194            s => PickupDropOffType::Unknown(s.parse().map_err(|_| {
195                serde::de::Error::custom(format!(
196                    "invalid value for PickupDropOffType, must be an integer: {s}"
197                ))
198            })?),
199        })
200    }
201}
202
203impl Serialize for PickupDropOffType {
204    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
205    where
206        S: Serializer,
207    {
208        // Note: for extended route type, we might loose the initial precise route type
209        serialize_i16_as_str(
210            serializer,
211            match self {
212                PickupDropOffType::Regular => 0,
213                PickupDropOffType::NotAvailable => 1,
214                PickupDropOffType::ArrangeByPhone => 2,
215                PickupDropOffType::CoordinateWithDriver => 3,
216                PickupDropOffType::Unknown(i) => *i,
217            },
218        )
219    }
220}
221
222/// Indicates whether a rider can board the transit vehicle anywhere along the vehicle’s travel path
223///
224/// Those values are only defined on <https://developers.google.com/transit/gtfs/reference#routestxt,> not on <https://gtfs.org/reference/static/#routestxt>
225#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
226pub enum ContinuousPickupDropOff {
227    /// Continuous stopping pickup or drop off.
228    Continuous,
229    /// No continuous stopping pickup or drop off (default when empty).
230    #[default]
231    NotAvailable,
232    /// Must phone agency to arrange continuous stopping pickup or drop off.
233    ArrangeByPhone,
234    /// Must coordinate with driver to arrange continuous stopping pickup or drop off.
235    CoordinateWithDriver,
236    /// An unknown value not in the specification
237    Unknown(i16),
238}
239
240impl Serialize for ContinuousPickupDropOff {
241    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242    where
243        S: Serializer,
244    {
245        // Note: for extended route type, we might loose the initial precise route type
246        serialize_i16_as_str(
247            serializer,
248            match self {
249                ContinuousPickupDropOff::Continuous => 0,
250                ContinuousPickupDropOff::NotAvailable => 1,
251                ContinuousPickupDropOff::ArrangeByPhone => 2,
252                ContinuousPickupDropOff::CoordinateWithDriver => 3,
253                ContinuousPickupDropOff::Unknown(i) => *i,
254            },
255        )
256    }
257}
258
259impl<'de> Deserialize<'de> for ContinuousPickupDropOff {
260    fn deserialize<D>(deserializer: D) -> Result<ContinuousPickupDropOff, D::Error>
261    where
262        D: Deserializer<'de>,
263    {
264        let s = <&str>::deserialize(deserializer)?;
265        Ok(match s {
266            "0" => ContinuousPickupDropOff::Continuous,
267            "" | "1" => ContinuousPickupDropOff::NotAvailable,
268            "2" => ContinuousPickupDropOff::ArrangeByPhone,
269            "3" => ContinuousPickupDropOff::CoordinateWithDriver,
270            s => ContinuousPickupDropOff::Unknown(s.parse().map_err(|_| {
271                serde::de::Error::custom(format!(
272                    "invalid value for ContinuousPickupDropOff, must be an integer: {s}"
273                ))
274            })?),
275        })
276    }
277}
278
279/// Describes if the stop time is exact or not. See <https://gtfs.org/reference/static/#stop_timestxt> `timepoint`
280#[derive(Debug, Default, Serialize, Copy, Clone, PartialEq, Eq, Hash)]
281pub enum TimepointType {
282    /// Times are considered approximate
283    #[serde(rename = "0")]
284    Approximate = 0,
285    /// Times are considered exact
286    #[default]
287    #[serde(rename = "1")]
288    Exact = 1,
289}
290
291impl<'de> Deserialize<'de> for TimepointType {
292    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293    where
294        D: Deserializer<'de>,
295    {
296        let s = <&str>::deserialize(deserializer)?;
297        match s {
298            "" | "1" => Ok(Self::Exact),
299            "0" => Ok(Self::Approximate),
300            v => Err(serde::de::Error::custom(format!(
301                "invalid value for timepoint: {v}"
302            ))),
303        }
304    }
305}
306
307/// Generic enum to define if a service (like wheelchair boarding) is available
308#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
309pub enum Availability {
310    /// No information if the service is available
311    #[default]
312    InformationNotAvailable,
313    /// The service is available
314    Available,
315    /// The service is not available
316    NotAvailable,
317    /// An unknown value not in the specification
318    Unknown(i16),
319}
320
321impl<'de> Deserialize<'de> for Availability {
322    fn deserialize<D>(deserializer: D) -> Result<Availability, D::Error>
323    where
324        D: Deserializer<'de>,
325    {
326        let s = <&str>::deserialize(deserializer)?;
327        Ok(match s {
328            "" | "0" => Availability::InformationNotAvailable,
329            "1" => Availability::Available,
330            "2" => Availability::NotAvailable,
331            s => Availability::Unknown(s.parse().map_err(|_| {
332                serde::de::Error::custom(format!(
333                    "invalid value for Availability, must be an integer: {s}"
334                ))
335            })?),
336        })
337    }
338}
339
340impl Serialize for Availability {
341    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
342    where
343        S: Serializer,
344    {
345        // Note: for extended route type, we might loose the initial precise route type
346        serialize_i16_as_str(
347            serializer,
348            match self {
349                Availability::InformationNotAvailable => 0,
350                Availability::Available => 1,
351                Availability::NotAvailable => 2,
352                Availability::Unknown(i) => *i,
353            },
354        )
355    }
356}
357
358/// Defines if a [CalendarDate] is added or deleted from a [Calendar]
359#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
360pub enum Exception {
361    /// There will be a service on that day
362    #[serde(rename = "1")]
363    Added,
364    /// There won’t be a service on that day
365    #[serde(rename = "2")]
366    Deleted,
367}
368
369/// Defines the direction of a [Trip], only for display, not for routing. See <https://gtfs.org/reference/static/#tripstxt> `direction_id`
370#[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq, Hash)]
371pub enum DirectionType {
372    /// Travel in one direction (e.g. outbound travel).
373    #[serde(rename = "0")]
374    Outbound,
375    /// Travel in the opposite direction (e.g. inbound travel).
376    #[serde(rename = "1")]
377    Inbound,
378}
379
380/// Is the [Trip] accessible with a bike. See <https://gtfs.org/reference/static/#tripstxt> `bikes_allowed`
381#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
382pub enum BikesAllowedType {
383    /// No bike information for the trip
384    #[default]
385    NoBikeInfo,
386    /// Vehicle being used on this particular trip can accommodate at least one bicycle
387    AtLeastOneBike,
388    /// No bicycles are allowed on this trip
389    NoBikesAllowed,
390    /// An unknown value not in the specification
391    Unknown(i16),
392}
393
394impl<'de> Deserialize<'de> for BikesAllowedType {
395    fn deserialize<D>(deserializer: D) -> Result<BikesAllowedType, D::Error>
396    where
397        D: Deserializer<'de>,
398    {
399        let s = <&str>::deserialize(deserializer)?;
400        Ok(match s {
401            "" | "0" => BikesAllowedType::NoBikeInfo,
402            "1" => BikesAllowedType::AtLeastOneBike,
403            "2" => BikesAllowedType::NoBikesAllowed,
404            s => BikesAllowedType::Unknown(s.parse().map_err(|_| {
405                serde::de::Error::custom(format!(
406                    "invalid value for BikeAllowedType, must be an integer: {s}"
407                ))
408            })?),
409        })
410    }
411}
412
413impl Serialize for BikesAllowedType {
414    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
415    where
416        S: Serializer,
417    {
418        // Note: for extended route type, we might loose the initial precise route type
419        serialize_i16_as_str(
420            serializer,
421            match self {
422                BikesAllowedType::NoBikeInfo => 0,
423                BikesAllowedType::AtLeastOneBike => 1,
424                BikesAllowedType::NoBikesAllowed => 2,
425                BikesAllowedType::Unknown(i) => *i,
426            },
427        )
428    }
429}
430
431/// Defines where a [FareAttribute] can be paid
432#[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq)]
433pub enum PaymentMethod {
434    /// Fare is paid on board
435    #[serde(rename = "0")]
436    Aboard,
437    /// Fare must be paid before boarding
438    #[serde(rename = "1")]
439    PreBoarding,
440}
441
442/// Defines if the [Frequency] is exact (the vehicle runs exactly every n minutes) or not
443#[derive(Debug, Serialize, Copy, Clone, PartialEq, Eq, Hash)]
444pub enum ExactTimes {
445    /// Frequency-based trips
446    FrequencyBased = 0,
447    /// Schedule-based trips with the exact same headway throughout the day.
448    ScheduleBased = 1,
449}
450
451impl<'de> Deserialize<'de> for ExactTimes {
452    fn deserialize<D>(deserializer: D) -> Result<ExactTimes, D::Error>
453    where
454        D: Deserializer<'de>,
455    {
456        let s = <&str>::deserialize(deserializer)?;
457        Ok(match s {
458            "" | "0" => ExactTimes::FrequencyBased,
459            "1" => ExactTimes::ScheduleBased,
460            &_ => {
461                return Err(serde::de::Error::custom(format!(
462                    "Invalid value `{s}`, expected 0 or 1"
463                )));
464            }
465        })
466    }
467}
468
469/// Defines how many transfers can be done with on [FareAttribute]
470#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
471pub enum Transfers {
472    /// Unlimited transfers are permitted
473    #[default]
474    Unlimited,
475    /// No transfers permitted on this fare
476    NoTransfer,
477    /// Riders may transfer once
478    UniqueTransfer,
479    ///Riders may transfer twice
480    TwoTransfers,
481    /// Other transfer values
482    Other(i16),
483}
484
485impl<'de> Deserialize<'de> for Transfers {
486    fn deserialize<D>(deserializer: D) -> Result<Transfers, D::Error>
487    where
488        D: Deserializer<'de>,
489    {
490        let i = Option::<i16>::deserialize(deserializer)?;
491        Ok(match i {
492            Some(0) => Transfers::NoTransfer,
493            Some(1) => Transfers::UniqueTransfer,
494            Some(2) => Transfers::TwoTransfers,
495            Some(a) => Transfers::Other(a),
496            None => Transfers::default(),
497        })
498    }
499}
500
501impl Serialize for Transfers {
502    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
503    where
504        S: Serializer,
505    {
506        match self {
507            Transfers::NoTransfer => serialize_i16_as_str(serializer, 0),
508            Transfers::UniqueTransfer => serialize_i16_as_str(serializer, 1),
509            Transfers::TwoTransfers => serialize_i16_as_str(serializer, 2),
510            Transfers::Other(a) => serialize_i16_as_str(serializer, *a),
511            Transfers::Unlimited => serializer.serialize_none(),
512        }
513    }
514}
515/// Defines the type of a [StopTransfer]
516#[derive(Debug, Serialize, Default, Copy, Clone, PartialEq, Eq, Hash)]
517pub enum TransferType {
518    /// Recommended transfer point between routes
519    #[serde(rename = "0")]
520    #[default]
521    Recommended,
522    /// Departing vehicle waits for arriving one
523    #[serde(rename = "1")]
524    Timed,
525    /// Transfer requires a minimum amount of time between arrival and departure to ensure a connection.
526    #[serde(rename = "2")]
527    MinTime,
528    /// Transfer is not possible at this location
529    #[serde(rename = "3")]
530    Impossible,
531    /// Passengers can stay onboard the same vehicle to transfer from one trip to another
532    #[serde(rename = "4")]
533    StayOnBoard,
534    /// In-seat transfers aren't allowed between sequential trips.
535    /// The passenger must alight from the vehicle and re-board.
536    #[serde(rename = "5")]
537    MustAlight,
538}
539
540impl<'de> Deserialize<'de> for TransferType {
541    fn deserialize<D>(deserializer: D) -> Result<TransferType, D::Error>
542    where
543        D: Deserializer<'de>,
544    {
545        let s = <&str>::deserialize(deserializer)?;
546        Ok(match s {
547            "" | "0" => TransferType::Recommended,
548            "1" => TransferType::Timed,
549            "2" => TransferType::MinTime,
550            "3" => TransferType::Impossible,
551            "4" => TransferType::StayOnBoard,
552            "5" => TransferType::MustAlight,
553            s => {
554                return Err(serde::de::Error::custom(format!(
555                    "Invalid value `{s}`, expected 0, 1, 2, 3, 4, 5"
556                )));
557            }
558        })
559    }
560}
561
562/// Type of pathway between [from_stop] and [to_stop]
563#[derive(Debug, Serialize, Deserialize, Default, Copy, Clone, PartialEq, Eq, Hash)]
564pub enum PathwayMode {
565    /// A walkway
566    #[serde(rename = "1")]
567    #[default]
568    Walkway,
569    /// Stairs
570    #[serde(rename = "2")]
571    Stairs,
572    /// Moving sidewalk / travelator
573    #[serde(rename = "3")]
574    MovingSidewalk,
575    /// Escalator
576    #[serde(rename = "4")]
577    Escalator,
578    /// Elevator
579    #[serde(rename = "5")]
580    Elevator,
581    /// A pathway that crosses into an area of the station where a
582    /// proof of payment is required (usually via a physical payment gate)
583    #[serde(rename = "6")]
584    FareGate,
585    /// Indicates a pathway exiting an area where proof-of-payment is required
586    /// into an area where proof-of-payment is no longer required.
587    #[serde(rename = "7")]
588    ExitGate,
589}
590
591/// Indicates in which direction the pathway can be used
592#[derive(Debug, Serialize, Deserialize, Default, Copy, Clone, PartialEq, Eq, Hash)]
593pub enum PathwayDirectionType {
594    /// Unidirectional pathway, it can only be used from [from_stop_id] to [to_stop_id].
595    #[serde(rename = "0")]
596    #[default]
597    Unidirectional,
598    /// Bidirectional pathway, it can be used in the two directions.
599    #[serde(rename = "1")]
600    Bidirectional,
601}
602
603/// Defines the type of a [FareMedia]
604#[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq)]
605pub enum FareMediaType {
606    /// Used when there is no fare media involved in purchasing or validating a fare product
607    #[serde(rename = "0")]
608    None,
609    /// Physical paper ticket
610    #[serde(rename = "1")]
611    PhysicalPaperTicket,
612    /// Physical transit card
613    #[serde(rename = "2")]
614    PhysicalTransitCard,
615    /// cEMV (contactless Europay, Mastercard and Visa)
616    #[serde(rename = "3")]
617    CEmv,
618    /// Mobile app
619    #[serde(rename = "4")]
620    MobileApp,
621}
622
623/// Specifies if an entry in rider_categories.txt should be considered the default category
624#[derive(Debug, Serialize, Copy, Clone, PartialEq, Eq, Hash)]
625pub enum DefaultFareCategory {
626    /// Category is not considered the default.
627    NotDefault = 0,
628    /// Category is considered the default one.
629    Default = 1,
630}
631
632impl<'de> Deserialize<'de> for DefaultFareCategory {
633    fn deserialize<D>(deserializer: D) -> Result<DefaultFareCategory, D::Error>
634    where
635        D: Deserializer<'de>,
636    {
637        let s = <&str>::deserialize(deserializer)?;
638        Ok(match s {
639            "" | "0" => DefaultFareCategory::NotDefault,
640            "1" => DefaultFareCategory::Default,
641            &_ => {
642                return Err(serde::de::Error::custom(format!(
643                    "Invalid value `{s}`, expected 0 or 1"
644                )));
645            }
646        })
647    }
648}
649
650/// Specifies whether tickets can be bought for this item
651#[derive(Debug, Default, Deserialize, Serialize, Copy, Clone, PartialEq, Eq)]
652pub enum TicketingType {
653    /// If a ticketing_deep_link_id is set, tickets are available
654    #[default]
655    #[serde(rename = "0")]
656    Available,
657    /// Tickets are unavailable
658    #[serde(rename = "1")]
659    Unavailable,
660}