Update web to serve embedded static files

This commit is contained in:
Austen Adler 2023-03-11 17:40:38 -05:00
parent 9ab794a75a
commit 07d43ff6b5
5 changed files with 195 additions and 66 deletions

93
Cargo.lock generated
View File

@ -1141,6 +1141,41 @@ dependencies = [
"uncased", "uncased",
] ]
[[package]]
name = "rust-embed"
version = "6.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb133b9a38b5543fad3807fb2028ea47c5f2b566f4f5e28a11902f1a358348b6"
dependencies = [
"rocket",
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
dependencies = [
"sha2",
"walkdir",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.11" version = "1.0.11"
@ -1165,6 +1200,15 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@ -1533,6 +1577,33 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "ufmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a64846ec02b57e9108d6469d98d1648782ad6bb150a95a9baac26900bbeab9d"
dependencies = [
"ufmt-macros",
"ufmt-write",
]
[[package]]
name = "ufmt-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ufmt-write"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]] [[package]]
name = "uncased" name = "uncased"
version = "0.9.7" version = "0.9.7"
@ -1577,6 +1648,17 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.0" version = "0.3.0"
@ -1652,8 +1734,10 @@ name = "web"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"rocket", "rocket",
"rust-embed",
"serde", "serde",
"tokio", "tokio",
"ufmt",
"xpin", "xpin",
] ]
@ -1673,6 +1757,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View File

@ -10,3 +10,5 @@ rocket = {version="0.5.0-rc.2", features=["json"]}
tokio = {version="1"} tokio = {version="1"}
xpin={path=".."} xpin={path=".."}
serde = {version="1", features=["derive"]} serde = {version="1", features=["derive"]}
rust-embed = { version = "6.6.0", features = ["rocket"] }
ufmt = "0.2.0"

65
web/src/api.rs Normal file
View File

@ -0,0 +1,65 @@
use std::str::FromStr;
use rocket::{get, serde::json::Json, Responder};
use serde::{Deserialize, Serialize};
use xpin::Address;
#[get("/address?<lat>&<lon>")]
pub(crate) fn get_address(lat: Option<String>, lon: Option<String>) -> Result<String, ApiError> {
let (lat, lon) = lat.zip(lon).ok_or(ApiError::InvalidRequest(
"lat and lon parameters are required",
))?;
let lat = lat
.parse::<f64>()
.map_err(|_| ApiError::InvalidRequest("Invalid lat parameter"))?;
let lon = lon
.parse::<f64>()
.map_err(|_| ApiError::InvalidRequest("Invalid lon parameter"))?;
Ok(Address::from_lat_lon(lat, lon)?.to_string())
}
#[get("/coords?<address>")]
pub(crate) fn get_coords(address: Option<String>) -> Result<Json<Coords>, ApiError> {
let address = address.ok_or(ApiError::InvalidRequest("address parameter required"))?;
Address::from_str(&address)
.as_ref()
.map_err(Into::into)
.map(Address::as_lat_lon)
.map(Coords::from)
.map(Json)
}
#[derive(Serialize, Deserialize)]
pub(crate) struct Coords {
lat: f64,
lon: f64,
}
impl From<(f64, f64)> for Coords {
fn from((lat, lon): (f64, f64)) -> Self {
Self { lat, lon }
}
}
#[derive(Responder)]
#[response(status = 400)]
pub(crate) enum ApiError {
#[response(status = 400)]
AlgorithmError(String),
#[response(status = 400)]
InvalidRequest(&'static str),
}
impl From<&xpin::Error> for ApiError {
fn from(e: &xpin::Error) -> Self {
Self::AlgorithmError(e.to_string())
}
}
impl From<xpin::Error> for ApiError {
fn from(e: xpin::Error) -> Self {
Self::from(&e)
}
}

View File

@ -1,78 +1,17 @@
use std::str::FromStr; mod api;
mod static_assets;
use rocket::{get, routes, serde::json::Json, Responder}; use rocket::routes;
use serde::{Deserialize, Serialize};
use xpin::Address;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _r = rocket::build() let _r = rocket::build()
// Add the API // Add the API
.mount("/api/v1", routes![get_address, get_coords]) .mount("/api/v1", routes![api::get_address, api::get_coords])
// Add the webui frontend // Add the webui frontend
// TODO: ./mount("/", routes![webui]) .mount("/", routes![static_assets::dist])
.launch() .launch()
.await?; .await?;
Ok(()) Ok(())
} }
#[get("/address?<lat>&<lon>")]
async fn get_address(lat: Option<String>, lon: Option<String>) -> Result<String, ApiError> {
let (lat, lon) = lat.zip(lon).ok_or(ApiError::InvalidRequest(
"lat and lon parameters are required",
))?;
let lat = lat
.parse::<f64>()
.map_err(|_| ApiError::InvalidRequest("Invalid lat parameter"))?;
let lon = lon
.parse::<f64>()
.map_err(|_| ApiError::InvalidRequest("Invalid lon parameter"))?;
Ok(Address::from_lat_lon(lat, lon)?.to_string())
}
#[get("/coords?<address>")]
async fn get_coords(address: Option<String>) -> Result<Json<Coords>, ApiError> {
let address = address.ok_or(ApiError::InvalidRequest("address parameter required"))?;
Address::from_str(&address)
.as_ref()
.map_err(Into::into)
.map(Address::as_lat_lon)
.map(Coords::from)
.map(Json)
}
#[derive(Serialize, Deserialize)]
struct Coords {
lat: f64,
lon: f64,
}
impl From<(f64, f64)> for Coords {
fn from((lat, lon): (f64, f64)) -> Self {
Self { lat, lon }
}
}
#[derive(Responder)]
#[response(status = 400)]
enum ApiError {
#[response(status = 400)]
AlgorithmError(String),
#[response(status = 400)]
InvalidRequest(&'static str),
}
impl From<&xpin::Error> for ApiError {
fn from(e: &xpin::Error) -> Self {
Self::AlgorithmError(e.to_string())
}
}
impl From<xpin::Error> for ApiError {
fn from(e: xpin::Error) -> Self {
Self::from(&e)
}
}

30
web/src/static_assets.rs Normal file
View File

@ -0,0 +1,30 @@
use rocket::get;
use rocket::http::ContentType;
use rust_embed::RustEmbed;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::PathBuf;
#[derive(RustEmbed)]
#[folder = "../build/"]
struct Asset;
#[get("/<file..>")]
pub(crate) fn dist(file: PathBuf) -> Option<(ContentType, Cow<'static, [u8]>)> {
if let Some(a) = Asset::get(&format!("{}", file.display().to_string())) {
let content_type = file
.extension()
.and_then(OsStr::to_str)
.and_then(ContentType::from_extension)
.unwrap_or(ContentType::Bytes);
return Some((content_type, a.data));
}
[".html", "index.html"].iter().find_map(|ext| {
let p = format!("{}{}", file.display().to_string(), ext);
Asset::get(&p).map(|a| (ContentType::HTML, a.data))
})
}