use std::path::{Component, Path};
use cbh_model::{OBJECTS_SEGMENT, STORAGE_VERSION, sanitize_segment};
use super::StorageError;
pub(crate) fn is_plain_segment(segment: &str) -> bool {
let mut components = Path::new(segment).components();
matches!(components.next(), Some(Component::Normal(_))) && components.next().is_none()
}
pub(crate) fn validate_key(key: &str) -> Result<(), StorageError> {
for segment in key.split('/') {
if !is_plain_segment(segment) {
return Err(StorageError::InvalidKey {
key: key.to_owned(),
});
}
}
Ok(())
}
const CACHE_EPOCH_SEGMENT: &str = "_cache-epoch";
pub(crate) fn cache_epoch_key(project: &str) -> String {
format!(
"{STORAGE_VERSION}/{project}/{CACHE_EPOCH_SEGMENT}",
project = sanitize_segment(project)
)
}
#[must_use]
pub fn project_objects_prefix(project: &str) -> String {
format!(
"{STORAGE_VERSION}/{project}/{OBJECTS_SEGMENT}/",
project = sanitize_segment(project)
)
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn cache_epoch_key_lands_in_the_project_partition() {
assert_eq!(cache_epoch_key("folo"), "v1/folo/_cache-epoch");
assert_eq!(cache_epoch_key("a b"), "v1/a_b/_cache-epoch");
}
#[test]
fn project_objects_prefix_narrows_to_the_data_subtree() {
assert_eq!(project_objects_prefix("folo"), "v1/folo/objects/");
assert_eq!(project_objects_prefix("a b"), "v1/a_b/objects/");
assert!(!cache_epoch_key("folo").starts_with(&project_objects_prefix("folo")));
}
}