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| surf::get(url).recv_bytes().await); // 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//!
43//! Using the ORM does not require SQL and makes for generally clean code.
44//! However, doing this is very inefficient as each attribute access is one SQL query.
45//! ```rust
46//! use gatelogue_types::AirAirport;
47//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! # let gd = gatelogue_types::GD::get_no_sources(gatelogue_types::getter!(ureq))?;
49//! for airport in gd.nodes_of_type::<AirAirport>()? {
50//!     for gate in airport.gates(&gd)? {
51//!         println!("Airport {:?} has gate {:?}", airport.code(&gd)?, gate.code(&gd)?)
52//!     }
53//! }
54//! # Ok(()) }
55//! ```
56//!
57//! Querying the underlying `SQLite` database directly with `rusqlite` is generally more efficient and faster.
58//! It is also the only way to access the `*Source` tables, if you retrieved the database with those.
59//!
60//! ```rust
61//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
62//! # let gd = gatelogue_types::GD::get_no_sources(gatelogue_types::getter!(ureq))?;
63//! gd.0.prepare("SELECT A.code, G.code FROM AirGate G INNER JOIN AirAirport A ON G.airport = A.i")?
64//!     .query_map((), |row| {
65//!         println!("Airport {:?} has gate {:?}", row.get::<_, String>(0)?, row.get::<_, String>(1)?);
66//!         Ok(())
67//!     })?;
68//! # Ok(()) }
69//! ```
70
71use std::fmt::Debug;
72
73use rusqlite::{types::FromSql, Connection};
74
75mod error;
76mod node;
77mod util;
78
79pub use error::*;
80pub use node::{
81    air::*, bus::*, located::*, rail::*, sea::*, spawn_warp::*, town::*, AnyNode, Node,
82};
83pub use util::ID;
84
85use crate::util::ConnectionExt;
86
87pub const URL: &str = "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data.db";
88pub const URL_NO_SOURCES: &str =
89    "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data-ns.db";
90
91#[macro_export]
92macro_rules! getter {
93    (reqwest) => {
94        async |url: &'static str| -> Result<Vec<u8>, reqwest::Error> {
95            Ok(reqwest::get(url).await?.bytes().await?.to_vec())
96        }
97    };
98    (reqwest_blocking) => {
99        |url: &'static str| -> Result<Vec<u8>, reqwest::Error> {
100            Ok(reqwest::blocking::get(url)?.bytes()?.to_vec())
101        }
102    };
103    (surf) => {
104        async |url: &'static str| -> Result<Vec<u8>, surf::Error> {
105            surf::get(url).recv_bytes().await
106        }
107    };
108    (ureq) => {
109        |url: &'static str| -> Result<Vec<u8>, ureq::Error> {
110            ureq::get(url).call()?.into_body().read_to_vec()
111        }
112    };
113    (isahc) => {
114        |url: &'static str| -> Result<Vec<u8>, isahc::Error> {
115            Ok(isahc::ReadResponseExt::bytes(&mut isahc::get(url)?)?)
116        }
117    };
118    (isahc_async) => {
119        async |url: &'static str| -> Result<Vec<u8>, isahc::Error> {
120            Ok(isahc::AsyncReadResponseExt::bytes(&mut isahc::get_async(url).await?).await?)
121        }
122    };
123    (attohttpc) => {
124        |url: &'static str| -> Result<Vec<u8>, attohttpc::Error> {
125            attohttpc::get(url).send()?.bytes()
126        }
127    };
128    (minreq) => {
129        |url: &'static str| -> Result<Vec<u8>, minreq::Error> {
130            Ok(minreq::get(url).send()?.into_bytes())
131        }
132    };
133    (wreq) => {
134        async |url: &'static str| -> Result<Vec<u8>, wreq::Error> {
135            Ok(wreq::get(url).send().await?.bytes().await?.to_vec())
136        }
137    };
138}
139
140pub struct GD(pub Connection);
141
142impl GD {
143    fn from_bytes(bytes: &[u8]) -> Result<Self> {
144        let mut conn = Connection::open_in_memory()?;
145        conn.deserialize_read_exact("main", bytes, bytes.len(), true)?;
146        Ok(Self(conn))
147    }
148    pub async fn get_async_with_sources<
149        F: AsyncFnOnce(&'static str) -> Result<B, E>,
150        B: AsRef<[u8]>,
151        E: Debug + Send + Sync + 'static,
152    >(
153        getter: F,
154    ) -> Result<Self> {
155        Self::from_bytes(
156            getter(URL)
157                .await
158                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
159                .as_ref(),
160        )
161    }
162    pub async fn get_async_no_sources<
163        F: AsyncFnOnce(&'static str) -> Result<B, E>,
164        B: AsRef<[u8]>,
165        E: Debug + Send + Sync + 'static,
166    >(
167        getter: F,
168    ) -> Result<Self> {
169        Self::from_bytes(
170            getter(URL_NO_SOURCES)
171                .await
172                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
173                .as_ref(),
174        )
175    }
176    pub fn get_with_sources<
177        F: FnOnce(&'static str) -> Result<B, E>,
178        B: AsRef<[u8]>,
179        E: Debug + Send + Sync + 'static,
180    >(
181        getter: F,
182    ) -> Result<Self> {
183        Self::from_bytes(
184            getter(URL)
185                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
186                .as_ref(),
187        )
188    }
189    pub fn get_no_sources<
190        F: FnOnce(&'static str) -> Result<B, E>,
191        B: AsRef<[u8]>,
192        E: Debug + Send + Sync + 'static,
193    >(
194        getter: F,
195    ) -> Result<Self> {
196        Self::from_bytes(
197            getter(URL_NO_SOURCES)
198                .map_err(|e| Error::HTTPGetError(Box::new(e)))?
199                .as_ref(),
200        )
201    }
202
203    pub fn timestamp(&self) -> Result<String> {
204        self.0
205            .query_one("SELECT timestamp FROM Metadata", (), |a| a.get(0))
206            .map_err(Into::into)
207    }
208    pub fn version(&self) -> Result<u32> {
209        self.0
210            .query_one("SELECT version FROM Metadata", (), |a| a.get(0))
211            .map_err(Into::into)
212    }
213    pub fn has_sources(&self) -> Result<bool> {
214        self.0
215            .query_one("SELECT has_sources FROM Metadata", (), |a| a.get(0))
216            .map_err(Into::into)
217    }
218
219    pub fn nodes(&self) -> Result<Vec<AnyNode>> {
220        self.0
221            .query_and_then_get_vec("SELECT i FROM Node", (), |a| {
222                AnyNode::from_id(self, a.get(0)?).map(|a| a.unwrap())
223            })
224    }
225    pub fn located_nodes(&self) -> Result<Vec<AnyLocatedNode>> {
226        self.0
227            .query_and_then_get_vec("SELECT i FROM LocatedNode", (), |a| {
228                AnyLocatedNode::from_id(self, a.get(0)?).map(|a| a.unwrap())
229            })
230    }
231    pub fn nodes_of_type<T: Node + From<ID> + FromSql>(&self) -> Result<Vec<T>> {
232        let ty = T::from(0).ty();
233        self.0.query_and_then_get_vec(
234            "SELECT i FROM Node WHERE type = ?",
235            (ty,),
236            |a| Ok(a.get(0)?),
237        )
238    }
239    pub fn get_node(&self, id: ID) -> Result<Option<AnyNode>> {
240        AnyNode::from_id(self, id)
241    }
242    pub fn get_located_node(&self, id: ID) -> Result<Option<AnyLocatedNode>> {
243        AnyLocatedNode::from_id(self, id)
244    }
245}
246
247#[cfg(test)]
248mod test {
249    use super::*;
250
251    #[test]
252    fn ureq_sources() {
253        let gd = GD::get_with_sources(getter!(ureq)).unwrap();
254        println!("{} {}", gd.version().unwrap(), gd.timestamp().unwrap());
255        assert!(gd.has_sources().unwrap());
256    }
257
258    #[test]
259    fn ureq_no_sources() {
260        let gd = GD::get_no_sources(getter!(ureq)).unwrap();
261        println!("{} {}", gd.version().unwrap(), gd.timestamp().unwrap());
262        assert!(!gd.has_sources().unwrap());
263    }
264
265    #[tokio::test]
266    async fn reqwest() {
267        GD::get_async_no_sources(getter!(reqwest)).await.unwrap();
268    }
269
270    #[test]
271    fn reqwest_blocking() {
272        GD::get_no_sources(getter!(reqwest_blocking)).unwrap();
273    }
274
275    #[tokio::test]
276    async fn surf() {
277        GD::get_async_no_sources(getter!(surf)).await.unwrap();
278    }
279
280    #[test]
281    fn isahc() {
282        GD::get_no_sources(getter!(isahc)).unwrap();
283    }
284
285    #[tokio::test]
286    async fn isahc_async() {
287        GD::get_async_no_sources(getter!(isahc_async))
288            .await
289            .unwrap();
290    }
291
292    // #[test]
293    // fn attohttpc() {
294    //     GD::get_no_sources(getter!(attohttpc)).unwrap();
295    // }
296
297    #[test]
298    fn minreq() {
299        GD::get_no_sources(getter!(minreq)).unwrap();
300    }
301
302    #[tokio::test]
303    async fn wreq() {
304        GD::get_async_no_sources(getter!(wreq)).await.unwrap();
305    }
306}