use crate::kb::ObjectMeta;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Rollup {
pub folders: BTreeSet<String>,
pub files: BTreeMap<String, ObjectMeta>,
}
impl Rollup {
pub fn is_empty(&self) -> bool {
self.folders.is_empty() && self.files.is_empty()
}
}
pub fn roll_up(objects: impl IntoIterator<Item = ObjectMeta>, prefix: &str) -> Rollup {
let mut rollup = Rollup::default();
for meta in objects {
let Some(relative) = meta.key.strip_prefix(prefix) else {
continue;
};
if relative.is_empty() {
continue;
}
if let Some((folder, _)) = relative.split_once('/') {
rollup.folders.insert(folder.to_string());
} else {
rollup.files.insert(relative.to_string(), meta);
}
}
rollup
}
#[cfg(test)]
mod tests {
use super::*;
fn meta(key: &str) -> ObjectMeta {
ObjectMeta {
key: key.to_string(),
size: 1,
last_modified: None,
content_type: None,
etag: None,
}
}
#[test]
fn a_root_listing_splits_into_folders_and_files() {
let rollup = roll_up(
["a.md", "docs/b.md", "docs/deep/c.md", "z.md"].map(meta),
"",
);
assert_eq!(
rollup
.folders
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
["docs"],
"a folder is contributed once however many keys sit under it"
);
assert_eq!(
rollup.files.keys().map(String::as_str).collect::<Vec<_>>(),
["a.md", "z.md"]
);
}
#[test]
fn a_nested_listing_shows_only_its_own_level() {
let rollup = roll_up(
[
"docs/b.md",
"docs/deep/c.md",
"docs/deep/deeper/d.md",
"a.md",
]
.map(meta),
"docs/",
);
assert_eq!(
rollup
.folders
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
["deep"]
);
assert_eq!(
rollup.files.keys().map(String::as_str).collect::<Vec<_>>(),
["b.md"],
"keys outside the prefix must not leak into the level"
);
}
#[test]
fn an_object_stored_at_the_prefix_itself_is_not_a_child_of_it() {
let rollup = roll_up(["docs", "docs/b.md"].map(meta), "docs/");
assert!(rollup.folders.is_empty());
assert_eq!(
rollup.files.keys().map(String::as_str).collect::<Vec<_>>(),
["b.md"]
);
}
#[test]
fn a_folder_and_an_object_of_the_same_name_are_both_reported() {
let rollup = roll_up(["collide", "collide/inner.md"].map(meta), "");
assert!(rollup.folders.contains("collide"));
assert!(rollup.files.contains_key("collide"));
}
#[test]
fn an_empty_listing_rolls_up_to_nothing() {
assert!(roll_up(Vec::new(), "").is_empty());
assert!(roll_up(["other/a.md"].map(meta), "docs/").is_empty());
}
}