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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use derive_more::{Display, From};
use thiserror::Error;
use super::StorePrefix;
/// A Zarr abstract store key.
///
/// See <https://zarr-specs.readthedocs.io/en/latest/v3/core/index.html#abstract-store-interface>.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)]
pub struct StoreKey(String);
/// An invalid store key.
#[derive(Debug, Clone, From, Error)]
#[error("invalid store key {0}")]
pub struct StoreKeyError(String);
/// A list of [`StoreKey`].
pub type StoreKeys = Vec<StoreKey>;
impl StoreKey {
/// Returns the root store key.
#[must_use]
pub fn root() -> Self {
// SAFETY: The empty string is a valid store key.
unsafe { Self::new_unchecked(String::default()) }
}
/// Create a new Zarr abstract store key from `key`.
///
/// # Errors
///
/// Returns [`StoreKeyError`] if `key` is not valid according to [`StoreKey::validate()`].
pub fn new(key: impl Into<String>) -> Result<Self, StoreKeyError> {
let key = key.into();
if Self::validate(&key) {
Ok(Self(key))
} else {
Err(StoreKeyError(key))
}
}
/// Create a new Zarr abstract store key from `key` without validation.
///
/// # Safety
///
/// `key` is not validated, so this can result in an invalid store key.
#[must_use]
pub unsafe fn new_unchecked(key: impl Into<String>) -> Self {
let key = key.into();
debug_assert!(Self::validate(&key));
Self(key)
}
/// Extracts a string slice of the underlying Key [String].
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
/// Validates a key according to the following rule from the specification:
/// - a key is a Unicode string, where the final character is not a `/` character.
///
/// Additional checks (not in the specification):
/// - a key which starts with '/' is invalid, and
/// - a key that contains '//' is invalid.
///
/// The empty string is a valid key: it addresses a store's root resource as a single blob
/// (e.g. a [`FilesystemStore`](https://docs.rs/zarrs_filesystem/latest/zarrs_filesystem/struct.FilesystemStore.html)
/// rooted directly at a file, rather than a directory).
#[must_use]
pub fn validate(key: &str) -> bool {
!key.starts_with('/') && !key.ends_with('/') && !key.contains("//")
}
/// Returns true if the key has prefix `prefix`.
#[must_use]
pub fn has_prefix(&self, prefix: &StorePrefix) -> bool {
self.0.starts_with(prefix.as_str())
}
/// Convert to a [`StoreKey`].
#[must_use]
pub fn to_prefix(&self) -> StorePrefix {
if self.0.is_empty() {
StorePrefix::root()
} else {
unsafe { StorePrefix::new_unchecked(self.0.clone() + "/") }
}
}
/// Returns the parent of this key.
#[must_use]
pub fn parent(&self) -> StorePrefix {
let key_split: Vec<_> = self.as_str().split('/').collect();
let mut parent = key_split[..key_split.len() - 1].join("/");
if !parent.is_empty() {
parent.push('/');
}
unsafe { StorePrefix::new_unchecked(&parent) }
}
}
impl TryFrom<&str> for StoreKey {
type Error = StoreKeyError;
fn try_from(key: &str) -> Result<Self, Self::Error> {
Self::new(key)
}
}
impl From<&StorePrefix> for StoreKey {
fn from(prefix: &StorePrefix) -> Self {
let prefix = prefix.as_str();
let key = prefix.strip_suffix('/').unwrap_or(prefix);
unsafe { Self::new_unchecked(key.to_string()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn store_prefix() {
assert!(StoreKey::new("a").is_ok());
assert_eq!(StoreKey::new("a").unwrap().to_string(), "a");
assert!(StoreKey::new("a/").is_err());
assert_eq!(
StoreKey::new("a/").unwrap_err().to_string(),
"invalid store key a/"
);
assert!(StoreKey::new("/a").is_err());
assert_eq!(
StoreKey::new("a").unwrap().to_prefix(),
StorePrefix::new("a/").unwrap()
);
assert_eq!(
StoreKey::new("a/b").unwrap().parent(),
StorePrefix::new("a/").unwrap()
);
assert_eq!(
StoreKey::new("a").unwrap().parent(),
StorePrefix::new("").unwrap()
);
}
#[test]
fn empty_key_is_valid() {
// The empty key addresses a store's root resource as a single blob (e.g. a
// `FilesystemStore` rooted directly at a file rather than a directory).
let key = StoreKey::new("").unwrap();
assert_eq!(key, StoreKey::root());
assert_eq!(key.to_string(), "");
assert_eq!(key.to_prefix(), StorePrefix::root());
assert_eq!(key.parent(), StorePrefix::root());
assert!(key.has_prefix(&StorePrefix::root()));
}
}