Skip to main content

gatelogue_types/
lib.rs

1//! Rust utility library for using Gatelogue data in Rust projects. It will load the database for you to access via ORM or raw SQL.
2//!
3//! # Installation
4//! Add to your `Cargo.toml`:
5//! ```toml
6//! gatelogue-types = { version = "3", features = [...] }
7//!
8//! # To import directly from the repository:
9//! gatelogue-types = { git = "https://github.com/mrt-map/gatelogue", package = "gatelogue-types", features = [...] }
10//! ```
11//! Add the `bundled` feature to bundle `SQLite`.
12//!
13//! Also add an HTTP client (e.g. `reqwest`, `ureq`) of your choice.
14//!
15//! # Usage
16//! To retrieve the data:
17//! ```rust
18//! use gatelogue_types::{GD, getter};
19//! # #[tokio::main]
20//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! // with pre-written getter!() functions:
22//! let gd = GD::get_async_no_sources(getter!(reqwest)).await?; // retrieve data (async)
23//! let gd = GD::get_no_sources(getter!(ureq))?; // retrieve data (blocking)
24//!
25//! // or with any HTTP client of your choice (with a callable that takes in a &str and returns an AsRef<[u8]>):
26//! let gd = GD::get_async_no_sources(async |url| reqwest::Result::Ok(reqwest::get(url).await?.bytes().await?.to_vec())); // retrieve data (async)
27//! let gd = GD::get_no_sources(|url| ureq::get(url).call()?.into_body().read_to_vec())?; // retrieve data (blocking)
28//!
29//! // similar syntax can be used if you want the sources, with `get_async_with_sources` and `get_with_sources`
30//! # Ok(()) }
31//! ```
32//! `getter!()` accepts the following inputs:
33//! - `reqwest` (async) requiring `reqwest`
34//! - `reqwest_blocking` (blocking) requiring `reqwest` with `blocking` feature
35//! - `surf` (async) requiring `surf`
36//! - `ureq` (blocking) requiring `ureq`
37//! - `isahc` (blocking) requiring `isahc`
38//! - `isahc_async` (async) requiring `isahc`
39//! - `attohttpc` (blocking) requiring `attohttpc` (untested)
40//! - `minreq` (blocking) requiring `minreq`
41//! - `wreq` (async) requiring `wreq`
42//! - `ehttp` (blocking) requiring `ehttp`
43//! - `ehttp_async` (async) requiring `ehttp` with `native_async` feature
44//!
45//! Using the ORM does not require SQL and makes for generally clean code.
46//! However, doing this is very inefficient as each attribute access is one SQL query.
47//! ```rust
48//! use gatelogue_types::AirAirport;
49//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
50//! # let gd = gatelogue_types::GD::get_no_sources(gatelogue_types::getter!(ureq))?;
51//! for airport in gd.nodes_of_type::<AirAirport>()? {
52//!     for gate in airport.gates(&gd)? {
53//!         println!("Airport {:?} has gate {:?}", airport.code(&gd)?, gate.code(&gd)?)
54//!     }
55//! }
56//! # Ok(()) }
57//! ```
58//!
59//! Querying the underlying `SQLite` database directly with `rusqlite` is generally more efficient and faster.
60//! It is also the only way to access the `*Source` tables, if you retrieved the database with those.
61//!
62//! ```rust
63//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
64//! # let gd = gatelogue_types::GD::get_no_sources(gatelogue_types::getter!(ureq))?;
65//! gd.0.prepare("SELECT A.code, G.code FROM AirGate G INNER JOIN AirAirport A ON G.airport = A.i")?
66//!     .query_map((), |row| {
67//!         println!("Airport {:?} has gate {:?}", row.get::<_, String>(0)?, row.get::<_, String>(1)?);
68//!         Ok(())
69//!     })?;
70//! # Ok(()) }
71//! ```
72
73use std::fmt::Debug;
74
75use rusqlite::{types::FromSql, Connection};
76
77mod error;
78mod node;
79mod util;
80
81pub use error::*;
82pub use node::{
83    air::*, bus::*, located::*, rail::*, sea::*, spawn_warp::*, town::*, AnyNode, Node,
84};
85pub use util::ID;
86
87use crate::util::ConnectionExt;
88
89pub const URL: &str = "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data.db";
90pub const URL_NO_SOURCES: &str =
91    "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data-ns.db";
92
93#[macro_export]
94macro_rules! getter {
95    (reqwest) => {
96        async |url: &'static str| -> Result<Vec<u8>, reqwest::Error> {
97            Ok(reqwest::get(url).await?.bytes().await?.to_vec())
98        }
99    };
100    (reqwest_blocking) => {
101        |url: &'static str| -> Result<Vec<u8>, reqwest::Error> {
102            Ok(reqwest::blocking::get(url)?.bytes()?.to_vec())
103        }
104    };
105    (surf) => {
106        async |url: &'static str| -> Result<Vec<u8>, surf::Error> {
107            surf::get(url).recv_bytes().await
108        }
109    };
110    (ureq) => {
111        |url: &'static str| -> Result<Vec<u8>, ureq::Error> {
112            ureq::get(url).call()?.into_body().read_to_vec()
113        }
114    };
115    (isahc) => {
116        |url: &'static str| -> Result<Vec<u8>, isahc::Error> {
117            Ok(isahc::ReadResponseExt::bytes(&mut isahc::get(url)?)?)
118        }
119    };
120    (isahc_async) => {
121        async |url: &'static str| -> Result<Vec<u8>, isahc::Error> {
122            Ok(isahc::AsyncReadResponseExt::bytes(&mut isahc::get_async(url).await?).await?)
123        }
124    };
125    (attohttpc) => {
126        |url: &'static str| -> Result<Vec<u8>, attohttpc::Error> {
127            attohttpc::get(url).send()?.bytes()
128        }
129    };
130    (minreq) => {
131        |url: &'static str| -> Result<Vec<u8>, minreq::Error> {
132            Ok(minreq::get(url).send()?.into_bytes())
133        }
134    };
135    (wreq) => {
136        async |url: &'static str| -> Result<Vec<u8>, wreq::Error> {
137            Ok(wreq::get(url).send().await?.bytes().await?.to_vec())
138        }
139    };
140    (ehttp) => {
141        |url: &'static str| -> Result<Vec<u8>, ehttp::Error> {
142            Ok(ehttp::fetch_blocking(&ehttp::Request::get(url))?.bytes)
143        }
144    };
145    (ehttp_async) => {
146        async |url: &'static str| -> Result<Vec<u8>, ehttp::Error> {
147            Ok(ehttp::fetch_async(ehttp::Request::get(url)).await?.bytes)
148        }
149    };
150}
151
152pub struct GD(pub Connection);
153
154impl GD {
155    fn from_bytes(bytes: &[u8]) -> Result<Self> {
156        let mut conn = Connection::open_in_memory()?;
157        conn.deserialize_read_exact("main", bytes, bytes.len(), true)?;
158        Ok(Self(conn))
159    }
160    pub async fn get_async_with_sources<
161        F: AsyncFnOnce(&'static str) -> Result<B, E>,
162        B: AsRef<[u8]>,
163        E: Debug + Send + Sync + 'static,
164    >(
165        getter: F,
166    ) -> Result<Self> {
167        Self::from_bytes(
168            getter(URL)
169                .await
170                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
171                .as_ref(),
172        )
173    }
174    pub async fn get_async_no_sources<
175        F: AsyncFnOnce(&'static str) -> Result<B, E>,
176        B: AsRef<[u8]>,
177        E: Debug + Send + Sync + 'static,
178    >(
179        getter: F,
180    ) -> Result<Self> {
181        Self::from_bytes(
182            getter(URL_NO_SOURCES)
183                .await
184                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
185                .as_ref(),
186        )
187    }
188    pub fn get_with_sources<
189        F: FnOnce(&'static str) -> Result<B, E>,
190        B: AsRef<[u8]>,
191        E: Debug + Send + Sync + 'static,
192    >(
193        getter: F,
194    ) -> Result<Self> {
195        Self::from_bytes(
196            getter(URL)
197                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
198                .as_ref(),
199        )
200    }
201    pub fn get_no_sources<
202        F: FnOnce(&'static str) -> Result<B, E>,
203        B: AsRef<[u8]>,
204        E: Debug + Send + Sync + 'static,
205    >(
206        getter: F,
207    ) -> Result<Self> {
208        Self::from_bytes(
209            getter(URL_NO_SOURCES)
210                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
211                .as_ref(),
212        )
213    }
214
215    pub fn timestamp(&self) -> Result<String> {
216        self.0
217            .query_one("SELECT timestamp FROM Metadata", (), |a| a.get(0))
218            .map_err(Into::into)
219    }
220    pub fn version(&self) -> Result<u32> {
221        self.0
222            .query_one("SELECT version FROM Metadata", (), |a| a.get(0))
223            .map_err(Into::into)
224    }
225    pub fn has_sources(&self) -> Result<bool> {
226        self.0
227            .query_one("SELECT has_sources FROM Metadata", (), |a| a.get(0))
228            .map_err(Into::into)
229    }
230
231    pub fn nodes(&self) -> Result<Vec<AnyNode>> {
232        self.0
233            .query_and_then_get_vec("SELECT i FROM Node", (), |a| {
234                AnyNode::from_id(self, a.get(0)?).map(|a| a.unwrap())
235            })
236    }
237    pub fn located_nodes(&self) -> Result<Vec<AnyLocatedNode>> {
238        self.0
239            .query_and_then_get_vec("SELECT i FROM LocatedNode", (), |a| {
240                AnyLocatedNode::from_id(self, a.get(0)?).map(|a| a.unwrap())
241            })
242    }
243    pub fn nodes_of_type<T: Node + From<ID> + FromSql>(&self) -> Result<Vec<T>> {
244        let ty = T::from(0).ty();
245        self.0.query_and_then_get_vec(
246            "SELECT i FROM Node WHERE type = ?",
247            (ty,),
248            |a| Ok(a.get(0)?),
249        )
250    }
251    pub fn get_node(&self, id: ID) -> Result<Option<AnyNode>> {
252        AnyNode::from_id(self, id)
253    }
254    pub fn get_located_node(&self, id: ID) -> Result<Option<AnyLocatedNode>> {
255        AnyLocatedNode::from_id(self, id)
256    }
257}
258
259#[cfg(test)]
260mod test {
261    use super::*;
262
263    #[test]
264    fn ureq_sources() {
265        let gd = GD::get_with_sources(getter!(ureq)).unwrap();
266        println!("{} {}", gd.version().unwrap(), gd.timestamp().unwrap());
267        assert!(gd.has_sources().unwrap());
268    }
269
270    #[test]
271    fn ureq_no_sources() {
272        let gd = GD::get_no_sources(getter!(ureq)).unwrap();
273        println!("{} {}", gd.version().unwrap(), gd.timestamp().unwrap());
274        assert!(!gd.has_sources().unwrap());
275    }
276
277    #[tokio::test]
278    async fn reqwest() {
279        GD::get_async_no_sources(getter!(reqwest)).await.unwrap();
280    }
281
282    #[test]
283    fn reqwest_blocking() {
284        GD::get_no_sources(getter!(reqwest_blocking)).unwrap();
285    }
286
287    // #[tokio::test]
288    // async fn surf() {
289    //     GD::get_async_no_sources(getter!(surf)).await.unwrap();
290    // }
291
292    #[test]
293    fn isahc() {
294        GD::get_no_sources(getter!(isahc)).unwrap();
295    }
296
297    #[tokio::test]
298    async fn isahc_async() {
299        GD::get_async_no_sources(getter!(isahc_async))
300            .await
301            .unwrap();
302    }
303
304    // #[test]
305    // fn attohttpc() {
306    //     GD::get_no_sources(getter!(attohttpc)).unwrap();
307    // }
308
309    #[test]
310    fn minreq() {
311        GD::get_no_sources(getter!(minreq)).unwrap();
312    }
313
314    #[tokio::test]
315    async fn wreq() {
316        GD::get_async_no_sources(getter!(wreq)).await.unwrap();
317    }
318
319    #[test]
320    fn ehttp() {
321        GD::get_no_sources(getter!(ehttp)).unwrap();
322    }
323
324    #[tokio::test]
325    async fn ehttp_async() {
326        GD::get_async_no_sources(getter!(ehttp_async))
327            .await
328            .unwrap();
329    }
330}