reddb_types/
key_dictionary.rs1use crate::types::{read_varint, write_varint, ValueError};
34use std::collections::HashMap;
35
36pub const MAGIC: &[u8; 4] = b"RKDX";
38
39pub const VERSION: u8 = 0x01;
41
42#[derive(Debug, PartialEq)]
44pub enum KeyDictError {
45 TruncatedData,
47 BadMagic,
49 UnsupportedVersion(u8),
51 InvalidName,
53 Varint(ValueError),
55}
56
57impl From<ValueError> for KeyDictError {
58 fn from(e: ValueError) -> Self {
59 KeyDictError::Varint(e)
60 }
61}
62
63impl std::fmt::Display for KeyDictError {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 match self {
66 Self::TruncatedData => write!(f, "key dictionary: truncated data"),
67 Self::BadMagic => write!(f, "key dictionary: bad magic bytes (expected RKDX)"),
68 Self::UnsupportedVersion(v) => write!(f, "key dictionary: unsupported version {v}"),
69 Self::InvalidName => write!(f, "key dictionary: name is not valid UTF-8"),
70 Self::Varint(e) => write!(f, "key dictionary: varint error: {e}"),
71 }
72 }
73}
74
75impl std::error::Error for KeyDictError {}
76
77#[derive(Debug, Clone, Default, PartialEq)]
81pub struct KeyDictionary {
82 names: Vec<String>,
84 ids: HashMap<String, u32>,
86}
87
88impl KeyDictionary {
89 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn len(&self) -> usize {
96 self.names.len()
97 }
98
99 pub fn is_empty(&self) -> bool {
101 self.names.is_empty()
102 }
103
104 pub fn intern(&mut self, name: &str) -> u32 {
111 if let Some(&id) = self.ids.get(name) {
112 return id;
113 }
114 let id = self.names.len() as u32;
115 self.names.push(name.to_string());
116 self.ids.insert(name.to_string(), id);
117 id
118 }
119
120 pub fn id_of(&self, name: &str) -> Option<u32> {
125 self.ids.get(name).copied()
126 }
127
128 pub fn name_of(&self, id: u32) -> Option<&str> {
130 self.names.get(id as usize).map(String::as_str)
131 }
132
133 pub fn encode(&self, out: &mut Vec<u8>) {
139 out.extend_from_slice(MAGIC);
140 out.push(VERSION);
141 write_varint(out, self.names.len() as u64);
142 for name in &self.names {
143 let bytes = name.as_bytes();
144 write_varint(out, bytes.len() as u64);
145 out.extend_from_slice(bytes);
146 }
147 }
148
149 pub fn decode(data: &[u8]) -> Result<Self, KeyDictError> {
151 if data.len() < 5 {
152 return Err(KeyDictError::TruncatedData);
153 }
154 if &data[0..4] != MAGIC.as_slice() {
155 return Err(KeyDictError::BadMagic);
156 }
157 if data[4] != VERSION {
158 return Err(KeyDictError::UnsupportedVersion(data[4]));
159 }
160
161 let mut cursor = 5;
162 let (count, n) = read_varint(&data[cursor..])?;
163 cursor += n;
164
165 let mut dict = KeyDictionary::new();
166 for _ in 0..count {
167 let (len, n) = read_varint(&data[cursor..])?;
168 cursor += n;
169 let end = cursor
170 .checked_add(len as usize)
171 .ok_or(KeyDictError::TruncatedData)?;
172 if end > data.len() {
173 return Err(KeyDictError::TruncatedData);
174 }
175 let name =
176 std::str::from_utf8(&data[cursor..end]).map_err(|_| KeyDictError::InvalidName)?;
177 dict.intern(name);
178 cursor = end;
179 }
180
181 Ok(dict)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn empty_dictionary() {
191 let dict = KeyDictionary::new();
192 assert!(dict.is_empty());
193 assert_eq!(dict.len(), 0);
194 assert_eq!(dict.id_of("anything"), None);
195 assert_eq!(dict.name_of(0), None);
196 }
197
198 #[test]
199 fn intern_assigns_dense_ids_in_order() {
200 let mut dict = KeyDictionary::new();
201 assert_eq!(dict.intern("name"), 0);
202 assert_eq!(dict.intern("email"), 1);
203 assert_eq!(dict.intern("active"), 2);
204 assert_eq!(dict.len(), 3);
205 }
206
207 #[test]
208 fn intern_is_idempotent_and_append_only() {
209 let mut dict = KeyDictionary::new();
210 let first = dict.intern("status");
211 dict.intern("createdAt");
212 let again = dict.intern("status");
214 assert_eq!(first, again);
215 assert_eq!(dict.len(), 2);
216 }
217
218 #[test]
219 fn id_of_does_not_mutate() {
220 let mut dict = KeyDictionary::new();
221 dict.intern("a");
222 assert_eq!(dict.id_of("a"), Some(0));
223 assert_eq!(dict.id_of("b"), None);
225 assert_eq!(dict.len(), 1);
226 }
227
228 #[test]
229 fn name_of_round_trips_ids() {
230 let mut dict = KeyDictionary::new();
231 dict.intern("first");
232 dict.intern("second");
233 assert_eq!(dict.name_of(0), Some("first"));
234 assert_eq!(dict.name_of(1), Some("second"));
235 assert_eq!(dict.name_of(2), None);
236 }
237
238 #[test]
239 fn encode_decode_round_trip() {
240 let mut dict = KeyDictionary::new();
241 for k in ["id", "name", "email", "país", "🔑"] {
242 dict.intern(k);
243 }
244 let mut buf = Vec::new();
245 dict.encode(&mut buf);
246 let restored = KeyDictionary::decode(&buf).expect("decode");
247 assert_eq!(restored, dict);
248 assert_eq!(restored.id_of("país"), Some(3));
250 assert_eq!(restored.name_of(4), Some("🔑"));
251 }
252
253 #[test]
254 fn empty_dictionary_round_trips() {
255 let dict = KeyDictionary::new();
256 let mut buf = Vec::new();
257 dict.encode(&mut buf);
258 let restored = KeyDictionary::decode(&buf).expect("decode");
259 assert!(restored.is_empty());
260 }
261
262 #[test]
263 fn decode_rejects_bad_magic_and_version() {
264 let mut buf = Vec::new();
265 KeyDictionary::new().encode(&mut buf);
266 let mut bad = buf.clone();
267 bad[0] = b'X';
268 assert_eq!(KeyDictionary::decode(&bad), Err(KeyDictError::BadMagic));
269 let mut bad = buf.clone();
270 bad[4] = 0x99;
271 assert_eq!(
272 KeyDictionary::decode(&bad),
273 Err(KeyDictError::UnsupportedVersion(0x99))
274 );
275 }
276
277 #[test]
278 fn decode_rejects_truncated() {
279 assert_eq!(KeyDictionary::decode(&[]), Err(KeyDictError::TruncatedData));
280 assert_eq!(
281 KeyDictionary::decode(b"RKDX"),
282 Err(KeyDictError::TruncatedData)
283 );
284 }
285}