pub(crate) mod config;
pub(crate) mod response_types;
pub(crate) mod server;
pub(crate) mod types;
pub(crate) mod utilities;
use log::LevelFilter;
use simple_logger::SimpleLogger;
use rtz_core::base::types::Void;
use crate::web::config::Config;
const VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn server_start(config_path: String, bind_address: Option<String>, port: Option<u16>, should_log: Option<bool>) -> Void {
tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async {
println!("Version: v{}", VERSION);
let config = Config::new(&config_path, bind_address, port, should_log)?;
let log_level = if config.should_log { LevelFilter::Info } else { LevelFilter::Off };
SimpleLogger::new().with_level(log_level).init().unwrap();
server::start(&config).await?;
Ok::<_, rtz_core::base::types::Err>(())
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use axum::{body::Body, Router};
use http_body_util::BodyExt;
use hyper::{Request, StatusCode};
use pretty_assertions::assert_eq;
use tower::{Service, ServiceExt};
fn get_client() -> Router {
let config = Config::new("", None, None, Some(false)).unwrap();
server::create_axum_app(&config)
}
#[tokio::test]
async fn can_get_ned_timezone_v1() {
let client = get_client();
let request = Request::get("/api/v1/ned/tz/-121.0/46.0").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap_or_default().to_bytes();
let expected = r#"[{"id":20,"identifier":"America/Los_Angeles","description":"Canada (most of British Columbia), Mexico (Baja California), United States (California, most of Nevada, most of Oregon, Washington (state))","dstDescription":"Canada (most of British Columbia), Mexico (Baja California), United States (California, most of Nevada, most of Oregon, Washington (state))","offset":"UTC-08:00","zone":-8.0,"rawOffset":-28800}]"#;
assert_eq!(body, expected);
}
#[tokio::test]
async fn can_get_not_found_ned_timezone_v1() {
let client = get_client();
let request = Request::get("/api/v1/ned/tz/179.9968/-67.0959").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap_or_default().to_bytes();
let expected = r#"[]"#;
assert_eq!(body, expected);
}
#[tokio::test]
async fn can_get_not_modified_ned_timezone_v1() {
let mut client = get_client();
let request = Request::get("/api/v1/ned/tz/-121.0/46.0").body(Body::empty()).unwrap();
let response = <axum::Router as tower::ServiceExt<Request<Body>>>::ready(&mut client).await.unwrap().call(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let if_modified_since = response.headers().get("If-Modified-Since").unwrap().as_bytes();
let request = Request::get("/api/v1/ned/tz/-121.0/46.0").header("If-Modified-Since", if_modified_since).body(Body::empty()).unwrap();
let response = <axum::Router as tower::ServiceExt<Request<Body>>>::ready(&mut client).await.unwrap().call(request).await.unwrap();
assert_eq!(response.status(), StatusCode::NOT_MODIFIED);
}
#[tokio::test]
async fn can_get_osm_timezone_v1() {
let client = get_client();
let request = Request::get("/api/v1/osm/tz/-112/33").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap_or_default().to_bytes();
let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
let identifiers = parsed.as_array().unwrap().iter().filter_map(|v| v["identifier"].as_str()).collect::<Vec<_>>();
assert!(identifiers.contains(&"America/Phoenix"), "expected America/Phoenix in {identifiers:?}");
}
#[tokio::test]
async fn can_get_osm_admin_v1() {
let client = get_client();
let request = Request::get("/api/v1/osm/admin/30/30").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap_or_default().to_bytes();
let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
let admins = parsed
.as_array()
.unwrap()
.iter()
.map(|v| (v["name"].as_str().unwrap(), v["level"].as_u64().unwrap(), v["relationId"].as_u64().unwrap()))
.collect::<Vec<_>>();
assert!(admins.contains(&("مصر", 2, 1473947)), "expected Egypt (level 2, rel 1473947) in {admins:?}");
assert!(admins.contains(&("مطروح", 4, 3061826)), "expected Matrouh (level 4, rel 3061826) in {admins:?}");
}
#[tokio::test]
async fn unversioned_ned_matches_v1_body() {
let client = get_client();
let a = client.clone().oneshot(Request::get("/api/ned/tz/-121.0/46.0").body(Body::empty()).unwrap()).await.unwrap();
let a_body = a.into_body().collect().await.unwrap().to_bytes();
let b = client.oneshot(Request::get("/api/v1/ned/tz/-121.0/46.0").body(Body::empty()).unwrap()).await.unwrap();
let b_body = b.into_body().collect().await.unwrap().to_bytes();
assert_eq!(a_body, b_body);
}
#[tokio::test]
async fn malformed_coordinate_is_bad_request() {
let client = get_client();
let request = Request::get("/api/v1/ned/tz/not-a-number/46.0").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn unknown_route_is_not_found() {
let client = get_client();
let request = Request::get("/api/does-not-exist").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn health_endpoint_is_ok() {
let client = get_client();
let request = Request::get("/api/health").body(Body::empty()).unwrap();
let response = client.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}