use std::{ collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, fs, io::Read, path::PathBuf }; use mime::Mime; use serde::{Deserialize, Serialize}; use thiserror::Error; use typeshare::typeshare; #[derive(Debug, Error)] 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}")] Unexpected(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::Unexpected(err), } } } #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[typeshare] pub struct AssetId(String); impl AssetId { pub fn as_str(&self) -> &str { &self.0 } } impl Display for AssetId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AssetId({})", self.0) } } impl From<&str> for AssetId { fn from(s: &str) -> Self { AssetId(s.to_owned()) } } impl From for AssetId { fn from(s: String) -> Self { AssetId(s) } } pub struct AssetIter<'a>(Iter<'a, AssetId, String>); impl<'a> Iterator for AssetIter<'a> { type Item = (&'a AssetId, &'a String); fn next(&mut self) -> Option { self.0.next() } } pub trait Assets { fn assets(&self) -> AssetIter; fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), Error>; } pub struct FsAssets { assets: HashMap, } impl FsAssets { pub fn new(path: PathBuf) -> Self { let dir = fs::read_dir(path).unwrap(); let mut assets = HashMap::new(); for dir_ent in dir { let path = dir_ent.unwrap().path(); let file_name = path.file_name().unwrap().to_str().unwrap(); assets.insert(AssetId::from(file_name), path.to_str().unwrap().to_owned()); } Self { assets, } } } impl Assets for FsAssets { fn assets(&self) -> AssetIter { AssetIter(self.assets.iter()) } fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), Error> { let path = match self.assets.get(&asset_id) { Some(asset) => Ok(asset), None => Err(Error::NotFound), }?; let mime = mime_guess::from_path(path).first().unwrap(); let mut content: Vec = Vec::new(); let mut file = std::fs::File::open(path)?; file.read_to_end(&mut content)?; Ok((mime, content)) } } #[cfg(test)] pub mod mocks { use std::collections::HashMap; use super::*; pub struct MemoryAssets { asset_paths: HashMap, assets: HashMap>, } impl MemoryAssets { pub fn new(data: Vec<(AssetId, String, Vec)>) -> Self { let mut asset_paths = HashMap::new(); let mut assets = HashMap::new(); data.into_iter().for_each(|(asset, path, data)| { asset_paths.insert(asset.clone(), path); assets.insert(asset, data); }); Self { asset_paths, assets, } } } impl Assets for MemoryAssets { fn assets<'a>(&'a self) -> AssetIter<'a> { AssetIter(self.asset_paths.iter()) } fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), Error> { match (self.asset_paths.get(&asset_id), self.assets.get(&asset_id)) { (Some(path), Some(data)) => { let mime = mime_guess::from_path(&path).first().unwrap(); Ok((mime, data.to_vec())) } _ => Err(Error::NotFound), } } } }