use std::future::Future;
use std::pin::Pin;
use anyhow::Result;
use bytes::Bytes;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait AssetProvider: Send + Sync {
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>>>;
fn list<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>>>;
}
#[derive(rust_embed::RustEmbed)]
#[folder = "embedded/"]
struct Embedded;
#[derive(Debug, Default, Clone, Copy)]
pub struct EmbeddedAssets;
impl AssetProvider for EmbeddedAssets {
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>>> {
let result = Embedded::get(key).map(|f| Bytes::copy_from_slice(&f.data));
Box::pin(async move { Ok(result) })
}
fn list<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>>> {
let result: Vec<String> = Embedded::iter()
.filter(|p| p.starts_with(prefix))
.map(|p| p.into_owned())
.collect();
Box::pin(async move { Ok(result) })
}
}
impl<T: AssetProvider + ?Sized> AssetProvider for Box<T> {
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>>> {
(**self).get(key)
}
fn list<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>>> {
(**self).list(prefix)
}
}
#[derive(Debug, Clone, Copy)]
pub struct LayeredAssets<O, B> {
pub over: O,
pub base: B,
}
impl<O: AssetProvider, B: AssetProvider> AssetProvider for LayeredAssets<O, B> {
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>>> {
Box::pin(async move {
match self.over.get(key).await? {
Some(b) => Ok(Some(b)),
None => self.base.get(key).await,
}
})
}
fn list<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>>> {
Box::pin(async move {
let mut out = self.over.list(prefix).await?;
for k in self.base.list(prefix).await? {
if !out.contains(&k) {
out.push(k);
}
}
Ok(out)
})
}
}
pub fn sync_provider<F>(get: F) -> SyncProvider<F>
where
F: Fn(&str) -> Result<Option<Bytes>> + Send + Sync + 'static,
{
SyncProvider { get }
}
pub struct SyncProvider<F> {
get: F,
}
impl<F> AssetProvider for SyncProvider<F>
where
F: Fn(&str) -> Result<Option<Bytes>> + Send + Sync + 'static,
{
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>>> {
let r = (self.get)(key);
Box::pin(async move { r })
}
fn list<'a>(&'a self, _prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>>> {
Box::pin(async move { Ok(Vec::new()) })
}
}
pub fn async_provider<F, Fut>(get: F) -> AsyncProvider<F>
where
F: Fn(String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Option<Bytes>>> + Send + 'static,
{
AsyncProvider { get }
}
pub struct AsyncProvider<F> {
get: F,
}
impl<F, Fut> AssetProvider for AsyncProvider<F>
where
F: Fn(String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Option<Bytes>>> + Send + 'static,
{
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>>> {
let fut = (self.get)(key.to_owned());
Box::pin(fut)
}
fn list<'a>(&'a self, _prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>>> {
Box::pin(async move { Ok(Vec::new()) })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn sync_provider_returns_value() {
let p = sync_provider(|key| {
if key == "hello" {
Ok(Some(Bytes::from_static(b"world")))
} else {
Ok(None)
}
});
assert_eq!(
p.get("hello").await.unwrap(),
Some(Bytes::from_static(b"world"))
);
assert!(p.get("nope").await.unwrap().is_none());
}
#[tokio::test]
async fn async_provider_can_defer() {
let p = async_provider(|key: String| async move {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
if key == "k" {
Ok(Some(Bytes::from_static(b"v")))
} else {
Ok(None)
}
});
assert_eq!(p.get("k").await.unwrap(), Some(Bytes::from_static(b"v")));
assert!(p.list("").await.unwrap().is_empty());
}
#[tokio::test]
async fn layered_prefers_over() {
let over = sync_provider(|k| match k {
"a" => Ok(Some(Bytes::from_static(b"OVER"))),
_ => Ok(None),
});
let base = sync_provider(|k| match k {
"a" => Ok(Some(Bytes::from_static(b"BASE"))),
"b" => Ok(Some(Bytes::from_static(b"BASE-B"))),
_ => Ok(None),
});
let layered = LayeredAssets { over, base };
assert_eq!(
layered.get("a").await.unwrap(),
Some(Bytes::from_static(b"OVER"))
);
assert_eq!(
layered.get("b").await.unwrap(),
Some(Bytes::from_static(b"BASE-B"))
);
assert_eq!(layered.get("c").await.unwrap(), None);
}
}