1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//! Explicit object-store namespaces for memory and disk caches.
use object_store::{local::LocalFileSystem, path::Path};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// The object namespace shared by cache users. Equal identities assert that
/// the same object path names the same immutable contents.
///
/// Include the backend, endpoint, bucket or filesystem root, and any wrapper
/// prefix in a stable namespace. Change it when replacing a store's contents;
/// credentials and display names alone do not identify a store.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CacheIdentity(u128);
impl CacheIdentity {
/// Names a namespace consistently across handles and process restarts.
///
/// ```
/// use moraine::CacheIdentity;
/// let identity = CacheIdentity::new("s3:https://storage.example/bucket/prefix");
/// assert_eq!(
/// identity,
/// CacheIdentity::new("s3:https://storage.example/bucket/prefix")
/// );
/// ```
#[must_use]
pub fn new(namespace: &str) -> Self {
Self::from_bytes(namespace.as_bytes())
}
fn from_bytes(namespace: &[u8]) -> Self {
let version = Uuid::new_v5(&Uuid::NAMESPACE_URL, b"moraine/cache-identity/v2");
Self(Uuid::new_v5(&version, namespace).as_u128())
}
/// Names a local store by its resolved filesystem root.
///
/// # Errors
/// Returns an error if the store cannot resolve a filesystem path.
pub fn local(store: &LocalFileSystem) -> object_store::Result<Self> {
// A fixed child resolves the root without requiring that child to exist.
let member = store.path_to_filesystem(&Path::from(".moraine-cache-identity"))?;
let mut namespace = b"local:".to_vec();
namespace.extend_from_slice(member.as_os_str().as_encoded_bytes());
Ok(Self::from_bytes(&namespace))
}
/// A fixed-width name suitable for a cache directory component.
pub(crate) fn directory(self) -> String {
Uuid::from_u128(self.0).simple().to_string()
}
}
impl Default for CacheIdentity {
/// Creates an isolated namespace. Clone it to share within this process.
fn default() -> Self {
Self(Uuid::new_v4().as_u128())
}
}
#[cfg(test)]
mod tests {
use foyer::Code;
use proptest::prelude::*;
use super::*;
proptest! {
#[test]
fn cache_identity_roundtrips(value in any::<u128>()) {
let identity = CacheIdentity(value);
let mut bytes = Vec::new();
identity.encode(&mut bytes).unwrap();
prop_assert_eq!(CacheIdentity::decode(&mut bytes.as_slice()).unwrap(), identity);
}
}
#[test]
fn stable_names_are_versioned_and_isolated_names_are_unique() {
let name = "AmazonS3(bucket)";
assert_eq!(CacheIdentity::new(name), CacheIdentity::new(name));
assert_ne!(
CacheIdentity::new(name).0,
Uuid::new_v5(&Uuid::NAMESPACE_URL, name.as_bytes()).as_u128()
);
assert_ne!(CacheIdentity::default(), CacheIdentity::default());
}
}