coven_storage/
object_listing.rs1use crate::{CloudProvider, ObjectPath, ObjectPrefix, StorageError, StorageFailure, StoredObject};
2use std::collections::BTreeMap;
3
4pub struct ObjectListing {
15 prefix: ObjectPrefix,
16 provider: CloudProvider,
17 objects: BTreeMap<ObjectPath, StoredObject>,
18 skipped: usize,
19}
20
21pub(crate) fn listed_within(name: &str, prefix: &str) -> Result<(), StorageError> {
24 if name.starts_with(prefix) {
25 Ok(())
26 } else {
27 Err(StorageFailure::Protocol.with_source("provider listed outside the requested prefix"))
28 }
29}
30
31impl ObjectListing {
32 pub(crate) fn new(prefix: ObjectPrefix, provider: CloudProvider) -> Self {
33 Self { prefix, provider, objects: BTreeMap::new(), skipped: 0 }
34 }
35
36 pub fn prefix(&self) -> &ObjectPrefix {
38 &self.prefix
39 }
40
41 pub fn insert(&mut self, object: StoredObject) -> Result<(), StorageError> {
44 object.check(&object.path)?;
45 if !self.prefix.contains(&object.path) {
46 return Err(StorageFailure::Protocol.with_source("provider listed outside prefix"));
47 }
48 if let Some(previous) = self.objects.get(&object.path) {
49 if self.provider == CloudProvider::CloudKit || previous != &object {
50 return Err(
51 StorageFailure::Protocol.with_source("provider listed conflicting objects")
52 );
53 }
54 }
55 self.objects.insert(object.path.clone(), object);
56 Ok(())
57 }
58
59 pub(crate) fn object_path(&mut self, name: &str) -> Result<Option<ObjectPath>, StorageError> {
64 self.within(name)?;
65 let path = ObjectPath::parse(name).ok();
66 self.skipped += usize::from(path.is_none());
67 Ok(path)
68 }
69
70 pub(crate) fn object_folder(&mut self, name: &str) -> Result<bool, StorageError> {
74 if name == self.prefix.folder() {
75 return Ok(true);
77 }
78 self.within(name)?;
79 let folder = crate::path::validate_directory(name).is_ok();
80 self.skipped += usize::from(!folder);
81 Ok(folder)
82 }
83
84 pub(crate) fn skip(&mut self) {
87 self.skipped += 1;
88 }
89
90 fn within(&self, name: &str) -> Result<(), StorageError> {
91 listed_within(name, self.prefix.as_str())
92 }
93
94 pub(crate) fn finish(self) -> Vec<StoredObject> {
95 if self.skipped > 0 {
96 tracing::debug!(
97 prefix = self.prefix.as_str(),
98 skipped = self.skipped,
99 "listing skipped entries outside the store layout"
100 );
101 }
102 self.objects.into_values().collect()
103 }
104}
105
106#[cfg(test)]
107#[path = "object_listing_tests.rs"]
108mod tests;