1use std::sync::Arc;
16
17use uuid::Uuid;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct PuffinStorageScope {
25 scheme: String,
26 name: String,
27 root: String,
28 instance_id: Uuid,
29}
30
31impl PuffinStorageScope {
32 pub fn new(scheme: String, name: String, root: String) -> Self {
36 Self {
37 scheme,
38 name,
39 root,
40 instance_id: Uuid::nil(),
41 }
42 }
43
44 pub fn unique(scheme: String, name: String, root: String) -> Self {
47 Self {
48 scheme,
49 name,
50 root,
51 instance_id: Uuid::new_v4(),
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59pub struct PuffinFileKey<H> {
60 scope: Arc<PuffinStorageScope>,
61 path: Arc<str>,
62 handle: H,
63}
64
65impl<H> PuffinFileKey<H> {
66 pub fn new(scope: Arc<PuffinStorageScope>, path: String, handle: H) -> Self {
68 Self {
69 scope,
70 path: path.into(),
71 handle,
72 }
73 }
74
75 pub fn map_handle<T>(self, map: impl FnOnce(H) -> T) -> PuffinFileKey<T> {
77 PuffinFileKey {
78 scope: self.scope,
79 path: self.path,
80 handle: map(self.handle),
81 }
82 }
83
84 pub fn handle(&self) -> &H {
86 &self.handle
87 }
88
89 pub fn estimated_size(&self) -> usize {
92 std::mem::size_of::<Self>()
93 + std::mem::size_of::<PuffinStorageScope>()
94 + self.scope.scheme.capacity()
95 + self.scope.name.capacity()
96 + self.scope.root.capacity()
97 + self.path.len()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use std::collections::HashSet;
104
105 use super::*;
106
107 fn scope(parts: [&str; 3]) -> Arc<PuffinStorageScope> {
108 let [scheme, name, root] = parts.map(str::to_string);
109 Arc::new(PuffinStorageScope::new(scheme, name, root))
110 }
111
112 #[test]
113 fn test_reconstructed_keys_share_identity() {
114 let parts = ["s3", "bucket", "prefix/"];
115 let original = PuffinFileKey::new(scope(parts), "table/file".into(), (17_u64, 2_u32));
116 let reconstructed = PuffinFileKey::new(scope(parts), "table/file".into(), (17_u64, 2_u32));
117 let keys = HashSet::from([original.clone()]);
118 assert!(keys.contains(&reconstructed));
119 assert!(keys.contains(&original.clone()));
120 assert_eq!(&(17_u64, 2_u32), original.handle());
121 assert!(!keys.contains(&PuffinFileKey::new(
122 scope(parts),
123 "table/file".into(),
124 (17_u64, 3_u32)
125 )));
126 assert!(!keys.contains(&PuffinFileKey::new(
127 scope(parts),
128 "table/file".into(),
129 (18_u64, 2_u32)
130 )));
131 }
132
133 #[test]
134 fn test_unique_scopes_require_explicit_sharing() {
135 let first = Arc::new(PuffinStorageScope::unique(
136 "s3".into(),
137 "bucket".into(),
138 "/".into(),
139 ));
140 let second = Arc::new(PuffinStorageScope::unique(
141 "s3".into(),
142 "bucket".into(),
143 "/".into(),
144 ));
145 let key = PuffinFileKey::new(first.clone(), "file".into(), 1_u64);
146 assert_eq!(key, PuffinFileKey::new(first, "file".into(), 1_u64));
147 assert_ne!(key, PuffinFileKey::new(second, "file".into(), 1_u64));
148 }
149
150 #[test]
151 fn test_storage_components_are_independent() {
152 let parts = ["s3", "bucket", "prefix/"];
153 let original = PuffinFileKey::new(scope(parts), "table/file".into(), 17_u64);
154 for index in 0..parts.len() {
155 let mut changed = parts;
156 changed[index] = "different";
157 assert_ne!(
158 original,
159 PuffinFileKey::new(scope(changed), "table/file".into(), 17_u64)
160 );
161 }
162 assert_ne!(
163 original,
164 PuffinFileKey::new(scope(parts), "other/file".into(), 17_u64)
165 );
166 assert_ne!(
167 PuffinFileKey::new(scope(["s3", "a:b", "c"]), "d".into(), 17_u64),
168 PuffinFileKey::new(scope(["s3", "a", "b:c"]), "d".into(), 17_u64),
169 );
170 }
171}