use_key_value_store/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6macro_rules! string_newtype {
7 ($(#[$meta:meta])* $name:ident) => {
8 $(#[$meta])*
9 #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10 pub struct $name(String);
11
12 impl $name {
13 pub fn new(value: impl Into<String>) -> Self {
15 Self(value.into())
16 }
17
18 pub fn as_str(&self) -> &str {
20 &self.0
21 }
22 }
23
24 impl AsRef<str> for $name {
25 fn as_ref(&self) -> &str {
26 self.as_str()
27 }
28 }
29
30 impl From<String> for $name {
31 fn from(value: String) -> Self {
32 Self::new(value)
33 }
34 }
35
36 impl From<&str> for $name {
37 fn from(value: &str) -> Self {
38 Self::new(value)
39 }
40 }
41
42 impl fmt::Display for $name {
43 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44 formatter.write_str(self.as_str())
45 }
46 }
47 };
48}
49
50string_newtype! {
51 Key
53}
54string_newtype! {
55 Value
57}
58string_newtype! {
59 KeyPrefix
61}
62string_newtype! {
63 KeyNamespace
65}
66string_newtype! {
67 BucketName
69}
70string_newtype! {
71 KeyPattern
73}
74
75impl Key {
76 pub fn from_segments(segments: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
78 let joined = segments
79 .into_iter()
80 .map(|segment| segment.as_ref().to_owned())
81 .filter(|segment| !segment.is_empty())
82 .collect::<Vec<_>>()
83 .join(":");
84 Self::new(joined)
85 }
86
87 pub fn with_prefix(prefix: &KeyPrefix, segment: impl AsRef<str>) -> Self {
89 Self::from_segments([prefix.as_str(), segment.as_ref()])
90 }
91}
92
93#[derive(Clone, Debug, Eq, Hash, PartialEq)]
95pub struct KeyValueEntry {
96 key: Key,
97 value: Value,
98}
99
100impl KeyValueEntry {
101 pub fn new(key: Key, value: Value) -> Self {
103 Self { key, value }
104 }
105
106 pub const fn key(&self) -> &Key {
108 &self.key
109 }
110
111 pub const fn value(&self) -> &Value {
113 &self.value
114 }
115}
116
117#[derive(Clone, Debug, Default, Eq, PartialEq)]
119pub struct KeyRange {
120 start: Option<Key>,
121 end: Option<Key>,
122}
123
124impl KeyRange {
125 pub const fn new(start: Option<Key>, end: Option<Key>) -> Self {
127 Self { start, end }
128 }
129
130 pub fn starting_at(start: Key) -> Self {
132 Self::new(Some(start), None)
133 }
134
135 pub fn ending_at(end: Key) -> Self {
137 Self::new(None, Some(end))
138 }
139
140 pub const fn start(&self) -> Option<&Key> {
142 self.start.as_ref()
143 }
144
145 pub const fn end(&self) -> Option<&Key> {
147 self.end.as_ref()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::{BucketName, Key, KeyPrefix, KeyRange, KeyValueEntry, Value};
154 use std::collections::hash_map::DefaultHasher;
155 use std::hash::{Hash, Hasher};
156
157 #[test]
158 fn constructs_and_displays_keys() {
159 let key = Key::from_segments(["tenant", "customer", "123"]);
160 assert_eq!(key.to_string(), "tenant:customer:123");
161 assert_eq!(key.as_ref(), "tenant:customer:123");
162 assert_eq!(BucketName::new("cache").to_string(), "cache");
163 }
164
165 #[test]
166 fn composes_keys_with_prefixes() {
167 let key = Key::with_prefix(&KeyPrefix::new("tenant:acme"), "profile");
168 let entry = KeyValueEntry::new(key.clone(), Value::new("payload"));
169 let range = KeyRange::starting_at(key.clone());
170
171 assert_eq!(key.as_str(), "tenant:acme:profile");
172 assert_eq!(entry.key(), &key);
173 assert_eq!(range.start(), Some(&key));
174 }
175
176 #[test]
177 fn hashes_equal_keys() {
178 let mut left = DefaultHasher::new();
179 let mut right = DefaultHasher::new();
180 Key::new("same").hash(&mut left);
181 Key::new("same").hash(&mut right);
182 assert_eq!(left.finish(), right.finish());
183 }
184}