use std::fmt::Debug;
use rusqlite::{types::FromSql, Connection};
mod error;
mod node;
mod util;
pub use error::*;
pub use node::{
air::*, bus::*, located::*, rail::*, sea::*, spawn_warp::*, town::*, AnyNode, Node,
};
pub use util::ID;
use crate::util::ConnectionExt;
pub const URL: &str = "https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data.db";
pub const URL_NO_SOURCES: &str =
"https://raw.githubusercontent.com/MRT-Map/gatelogue/refs/heads/dist/data-ns.db";
#[macro_export]
macro_rules! getter {
(reqwest) => {
async |url: &'static str| -> Result<Vec<u8>, reqwest::Error> {
Ok(reqwest::get(url).await?.bytes().await?.to_vec())
}
};
(reqwest_blocking) => {
|url: &'static str| -> Result<Vec<u8>, reqwest::Error> {
Ok(reqwest::blocking::get(url)?.bytes()?.to_vec())
}
};
(surf) => {
async |url: &'static str| -> Result<Vec<u8>, surf::Error> {
surf::get(url).recv_bytes().await
}
};
(ureq) => {
|url: &'static str| -> Result<Vec<u8>, ureq::Error> {
ureq::get(url).call()?.into_body().read_to_vec()
}
};
(isahc) => {
|url: &'static str| -> Result<Vec<u8>, isahc::Error> {
Ok(isahc::ReadResponseExt::bytes(&mut isahc::get(url)?)?)
}
};
(isahc_async) => {
async |url: &'static str| -> Result<Vec<u8>, isahc::Error> {
Ok(isahc::AsyncReadResponseExt::bytes(&mut isahc::get_async(url).await?).await?)
}
};
(attohttpc) => {
|url: &'static str| -> Result<Vec<u8>, attohttpc::Error> {
attohttpc::get(url).send()?.bytes()
}
};
(minreq) => {
|url: &'static str| -> Result<Vec<u8>, minreq::Error> {
Ok(minreq::get(url).send()?.into_bytes())
}
};
(wreq) => {
async |url: &'static str| -> Result<Vec<u8>, wreq::Error> {
Ok(wreq::get(url).send().await?.bytes().await?.to_vec())
}
};
(ehttp) => {
|url: &'static str| -> Result<Vec<u8>, ehttp::Error> {
Ok(ehttp::fetch_blocking(&ehttp::Request::get(url))?.bytes)
}
};
(ehttp_async) => {
async |url: &'static str| -> Result<Vec<u8>, ehttp::Error> {
Ok(ehttp::fetch_async(ehttp::Request::get(url)).await?.bytes)
}
};
}
pub struct GD(pub Connection);
impl GD {
fn from_bytes(bytes: &[u8]) -> Result<Self> {
let mut conn = Connection::open_in_memory()?;
conn.deserialize_read_exact("main", bytes, bytes.len(), true)?;
Ok(Self(conn))
}
pub async fn get_async_with_sources<
F: AsyncFnOnce(&'static str) -> Result<B, E>,
B: AsRef<[u8]>,
E: Debug + Send + Sync + 'static,
>(
getter: F,
) -> Result<Self> {
Self::from_bytes(
getter(URL)
.await
.map_err(|e| Error::HTTPGetError(Box::new(e)))?
.as_ref(),
)
}
pub async fn get_async_no_sources<
F: AsyncFnOnce(&'static str) -> Result<B, E>,
B: AsRef<[u8]>,
E: Debug + Send + Sync + 'static,
>(
getter: F,
) -> Result<Self> {
Self::from_bytes(
getter(URL_NO_SOURCES)
.await
.map_err(|e| Error::HTTPGetError(Box::new(e)))?
.as_ref(),
)
}
pub fn get_with_sources<
F: FnOnce(&'static str) -> Result<B, E>,
B: AsRef<[u8]>,
E: Debug + Send + Sync + 'static,
>(
getter: F,
) -> Result<Self> {
Self::from_bytes(
getter(URL)
.map_err(|e| Error::HTTPGetError(Box::new(e)))?
.as_ref(),
)
}
pub fn get_no_sources<
F: FnOnce(&'static str) -> Result<B, E>,
B: AsRef<[u8]>,
E: Debug + Send + Sync + 'static,
>(
getter: F,
) -> Result<Self> {
Self::from_bytes(
getter(URL_NO_SOURCES)
.map_err(|e| Error::HTTPGetError(Box::new(e)))?
.as_ref(),
)
}
pub fn timestamp(&self) -> Result<String> {
self.0
.query_one("SELECT timestamp FROM Metadata", (), |a| a.get(0))
.map_err(Into::into)
}
pub fn version(&self) -> Result<u32> {
self.0
.query_one("SELECT version FROM Metadata", (), |a| a.get(0))
.map_err(Into::into)
}
pub fn has_sources(&self) -> Result<bool> {
self.0
.query_one("SELECT has_sources FROM Metadata", (), |a| a.get(0))
.map_err(Into::into)
}
pub fn nodes(&self) -> Result<Vec<AnyNode>> {
self.0
.query_and_then_get_vec("SELECT i FROM Node", (), |a| {
AnyNode::from_id(self, a.get(0)?).map(|a| a.unwrap())
})
}
pub fn located_nodes(&self) -> Result<Vec<AnyLocatedNode>> {
self.0
.query_and_then_get_vec("SELECT i FROM LocatedNode", (), |a| {
AnyLocatedNode::from_id(self, a.get(0)?).map(|a| a.unwrap())
})
}
pub fn nodes_of_type<T: Node + From<ID> + FromSql>(&self) -> Result<Vec<T>> {
let ty = T::from(0).ty();
self.0.query_and_then_get_vec(
"SELECT i FROM Node WHERE type = ?",
(ty,),
|a| Ok(a.get(0)?),
)
}
pub fn get_node(&self, id: ID) -> Result<Option<AnyNode>> {
AnyNode::from_id(self, id)
}
pub fn get_located_node(&self, id: ID) -> Result<Option<AnyLocatedNode>> {
AnyLocatedNode::from_id(self, id)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ureq_sources() {
let gd = GD::get_with_sources(getter!(ureq)).unwrap();
println!("{} {}", gd.version().unwrap(), gd.timestamp().unwrap());
assert!(gd.has_sources().unwrap());
}
#[test]
fn ureq_no_sources() {
let gd = GD::get_no_sources(getter!(ureq)).unwrap();
println!("{} {}", gd.version().unwrap(), gd.timestamp().unwrap());
assert!(!gd.has_sources().unwrap());
}
#[tokio::test]
async fn reqwest() {
GD::get_async_no_sources(getter!(reqwest)).await.unwrap();
}
#[test]
fn reqwest_blocking() {
GD::get_no_sources(getter!(reqwest_blocking)).unwrap();
}
#[test]
fn isahc() {
GD::get_no_sources(getter!(isahc)).unwrap();
}
#[tokio::test]
async fn isahc_async() {
GD::get_async_no_sources(getter!(isahc_async))
.await
.unwrap();
}
#[test]
fn minreq() {
GD::get_no_sources(getter!(minreq)).unwrap();
}
#[tokio::test]
async fn wreq() {
GD::get_async_no_sources(getter!(wreq)).await.unwrap();
}
#[test]
fn ehttp() {
GD::get_no_sources(getter!(ehttp)).unwrap();
}
#[tokio::test]
async fn ehttp_async() {
GD::get_async_no_sources(getter!(ehttp_async))
.await
.unwrap();
}
}