use std::{ collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, io::Read, }; 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}")] 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, Deserialize, Eq, Hash, PartialEq, Serialize)] #[typeshare] pub struct AssetId(String); 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<'a>(&'a self) -> AssetIter<'a>; fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), Error>; } pub struct FsAssets { assets: HashMap, } impl FsAssets { pub fn new() -> Self { Self { assets: HashMap::new(), } } fn assets<'a>(&'a self) -> impl Iterator { self.assets.keys() } } impl Assets for FsAssets { fn assets<'a>(&'a self) -> AssetIter<'a> { 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), } } } }