1use duplicate::duplicate_item;
23use enum_as_inner::EnumAsInner;
24use serde::{Deserialize, Deserializer, Serialize};
25use std::collections::HashMap;
26use std::ops::{Deref, DerefMut};
27use thiserror::Error;
28
29#[derive(Error, Debug)]
30#[non_exhaustive]
31pub enum Error {
32 #[cfg(feature = "reqwest_get")]
33 #[error("reqwest error: {0:?}")]
34 Reqwest(#[from] reqwest::Error),
35 #[cfg(feature = "surf_get")]
36 #[error("surf error: {0:?}")]
37 Surf(surf::Error),
38 #[cfg(feature = "ureq_get")]
39 #[error("ureq error: {0:?}")]
40 Ureq(#[from] ureq::Error),
41
42 #[error("decoding error: {0:?}")]
43 Decode(#[from] serde_json::error::Error),
44
45 #[error("No node {0}")]
46 NoNode(ID),
47 #[error("{0} not {1}")]
48 IncorrectType(ID, &'static str),
49
50 #[error("unknown error")]
51 Unknown,
52}
53
54#[cfg(feature = "surf_get")]
55impl From<surf::Error> for Error {
56 fn from(err: surf::Error) -> Self {
57 Self::Surf(err)
58 }
59}
60
61pub type Result<T, E = Error> = std::result::Result<T, E>;
62
63pub type ID = u16;
64
65#[derive(Clone, Debug, Serialize, Deserialize)]
66pub struct GatelogueData {
67 pub timestamp: String,
68 pub version: u64,
69 pub nodes: HashMap<ID, Node>,
70}
71impl GatelogueData {
72 #[cfg(feature = "reqwest_get")]
73 pub async fn reqwest_get_with_sources() -> Result<Self> {
74 let bytes = reqwest::get(
75 "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data.json",
76 )
77 .await?
78 .bytes()
79 .await?;
80 Ok(serde_json::from_slice(&bytes)?)
81 }
82 #[cfg(feature = "reqwest_get")]
83 pub async fn reqwest_get_no_sources() -> Result<Self> {
84 let bytes = reqwest::get("https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data_no_sources.json").await?.bytes().await?;
85 Ok(serde_json::from_slice(&bytes)?)
86 }
87 #[cfg(feature = "surf_get")]
88 pub async fn surf_get_with_sources() -> Result<Self> {
89 let bytes = surf::get(
90 "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data.json",
91 )
92 .recv_bytes()
93 .await?;
94 Ok(serde_json::from_slice(&bytes)?)
95 }
96 #[cfg(feature = "surf_get")]
97 pub async fn surf_get_no_sources() -> Result<Self> {
98 let bytes = surf::get("https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data_no_sources.json").recv_bytes().await?;
99 Ok(serde_json::from_slice(&bytes)?)
100 }
101 #[cfg(feature = "ureq_get")]
102 pub fn ureq_get_with_sources() -> Result<Self> {
103 let reader = ureq::get(
104 "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data.json",
105 )
106 .call()?
107 .into_body()
108 .into_reader();
109 Ok(serde_json::from_reader(reader)?)
110 }
111 #[cfg(feature = "ureq_get")]
112 pub fn ureq_get_no_sources() -> Result<Self> {
113 let reader = ureq::get("https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data_no_sources.json").call()?.into_body().into_reader();
114 Ok(serde_json::from_reader(reader)?)
115 }
116
117 #[duplicate_item(
118 nodes_ as_node Ty;
119 [air_airlines] [as_air_airline] [AirAirline];
120 [air_airports] [as_air_airport] [AirAirport];
121 [air_flights] [as_air_flight] [AirFlight];
122 [air_gates] [as_air_gate] [AirGate];
123 [bus_companies] [as_bus_company] [BusCompany];
124 [bus_lines] [as_bus_line] [BusLine];
125 [bus_stops] [as_bus_stop] [BusStop];
126 [rail_companies] [as_rail_company] [RailCompany];
127 [rail_lines] [as_rail_line] [RailLine];
128 [rail_stations] [as_rail_station] [RailStation];
129 [sea_companies] [as_sea_company] [SeaCompany];
130 [sea_lines] [as_sea_line] [SeaLine];
131 [sea_stops] [as_sea_stop] [SeaStop];
132 [spawn_warps] [as_spawn_warp] [SpawnWarp];
133 [towns] [as_town] [Town];
134 )]
135 pub fn nodes_(&self) -> impl Iterator<Item = &Ty> {
136 self.nodes.values().filter_map(|a| a.as_node())
137 }
138
139 #[duplicate_item(
140 nodes_mut as_node_mut Ty;
141 [air_airlines_mut] [as_air_airline_mut] [AirAirline];
142 [air_airports_mut] [as_air_airport_mut] [AirAirport];
143 [air_flights_mut] [as_air_flight_mut] [AirFlight];
144 [air_gates_mut] [as_air_gate_mut] [AirGate];
145 [bus_companies_mut] [as_bus_company_mut] [BusCompany];
146 [bus_lines_mut] [as_bus_line_mut] [BusLine];
147 [bus_stops_mut] [as_bus_stop_mut] [BusStop];
148 [rail_companies_mut] [as_rail_company_mut] [RailCompany];
149 [rail_lines_mut] [as_rail_line_mut] [RailLine];
150 [rail_stations_mut] [as_rail_station_mut] [RailStation];
151 [sea_companies_mut] [as_sea_company_mut] [SeaCompany];
152 [sea_lines_mut] [as_sea_line_mut] [SeaLine];
153 [sea_stops_mut] [as_sea_stop_mut] [SeaStop];
154 [spawn_warps_mut] [as_spawn_warp_mut] [SpawnWarp];
155 [towns_mut] [as_town_mut] [Town];
156 )]
157 pub fn nodes_mut(&mut self) -> impl Iterator<Item = &mut Ty> {
158 self.nodes.values_mut().filter_map(|a| a.as_node_mut())
159 }
160
161 #[duplicate_item(
162 get_node as_node Ty;
163 [get_air_airline] [as_air_airline] [AirAirline];
164 [get_air_airport] [as_air_airport] [AirAirport];
165 [get_air_flight] [as_air_flight] [AirFlight];
166 [get_air_gate] [as_air_gate] [AirGate];
167 [get_bus_company] [as_bus_company] [BusCompany];
168 [get_bus_line] [as_bus_line] [BusLine];
169 [get_bus_stop] [as_bus_stop] [BusStop];
170 [get_rail_company] [as_rail_company] [RailCompany];
171 [get_rail_line] [as_rail_line] [RailLine];
172 [get_rail_station] [as_rail_station] [RailStation];
173 [get_sea_company] [as_sea_company] [SeaCompany];
174 [get_sea_line] [as_sea_line] [SeaLine];
175 [get_sea_stop] [as_sea_stop] [SeaStop];
176 [get_spawn_warp] [as_spawn_warp] [SpawnWarp];
177 [get_town] [as_town] [Town];
178 )]
179 pub fn get_node(&self, id: ID) -> Result<&Ty> {
180 self.nodes
181 .get(&id)
182 .ok_or(Error::NoNode(id))?
183 .as_node()
184 .ok_or(Error::IncorrectType(id, stringify!(Ty)))
185 }
186
187 #[duplicate_item(
188 get_node_mut as_node_mut Ty;
189 [get_air_airline_mut] [as_air_airline_mut] [AirAirline];
190 [get_air_airport_mut] [as_air_airport_mut] [AirAirport];
191 [get_air_flight_mut] [as_air_flight_mut] [AirFlight];
192 [get_air_gate_mut] [as_air_gate_mut] [AirGate];
193 [get_bus_company_mut] [as_bus_company_mut] [BusCompany];
194 [get_bus_line_mut] [as_bus_line_mut] [BusLine];
195 [get_bus_stop_mut] [as_bus_stop_mut] [BusStop];
196 [get_rail_company_mut] [as_rail_company_mut] [RailCompany];
197 [get_rail_line_mut] [as_rail_line_mut] [RailLine];
198 [get_rail_station_mut] [as_rail_station_mut] [RailStation];
199 [get_sea_company_mut] [as_sea_company_mut] [SeaCompany];
200 [get_sea_line_mut] [as_sea_line_mut] [SeaLine];
201 [get_sea_stop_mut] [as_sea_stop_mut] [SeaStop];
202 [get_spawn_warp_mut] [as_spawn_warp_mut] [SpawnWarp];
203 [get_town_mut] [as_town_mut] [Town];
204 )]
205 pub fn get_node_mut(&mut self, id: ID) -> Result<&mut Ty> {
206 self.nodes
207 .get_mut(&id)
208 .ok_or(Error::NoNode(id))?
209 .as_node_mut()
210 .ok_or(Error::IncorrectType(id, stringify!(Ty)))
211 }
212}
213
214#[derive(Clone, Debug, Serialize, Deserialize)]
215#[serde(untagged)]
216pub enum Sourced<T> {
217 Unsourced(T),
218 Sourced { v: T, s: Vec<String> },
219}
220
221impl<T> Deref for Sourced<T> {
222 type Target = T;
223
224 fn deref(&self) -> &Self::Target {
225 match self {
226 Self::Unsourced(v) | Self::Sourced { v, .. } => v,
227 }
228 }
229}
230impl<T> DerefMut for Sourced<T> {
231 fn deref_mut(&mut self) -> &mut Self::Target {
232 match self {
233 Self::Unsourced(v) | Self::Sourced { v, .. } => v,
234 }
235 }
236}
237
238#[derive(Clone, Debug, Serialize, Deserialize)]
239pub struct NodeCommon {
240 pub i: ID,
241 pub source: Vec<String>,
242}
243
244#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
245pub enum World {
246 Old,
247 New,
248 Space,
249}
250
251#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
252pub struct Proximity {
253 pub distance: Option<f64>,
254 pub explicit: bool,
255}
256
257#[derive(Clone, Debug, Serialize, Deserialize)]
258pub struct LocatedNodeCommon {
259 pub i: ID,
260 pub source: Vec<String>,
261 pub world: Option<Sourced<World>>,
262 pub coordinates: Option<Sourced<(f64, f64)>>,
263 #[serde(deserialize_with = "deserialise_proximity")]
264 pub proximity: HashMap<ID, Sourced<Proximity>>,
265 pub shared_facility: Vec<Sourced<ID>>,
266}
267
268#[derive(Clone, Debug, Serialize, Deserialize, EnumAsInner)]
269#[serde(tag = "type")]
270pub enum Node {
271 AirAirline(AirAirline),
272 AirAirport(AirAirport),
273 AirFlight(AirFlight),
274 AirGate(AirGate),
275 BusCompany(BusCompany),
276 BusLine(BusLine),
277 BusStop(BusStop),
278 SeaCompany(SeaCompany),
279 SeaLine(SeaLine),
280 SeaStop(SeaStop),
281 RailCompany(RailCompany),
282 RailLine(RailLine),
283 RailStation(RailStation),
284 SpawnWarp(SpawnWarp),
285 Town(Town),
286}
287
288#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
289pub enum AirMode {
290 #[serde(rename = "helicopter")]
291 Helicopter,
292 #[serde(rename = "seaplane")]
293 Seaplane,
294 #[serde(rename = "warp plane")]
295 WarpPlane,
296 #[serde(rename = "traincarts plane")]
297 TrainCartsPlane,
298}
299
300#[derive(Clone, Debug, Serialize, Deserialize)]
301pub struct AirAirline {
302 pub name: String,
303 pub link: Option<Sourced<String>>,
304 pub flights: Vec<Sourced<ID>>,
305 pub gates: Vec<Sourced<ID>>,
306 #[serde(flatten)]
307 pub common: NodeCommon,
308}
309
310#[derive(Clone, Debug, Serialize, Deserialize)]
311pub struct AirAirport {
312 pub code: String,
313 pub name: Option<Sourced<String>>,
314 pub link: Option<Sourced<String>>,
315 pub modes: Option<Sourced<Vec<AirMode>>>,
316 pub gates: Vec<Sourced<ID>>,
317 #[serde(flatten)]
318 pub common: LocatedNodeCommon,
319}
320
321#[derive(Clone, Debug, Serialize, Deserialize)]
322pub struct AirFlight {
323 pub codes: Vec<String>,
324 pub mode: Option<Sourced<AirMode>>,
325 pub airline: Sourced<ID>,
326 pub gates: Vec<Sourced<ID>>,
327 #[serde(flatten)]
328 pub common: NodeCommon,
329}
330
331#[derive(Clone, Debug, Serialize, Deserialize)]
332pub struct AirGate {
333 pub codes: Option<String>,
334 pub size: Option<Sourced<String>>,
335 pub airline: Option<Sourced<ID>>,
336 pub airport: Sourced<ID>,
337 pub flights: Vec<Sourced<ID>>,
338 #[serde(flatten)]
339 pub common: NodeCommon,
340}
341
342#[derive(Clone, Debug, Serialize, Deserialize)]
343pub struct BusCompany {
344 pub name: String,
345 pub lines: Vec<Sourced<ID>>,
346 pub stops: Vec<Sourced<ID>>,
347 pub local: bool,
348 #[serde(flatten)]
349 pub common: NodeCommon,
350}
351
352#[derive(Clone, Debug, Serialize, Deserialize)]
353pub struct BusLine {
354 pub code: String,
355 pub name: Option<Sourced<String>>,
356 pub colour: Option<Sourced<String>>,
357 pub company: Sourced<ID>,
358 pub ref_stop: Option<Sourced<ID>>,
359 #[serde(flatten)]
360 pub common: NodeCommon,
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
364pub struct BusStop {
365 pub codes: Vec<String>,
366 pub name: Option<Sourced<String>>,
367 pub company: Sourced<ID>,
368 #[serde(deserialize_with = "deserialise_connections")]
369 pub connections: HashMap<ID, Vec<Sourced<Connection>>>,
370 #[serde(flatten)]
371 pub common: LocatedNodeCommon,
372}
373
374#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
375pub enum RailMode {
376 #[serde(rename = "warp")]
377 Warp,
378 #[serde(rename = "cart")]
379 Cart,
380 #[serde(rename = "traincarts")]
381 TrainCart,
382 #[serde(rename = "vehicles")]
383 Vehicles,
384}
385
386#[derive(Clone, Debug, Serialize, Deserialize)]
387pub struct RailCompany {
388 pub name: String,
389 pub lines: Vec<Sourced<ID>>,
390 pub stations: Vec<Sourced<ID>>,
391 pub local: bool,
392 #[serde(flatten)]
393 pub common: NodeCommon,
394}
395
396#[derive(Clone, Debug, Serialize, Deserialize)]
397pub struct RailLine {
398 pub code: String,
399 pub name: Option<Sourced<String>>,
400 pub colour: Option<Sourced<String>>,
401 pub company: Sourced<ID>,
402 pub ref_stop: Option<Sourced<ID>>,
403 #[serde(flatten)]
404 pub common: NodeCommon,
405}
406
407#[derive(Clone, Debug, Serialize, Deserialize)]
408pub struct RailStation {
409 pub codes: Vec<String>,
410 pub name: Option<Sourced<String>>,
411 pub company: Sourced<ID>,
412 #[serde(deserialize_with = "deserialise_connections")]
413 pub connections: HashMap<ID, Vec<Sourced<Connection>>>,
414 #[serde(flatten)]
415 pub common: LocatedNodeCommon,
416}
417
418#[derive(Clone, Debug, Serialize, Deserialize)]
419pub struct SeaCompany {
420 pub name: String,
421 pub lines: Vec<Sourced<ID>>,
422 pub stops: Vec<Sourced<ID>>,
423 pub local: bool,
424 #[serde(flatten)]
425 pub common: NodeCommon,
426}
427
428#[derive(Clone, Debug, Serialize, Deserialize)]
429pub struct SeaLine {
430 pub code: String,
431 pub name: Option<Sourced<String>>,
432 pub colour: Option<Sourced<String>>,
433 pub company: Sourced<ID>,
434 pub ref_stop: Option<Sourced<ID>>,
435 #[serde(flatten)]
436 pub common: NodeCommon,
437}
438
439#[derive(Clone, Debug, Serialize, Deserialize)]
440pub struct SeaStop {
441 pub codes: Vec<String>,
442 pub name: Option<Sourced<String>>,
443 pub company: Sourced<ID>,
444 #[serde(deserialize_with = "deserialise_connections")]
445 pub connections: HashMap<ID, Vec<Sourced<Connection>>>,
446 #[serde(flatten)]
447 pub common: LocatedNodeCommon,
448}
449
450#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
451#[serde(rename_all = "lowercase")]
452pub enum SpawnWarpType {
453 Premier,
454 Terminus,
455 Portal,
456 Misc,
457}
458
459#[derive(Clone, Debug, Serialize, Deserialize)]
460pub struct SpawnWarp {
461 pub name: String,
462 pub warp_type: SpawnWarpType,
463 #[serde(flatten)]
464 pub common: LocatedNodeCommon,
465}
466
467#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
468pub enum Rank {
469 Unranked,
470 Councillor,
471 Mayor,
472 Senator,
473 Governor,
474 Premier,
475 Community,
476}
477
478#[derive(Clone, Debug, Serialize, Deserialize)]
479pub struct Town {
480 pub name: String,
481 pub rank: Sourced<Rank>,
482 pub mayor: Sourced<String>,
483 pub deputy_mayor: Sourced<Option<String>>,
484 #[serde(flatten)]
485 pub common: LocatedNodeCommon,
486}
487
488#[derive(Clone, Debug, Serialize, Deserialize)]
489pub struct Connection {
490 pub line: ID,
491 pub direction: Option<Direction>,
492}
493
494#[derive(Clone, Debug, Serialize, Deserialize)]
495pub struct Direction {
496 pub direction: ID,
497 pub forward_label: Option<String>,
498 pub backward_label: Option<String>,
499 pub one_way: Sourced<bool>,
500}
501
502fn deserialise_connections<'de, D: Deserializer<'de>>(
503 de: D,
504) -> Result<HashMap<ID, Vec<Sourced<Connection>>>, D::Error> {
505 HashMap::<String, Vec<Sourced<Connection>>>::deserialize(de)?
506 .into_iter()
507 .map(|(k, v)| Ok((k.parse::<ID>().map_err(serde::de::Error::custom)?, v)))
508 .collect()
509}
510fn deserialise_proximity<'de, D: Deserializer<'de>>(
511 de: D,
512) -> Result<HashMap<ID, Sourced<Proximity>>, D::Error> {
513 HashMap::<String, Sourced<Proximity>>::deserialize(de)?
514 .into_iter()
515 .map(|(k, v)| Ok((k.parse::<ID>().map_err(serde::de::Error::custom)?, v)))
516 .collect()
517}
518
519#[cfg(test)]
520#[cfg(feature = "ureq_get")]
521mod test {
522 use super::*;
523
524 #[test]
525 fn ureq1() {
526 let _ = GatelogueData::ureq_get_with_sources().unwrap();
527 }
528
529 #[test]
530 fn ureq2() {
531 let _ = GatelogueData::ureq_get_no_sources().unwrap();
532 }
533}