From 0f42ebcc30f5480d3ba4d10f83b0ac44fa17fc8e Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 21 Nov 2024 18:46:05 -0500 Subject: [PATCH] Isolate error handling from Warp --- visions/server/src/asset_db.rs | 33 ++++++++-- visions/server/src/core.rs | 23 +++---- visions/server/src/handlers.rs | 111 ++++++++++++++++++++++----------- visions/server/src/main.rs | 4 +- visions/server/src/types.rs | 3 + 5 files changed, 118 insertions(+), 56 deletions(-) diff --git a/visions/server/src/asset_db.rs b/visions/server/src/asset_db.rs index 00a783c..6861dfa 100644 --- a/visions/server/src/asset_db.rs +++ b/visions/server/src/asset_db.rs @@ -1,14 +1,32 @@ use std::{ collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, + io::Read, }; use thiserror::Error; #[derive(Debug, Error)] -enum Error { - #[error("Asset could not be found: {0}")] - AssetNotFound(AssetId), +pub enum Error { + #[error("Asset could not be found")] + NotFound, + #[error("Asset could not be opened")] + Inaccessible, + + #[error("An unexpected IO error occured when retrieving an asset {0}")] + UnexpectedError(std::io::Error), +} + +impl From for Error { + fn from(err: std::io::Error) -> Error { + use std::io::ErrorKind::*; + + match err.kind() { + NotFound => Error::NotFound, + PermissionDenied | UnexpectedEof => Error::Inaccessible, + _ => Error::UnexpectedError(err), + } + } } #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -70,7 +88,14 @@ impl Assets for FsAssets { } fn get(&self, asset_id: AssetId) -> Result, Error> { - unimplemented!() + let path = match self.assets.get(&asset_id) { + Some(asset) => Ok(asset), + None => Err(Error::NotFound), + }?; + let mut content: Vec = Vec::new(); + let mut file = std::fs::File::open(&path)?; + file.read_to_end(&mut content)?; + Ok(content) } } diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index f1be756..4299603 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -10,7 +10,7 @@ use urlencoding::decode; use uuid::Uuid; use crate::{ - asset_db::{AssetId, Assets}, + asset_db::{self, AssetId, Assets}, types::{AppError, Message, Tabletop, RGB}, }; @@ -82,19 +82,14 @@ impl Core { self.0.read().unwrap().tabletop.clone() } - pub async fn get_file(&self, file_name: String) -> Vec { - /* - let file_name = decode(&file_name).expect("UTF-8"); - - let mut full_path = self.0.read().unwrap().image_base.clone(); - full_path.push(file_name.to_string()); - - let mut content: Vec = Vec::new(); - let mut file = std::fs::File::open(&full_path).unwrap(); - file.read_to_end(&mut content).unwrap(); - content - */ - unimplemented!() + pub async fn get_asset(&self, asset_id: AssetId) -> Result, AppError> { + self.0.read().unwrap().asset_db.get(asset_id.clone()).map_err(|err| { + match err { + asset_db::Error::NotFound => AppError::NotFound(format!("{}", asset_id)), + asset_db::Error::Inaccessible => AppError::Inaccessible(format!("{}", asset_id)), + asset_db::Error::UnexpectedError(err) => AppError::Inaccessible(format!("{}", err)), + } + }) } pub fn available_images(&self) -> Vec { diff --git a/visions/server/src/handlers.rs b/visions/server/src/handlers.rs index 0796768..e58fc6b 100644 --- a/visions/server/src/handlers.rs +++ b/visions/server/src/handlers.rs @@ -1,8 +1,10 @@ +use std::future::Future; + use futures::{SinkExt, StreamExt}; use serde::{Deserialize, Serialize}; -use warp::{http::Response, reply::Reply, ws::Message}; +use warp::{http::Response, http::StatusCode, reply::Reply, ws::Message}; -use crate::core::Core; +use crate::{asset_db::AssetId, core::Core, types::AppError}; /* pub async fn handle_auth( @@ -28,24 +30,52 @@ pub async fn handle_auth( } */ -pub async fn handle_file(core: Core, file_name: String) -> impl Reply { - let mimetype = mime_guess::from_path(&file_name).first().unwrap(); - let bytes = core.get_file(file_name).await; - Response::builder() - .header("application-type", mimetype.to_string()) - .body(bytes) - .unwrap() +pub async fn handler(f: F) -> impl Reply +where + F: Future>, AppError>>, +{ + match f.await { + Ok(response) => response, + Err(AppError::NotFound(_)) => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(vec![]) + .unwrap(), + Err(_) => Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(vec![]) + .unwrap(), + } +} + +pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply { + handler(async move { + let mimetype = mime_guess::from_path(&format!("{}", asset_id)) + .first() + .unwrap(); + let bytes = core.get_asset(asset_id).await?; + Ok(Response::builder() + .header("application-type", mimetype.to_string()) + .body(bytes) + .unwrap()) + }) + .await } pub async fn handle_available_images(core: Core) -> impl Reply { - let image_paths: Vec = core.available_images().into_iter() - .map(|path| format!("{}", path)).collect(); + handler(async move { + let image_paths: Vec = core + .available_images() + .into_iter() + .map(|path| format!("{}", path)) + .collect(); - Response::builder() - .header("Access-Control-Allow-Origin", "*") - .header("Content-Type", "application/json") - .body(serde_json::to_string(&image_paths).unwrap()) - .unwrap() + Ok(Response::builder() + .header("Access-Control-Allow-Origin", "*") + .header("Content-Type", "application/json") + .body(serde_json::to_vec(&image_paths).unwrap()) + .unwrap()) + }) + .await } #[derive(Deserialize, Serialize)] @@ -57,24 +87,30 @@ pub struct RegisterResponse { } pub async fn handle_register_client(core: Core, _request: RegisterRequest) -> impl Reply { - let client_id = core.register_client(); + handler(async move { + let client_id = core.register_client(); - Response::builder() - .header("Access-Control-Allow-Origin", "*") - .header("Content-Type", "application/json") - .body( - serde_json::to_string(&RegisterResponse { - url: format!("ws://127.0.0.1:8001/ws/{}", client_id), - }) - .unwrap(), - ) - .unwrap() + Ok(Response::builder() + .header("Access-Control-Allow-Origin", "*") + .header("Content-Type", "application/json") + .body( + serde_json::to_vec(&RegisterResponse { + url: format!("ws://127.0.0.1:8001/ws/{}", client_id), + }) + .unwrap(), + ) + .unwrap()) + }) + .await } pub async fn handle_unregister_client(core: Core, client_id: String) -> impl Reply { - core.unregister_client(client_id); + handler(async move { + core.unregister_client(client_id); - warp::reply::reply() + Ok(Response::builder().status(StatusCode::NO_CONTENT).body(vec![]).unwrap()) + }) + .await } pub async fn handle_connect_websocket( @@ -109,12 +145,15 @@ pub async fn handle_connect_websocket( } pub async fn handle_set_background_image(core: Core, image_name: String) -> impl Reply { - let _ = core.set_background_image(image_name); + handler(async move { + let _ = core.set_background_image(image_name); - Response::builder() - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "*") - .header("Content-Type", "application/json") - .body("") - .unwrap() + Ok(Response::builder() + .header("Access-Control-Allow-Origin", "*") + .header("Access-Control-Allow-Methods", "*") + .header("Content-Type", "application/json") + .body(vec![]) + .unwrap()) + }) + .await } diff --git a/visions/server/src/main.rs b/visions/server/src/main.rs index 94644f0..36d5323 100644 --- a/visions/server/src/main.rs +++ b/visions/server/src/main.rs @@ -1,4 +1,4 @@ -use asset_db::FsAssets; +use asset_db::{AssetId, FsAssets}; use authdb::AuthError; use handlers::{ handle_available_images, handle_connect_websocket, handle_file, handle_register_client, handle_set_background_image, handle_unregister_client, RegisterRequest @@ -103,7 +103,7 @@ pub async fn main() { .and(warp::get()) .then({ let core = core.clone(); - move |file_name| handle_file(core.clone(), file_name) + move |file_name| handle_file(core.clone(), AssetId::from(file_name)) }); let route_available_images = warp::path!("api" / "v1" / "image").and(warp::get()).then({ diff --git a/visions/server/src/types.rs b/visions/server/src/types.rs index 45c518f..f47a25b 100644 --- a/visions/server/src/types.rs +++ b/visions/server/src/types.rs @@ -1,10 +1,13 @@ use serde::{Deserialize, Serialize}; + use typeshare::typeshare; #[derive(Debug)] pub enum AppError { NotFound(String), + Inaccessible(String), JsonError(serde_json::Error), + UnexpectedError(String), } #[derive(Clone, Debug, Deserialize, Serialize)]