use std::{ collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, io::Read, }; use thiserror::Error; #[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, Hash, Eq, PartialEq)] 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, 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, Error> { 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) } } #[cfg(test)] pub mod mocks { use std::collections::HashMap; use super::*; pub struct MemoryAssets { assets: HashMap, } impl MemoryAssets { pub fn new(data: Vec<(AssetId, String)>) -> Self { let mut m = HashMap::new(); data.into_iter().for_each(|(asset, path)| { m.insert(asset, path); }); Self { assets: m } } } impl Assets for MemoryAssets { fn assets<'a>(&'a self) -> AssetIter<'a> { AssetIter(self.assets.iter()) } fn get(&self, asset_id: AssetId) -> Result, Error> { unimplemented!() } } }