use std::collections::BTreeMap;
use crate::domain::model::site::Asset;
use crate::domain::usecases::site::readers::AssetsReader;
pub struct OverlayAssetsReader<'a> {
base: &'a dyn AssetsReader,
overlay: &'a dyn AssetsReader,
}
impl<'a> OverlayAssetsReader<'a> {
pub fn new(base: &'a dyn AssetsReader, overlay: &'a dyn AssetsReader) -> Self {
Self { base, overlay }
}
fn merged(&self) -> anyhow::Result<BTreeMap<String, Asset>> {
let mut map: BTreeMap<String, Asset> = BTreeMap::new();
for asset in self.base.assets() {
let asset = asset?;
map.insert(asset.source.as_str().to_string(), asset);
}
for asset in self.overlay.assets() {
let asset = asset?;
map.insert(asset.source.as_str().to_string(), asset);
}
Ok(map)
}
}
impl AssetsReader for OverlayAssetsReader<'_> {
fn assets<'a>(&'a self) -> Box<dyn Iterator<Item = anyhow::Result<Asset>> + 'a> {
match self.merged() {
Ok(map) => Box::new(map.into_values().map(Ok)),
Err(e) => Box::new(std::iter::once(Err(e))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::model::site::Source;
struct StubReader(Vec<Asset>);
impl AssetsReader for StubReader {
fn assets<'a>(&'a self) -> Box<dyn Iterator<Item = anyhow::Result<Asset>> + 'a> {
Box::new(self.0.clone().into_iter().map(Ok))
}
}
fn asset(path: &str, body: &str) -> Asset {
Asset::new(
Source::relative_path(path).unwrap(),
body.as_bytes().to_vec(),
)
}
fn collect(reader: &dyn AssetsReader) -> Vec<(String, Vec<u8>)> {
reader
.assets()
.map(Result::unwrap)
.map(|a| (a.source.as_str().to_string(), a.bytes))
.collect()
}
#[test]
fn overlay_wins_on_path_collision() {
let base = StubReader(vec![asset("css/site.css", "base")]);
let overlay = StubReader(vec![asset("css/site.css", "overlay")]);
let reader = OverlayAssetsReader::new(&base, &overlay);
let out = collect(&reader);
assert_eq!(out, vec![("css/site.css".into(), b"overlay".to_vec())]);
}
#[test]
fn base_assets_pass_through_without_collision() {
let base = StubReader(vec![asset("a.css", "A"), asset("b.css", "B")]);
let overlay = StubReader(vec![asset("c.css", "C")]);
let reader = OverlayAssetsReader::new(&base, &overlay);
let out = collect(&reader);
assert_eq!(
out,
vec![
("a.css".into(), b"A".to_vec()),
("b.css".into(), b"B".to_vec()),
("c.css".into(), b"C".to_vec()),
]
);
}
#[test]
fn surface_errors_from_either_layer() {
struct FailingReader;
impl AssetsReader for FailingReader {
fn assets<'a>(&'a self) -> Box<dyn Iterator<Item = anyhow::Result<Asset>> + 'a> {
Box::new(std::iter::once(Err(anyhow::anyhow!("boom"))))
}
}
let base = StubReader(vec![asset("a.css", "A")]);
let overlay = FailingReader;
let reader = OverlayAssetsReader::new(&base, &overlay);
let mut it = reader.assets();
let first = it.next().unwrap();
assert!(first.is_err());
}
}