railway_api/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod error;
4pub use error::*;
5
6use rcore::Requester;
7
8// XXX: Ugly that this needs to be hard-coded; most of the enum is general over the requester, but the Motis one is not.
9type R = rcore::ReqwestRequester;
10
11// Note: The derive macro automatically generates the following:
12// - `RailwayProviderType`: The enum equivalent to this enum without the data for each enum variant. It furthermore generates `FromStr` and `Display` for it. There is also a `RailwayProviderType::variants` which lists all providers that are available.
13// - `RailwayProvider::new`: Construct `RailwayProvider` from `RailwayProviderType` and `R: Requester` using specified `constructor`.
14// - `impl<R: Requester> Provider<R> for RailwayProvider<R>`: Using error `BoxedError` and directly passing through the wrapped type.
15// It also looks for `#[cfg(...)]` and enables the specific code only if requested.
16/// A wrapper around all implemented [`Provider`s](rcore::Provider).
17///
18/// Note that the variants of this enum depend on the features activated via the API.
19#[derive(Clone, rapi_derive::ProviderApi)]
20pub enum RailwayProvider {
21    // Hafas
22    #[cfg(feature = "vbb-provider")]
23    #[provider(hafas = rhafas::profile::vbb::VbbProfile {})]
24    Vbb(rhafas::client::HafasClient<R>),
25    #[cfg(feature = "oebb-provider")]
26    #[provider(hafas = rhafas::profile::oebb::OebbProfile {})]
27    Oebb(rhafas::client::HafasClient<R>),
28    #[cfg(feature = "nahsh-provider")]
29    #[provider(hafas = rhafas::profile::nahsh::NahSHProfile {})]
30    NahSh(rhafas::client::HafasClient<R>),
31    #[cfg(feature = "vvt-provider")]
32    #[provider(hafas = rhafas::profile::vvt::VvtProfile {})]
33    Vvt(rhafas::client::HafasClient<R>),
34    #[cfg(feature = "pkp-provider")]
35    #[provider(hafas = rhafas::profile::pkp::PkpProfile {})]
36    Pkp(rhafas::client::HafasClient<R>),
37    #[cfg(feature = "irish-rail-provider")]
38    #[provider(hafas = rhafas::profile::irish_rail::IrishRailProfile {})]
39    IrishRail(rhafas::client::HafasClient<R>),
40    #[cfg(feature = "mobiliteit-lu-provider")]
41    #[provider(hafas = rhafas::profile::mobiliteit_lu::MobiliteitLuProfile {})]
42    MobiliteitLu(rhafas::client::HafasClient<R>),
43    #[cfg(feature = "dart-provider")]
44    #[provider(hafas = rhafas::profile::dart::DartProfile {})]
45    Dart(rhafas::client::HafasClient<R>),
46    #[cfg(feature = "rmv-provider")]
47    #[provider(hafas = rhafas::profile::rmv::RmvProfile {})]
48    Rmv(rhafas::client::HafasClient<R>),
49    #[cfg(feature = "insa-provider")]
50    #[provider(hafas = rhafas::profile::insa::InsaProfile {})]
51    Insa(rhafas::client::HafasClient<R>),
52    #[cfg(feature = "cmta-provider")]
53    #[provider(hafas = rhafas::profile::cmta::CmtaProfile {})]
54    Cmta(rhafas::client::HafasClient<R>),
55    #[cfg(feature = "sbahn-muenchen-provider")]
56    #[provider(hafas = rhafas::profile::sbahn_muenchen::SBahnMuenchenProfile {})]
57    SBahnMuenchen(rhafas::client::HafasClient<R>),
58    #[cfg(feature = "saarvv-provider")]
59    #[provider(hafas = rhafas::profile::saarvv::SaarvvProfile {})]
60    Saarvv(rhafas::client::HafasClient<R>),
61    #[cfg(feature = "cfl-provider")]
62    #[provider(hafas = rhafas::profile::cfl::CflProfile {})]
63    Cfl(rhafas::client::HafasClient<R>),
64    #[cfg(feature = "nvv-provider")]
65    #[provider(hafas = rhafas::profile::nvv::NvvProfile {})]
66    Nvv(rhafas::client::HafasClient<R>),
67    #[cfg(feature = "mobil-nrw-provider")]
68    #[provider(hafas = rhafas::profile::mobil_nrw::MobilNrwProfile {})]
69    MobilNrw(rhafas::client::HafasClient<R>),
70    #[cfg(feature = "vsn-provider")]
71    #[provider(hafas = rhafas::profile::vsn::VsnProfile {})]
72    Vsn(rhafas::client::HafasClient<R>),
73    #[cfg(feature = "vgi-provider")]
74    #[provider(hafas = rhafas::profile::vgi::VgiProfile {})]
75    Vgi(rhafas::client::HafasClient<R>),
76    #[cfg(feature = "vbn-provider")]
77    #[provider(hafas = rhafas::profile::vbn::VbnProfile {})]
78    Vbn(rhafas::client::HafasClient<R>),
79    #[cfg(feature = "vrn-provider")]
80    #[provider(hafas = rhafas::profile::vrn::VrnProfile {})]
81    Vrn(rhafas::client::HafasClient<R>),
82    #[cfg(feature = "rsag-provider")]
83    #[provider(hafas = rhafas::profile::rsag::RsagProfile {})]
84    Rsag(rhafas::client::HafasClient<R>),
85    #[cfg(feature = "vmt-provider")]
86    #[provider(hafas = rhafas::profile::vmt::VmtProfile {})]
87    Vmt(rhafas::client::HafasClient<R>),
88    #[cfg(feature = "vos-provider")]
89    #[provider(hafas = rhafas::profile::vos::VosProfile {})]
90    Vos(rhafas::client::HafasClient<R>),
91    #[cfg(feature = "avv-provider")]
92    #[provider(hafas = rhafas::profile::avv::AvvProfile {})]
93    Avv(rhafas::client::HafasClient<R>),
94    #[cfg(feature = "rejseplanen-provider")]
95    #[provider(hafas = rhafas::profile::rejseplanen::RejseplanenProfile {})]
96    Rejseplanen(rhafas::client::HafasClient<R>),
97    #[cfg(feature = "ooevv-provider")]
98    #[provider(hafas = rhafas::profile::ooevv::OoevvProfile {})]
99    Ooevv(rhafas::client::HafasClient<R>),
100    #[cfg(feature = "salzburg-provider")]
101    #[provider(hafas = rhafas::profile::salzburg::SalzburgProfile {})]
102    Salzburg(rhafas::client::HafasClient<R>),
103    #[cfg(feature = "verbundlinie-provider")]
104    #[provider(hafas = rhafas::profile::verbundlinie::VerbundlinieProfile {})]
105    Verbundlinie(rhafas::client::HafasClient<R>),
106    #[cfg(feature = "svv-provider")]
107    #[provider(hafas = rhafas::profile::svv::SvvProfile {})]
108    Svv(rhafas::client::HafasClient<R>),
109    #[cfg(feature = "vor-provider")]
110    #[provider(hafas = rhafas::profile::vor::VorProfile {})]
111    Vor(rhafas::client::HafasClient<R>),
112    #[cfg(feature = "vkg-provider")]
113    #[provider(hafas = rhafas::profile::vkg::VkgProfile {})]
114    Vkg(rhafas::client::HafasClient<R>),
115    #[cfg(feature = "vvv-provider")]
116    #[provider(hafas = rhafas::profile::vvv::VvvProfile {})]
117    Vvv(rhafas::client::HafasClient<R>),
118    #[cfg(feature = "bls-provider")]
119    #[provider(hafas = rhafas::profile::bls::BlsProfile {})]
120    Bls(rhafas::client::HafasClient<R>),
121    #[cfg(feature = "kvb-provider")]
122    #[provider(hafas = rhafas::profile::kvb::KvbProfile {})]
123    Kvb(rhafas::client::HafasClient<R>),
124    #[cfg(feature = "bart-provider")]
125    #[provider(hafas = rhafas::profile::bart::BartProfile {})]
126    Bart(rhafas::client::HafasClient<R>),
127    #[cfg(feature = "ivb-provider")]
128    #[provider(hafas = rhafas::profile::ivb::IvbProfile {})]
129    Ivb(rhafas::client::HafasClient<R>),
130    #[cfg(feature = "resrobot-provider")]
131    #[provider(hafas = rhafas::profile::resrobot::ResrobotProfile {})]
132    Resrobot(rhafas::client::HafasClient<R>),
133
134    // Search.ch
135    #[cfg(feature = "search-ch-provider")]
136    #[provider(constructor = rsearchch::SearchChClient::new)]
137    SearchCh(rsearchch::SearchChClient<R>),
138
139    // Transitous
140    #[cfg(feature = "transitous-provider")]
141    #[provider(constructor = |r| rmotis::MotisClient::new(url::Url::parse(rmotis::TRANSITOUS_URL).expect("Failed to parse TRANSITOUS_URL"), r))]
142    Transitous(rmotis::MotisClient),
143
144    // DB Movas
145    #[cfg(feature = "db-provider")]
146    #[provider(constructor = rdb::DbMovasClient::new)]
147    Db(rdb::DbMovasClient<R>),
148}
149
150#[cfg(test)]
151mod api_test {
152    use super::*;
153
154    #[test]
155    #[cfg(feature = "db-provider")]
156    fn macro_creates_enum_types() {
157        let _ = RailwayProviderType::Db;
158    }
159
160    #[test]
161    #[cfg(feature = "db-provider")]
162    fn macro_creates_enum_type_variants() {
163        let list = RailwayProviderType::variants();
164        assert!(list.len() >= 1);
165    }
166
167    #[test]
168    #[cfg(feature = "db-provider")]
169    fn macro_creates_enum_from_to_string() {
170        use std::str::FromStr;
171        assert_eq!(
172            RailwayProviderType::from_str("db").unwrap(),
173            RailwayProviderType::Db
174        );
175        assert_eq!(RailwayProviderType::Db.to_string(), "db".to_owned());
176    }
177
178    #[test]
179    #[cfg(feature = "db-provider")]
180    fn macro_creates_constructor() {
181        let _ = RailwayProvider::new(
182            RailwayProviderType::Db,
183            rcore::ReqwestRequesterBuilder::default(),
184        );
185    }
186
187    #[test]
188    #[cfg(feature = "db-provider")]
189    fn macro_implements_provider() {
190        use rcore::Provider;
191
192        let client = RailwayProvider::new(
193            RailwayProviderType::Db,
194            rcore::ReqwestRequesterBuilder::default(),
195        );
196        let _ = Box::new(client) as Box<dyn Provider<rcore::ReqwestRequester, Error = BoxedError>>;
197    }
198
199    #[test]
200    #[cfg(feature = "all-providers")]
201    fn readme_supported_up_to_date() {
202        use std::collections::HashSet;
203
204        let supported: HashSet<_> = RailwayProviderType::variants()
205            .into_iter()
206            .map(|p| p.to_string().to_lowercase().replace(&['-', '_'], ""))
207            .collect();
208        let readme = include_str!("../../README.md").lines();
209
210        let entries: HashSet<_> = readme
211            // Go to profiles-section.
212            .skip_while(|l| l != &"## Profiles")
213            // Go to first table.
214            .skip_while(|l| !l.starts_with("|"))
215            // Skip header.
216            .skip(2)
217            // Take rows
218            .take_while(|l| l.starts_with("|"))
219            // Take first column
220            .flat_map(|r| r.split('|').nth(1))
221            .map(|s| s.trim().to_lowercase().replace(&['-', '_', '.'], ""))
222            .collect();
223
224        let extra: HashSet<_> = entries.difference(&supported).collect();
225        let missing: HashSet<_> = supported.difference(&entries).collect();
226
227        if !(extra.is_empty() && missing.is_empty()) {
228            println!("Extra Providers: {:#?}", extra);
229            println!("Missing Providers: {:#?}", missing);
230            panic!("Mismatched README and available providers");
231        }
232    }
233
234    #[test]
235    #[cfg(feature = "all-providers")]
236    fn hafas_all_providers_up_to_date() {
237        use serde::Deserialize;
238        use std::collections::{HashMap, HashSet};
239        use toml;
240
241        #[derive(Deserialize)]
242        struct CrateCargo {
243            features: HashMap<String, Vec<String>>,
244        }
245
246        let hafas_cargo = include_str!("../../railway-provider-hafas/Cargo.toml");
247        let cargo: CrateCargo = toml::from_str(hafas_cargo).unwrap();
248
249        let hafas_supported_profiles: HashSet<_> = cargo
250            .features
251            .keys()
252            .filter_map(|s| s.strip_suffix("-profile"))
253            .map(|p| p.to_string().to_lowercase().replace(['-', '_'], ""))
254            .collect();
255        let hafas_all_profiles: HashSet<_> = cargo.features["all-profiles"]
256            .iter()
257            .map(|p| {
258                p.strip_suffix("-profile")
259                    .unwrap_or_default()
260                    .to_string()
261                    .to_lowercase()
262                    .replace(['-', '_'], "")
263            })
264            .collect();
265
266        let supported: HashSet<_> = RailwayProviderType::variants()
267            .iter()
268            .map(|p| p.to_string().to_lowercase().replace(['-', '_'], ""))
269            .collect();
270
271        let supported_hafas = supported
272            .intersection(&hafas_supported_profiles)
273            .map(|s| s.to_string())
274            .collect();
275
276        let extra: HashSet<_> = hafas_all_profiles.difference(&supported_hafas).collect();
277        let missing: HashSet<_> = supported_hafas.difference(&hafas_all_profiles).collect();
278
279        if !(extra.is_empty() && missing.is_empty()) {
280            println!("Extra Providers: {:#?}", extra);
281            println!("Missing Providers: {:#?}", missing);
282            panic!("Mismatched README and available providers");
283        }
284    }
285}