mongreldb_kit_core/
keys.rs1pub const KIT_KEY_VERSION: u32 = 1;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum KeyComponent {
33 Int(i64),
35 Text(String),
37 Null,
39}
40
41impl KeyComponent {
42 pub fn text(value: impl Into<String>) -> Self {
44 KeyComponent::Text(value.into())
45 }
46}
47
48fn escape_string(value: &str) -> String {
50 value.replace('\\', "\\\\").replace(':', "\\:")
51}
52
53fn unescape_string(value: &str) -> String {
56 value.replace("\\:", ":").replace("\\\\", "\\")
57}
58
59pub fn encode_component(value: &KeyComponent) -> String {
61 match value {
62 KeyComponent::Null => "n:null".to_string(),
63 KeyComponent::Int(i) => format!("i:{i}"),
64 KeyComponent::Text(s) => format!("s:{}", escape_string(s)),
65 }
66}
67
68fn decode_component(token: &str) -> KeyComponent {
73 if let Some(body) = token.strip_prefix("s:") {
74 KeyComponent::Text(unescape_string(body))
75 } else if let Some(body) = token.strip_prefix("i:") {
76 body.parse::<i64>()
79 .map(KeyComponent::Int)
80 .unwrap_or_else(|_| KeyComponent::Text(unescape_string(token)))
81 } else if token.strip_prefix("n:").is_some() {
82 KeyComponent::Null
83 } else {
84 KeyComponent::Text(unescape_string(token))
85 }
86}
87
88fn split_components(encoded: &str) -> Vec<String> {
94 let mut tokens: Vec<String> = Vec::new();
95 let mut current = String::new();
96 let mut escaped = false;
97 for ch in encoded.chars() {
98 if escaped {
99 current.push(ch);
100 escaped = false;
101 continue;
102 }
103 if ch == '\\' {
104 current.push(ch);
105 escaped = true;
106 continue;
107 }
108 if ch == ':' {
109 let starts_typed =
110 current.starts_with("s:") || current.starts_with("i:") || current.starts_with("n:");
111 if starts_typed {
112 tokens.push(std::mem::take(&mut current));
113 continue;
114 }
115 }
116 current.push(ch);
117 }
118 if current.len() >= 2 {
119 tokens.push(current);
120 }
121 tokens
122}
123
124pub fn decode_pk(encoded: &str) -> Vec<KeyComponent> {
126 split_components(encoded)
127 .iter()
128 .map(|t| decode_component(t))
129 .collect()
130}
131
132pub fn encode_pk(values: &[KeyComponent]) -> String {
149 values
150 .iter()
151 .map(encode_component)
152 .collect::<Vec<_>>()
153 .join(":")
154}
155
156pub fn encode_unique_key(
170 kit_version: u32,
171 constraint_name: &str,
172 values: &[KeyComponent],
173) -> String {
174 let components = values
175 .iter()
176 .map(encode_component)
177 .collect::<Vec<_>>()
178 .join(":");
179 format!("uq:{kit_version}:{constraint_name}:{components}")
180}
181
182pub fn encode_row_guard_key(table_name: &str, encoded_pk: &str) -> String {
187 format!("rg:{table_name}:{encoded_pk}")
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
199 fn int_and_text_do_not_collide() {
200 assert_eq!(encode_pk(&[KeyComponent::Int(1)]), "i:1");
201 assert_eq!(encode_pk(&[KeyComponent::text("1")]), "s:1");
202 assert_ne!(
203 encode_pk(&[KeyComponent::Int(1)]),
204 encode_pk(&[KeyComponent::text("1")])
205 );
206 }
207
208 #[test]
209 fn encode_pk_scalar_and_composite() {
210 assert_eq!(encode_pk(&[KeyComponent::text("hello")]), "s:hello");
211 assert_eq!(
212 encode_pk(&[KeyComponent::text("a"), KeyComponent::text("b")]),
213 "s:a:s:b"
214 );
215 assert_eq!(
216 encode_pk(&[KeyComponent::Int(42), KeyComponent::Int(7)]),
217 "i:42:i:7"
218 );
219 }
220
221 #[test]
222 fn encode_unique_key_matches_typescript_vectors() {
223 assert_eq!(
224 encode_unique_key(1, "users_email_uq", &[KeyComponent::text("a@example.com")]),
225 "uq:1:users_email_uq:s:a@example.com"
226 );
227 assert_eq!(
228 encode_unique_key(
229 1,
230 "shares_trip_user_uq",
231 &[KeyComponent::Int(42), KeyComponent::Int(7)]
232 ),
233 "uq:1:shares_trip_user_uq:i:42:i:7"
234 );
235 assert_eq!(
236 encode_unique_key(1, "uq_esc", &[KeyComponent::text("a:b\\c")]),
237 "uq:1:uq_esc:s:a\\:b\\\\c"
238 );
239 assert_eq!(
240 encode_unique_key(1, "uq_null", &[KeyComponent::Null]),
241 "uq:1:uq_null:n:null"
242 );
243 }
244
245 #[test]
246 fn encode_row_guard_key_matches_typescript_vectors() {
247 assert_eq!(
248 encode_row_guard_key("trips", &encode_pk(&[KeyComponent::Int(5)])),
249 "rg:trips:i:5"
250 );
251 assert_eq!(
252 encode_row_guard_key("users", &encode_pk(&[KeyComponent::text("alpha")])),
253 "rg:users:s:alpha"
254 );
255 }
256
257 #[test]
258 fn pk_round_trips_through_decode() {
259 let cases = vec![
260 vec![KeyComponent::Int(1)],
261 vec![KeyComponent::Int(-99)],
262 vec![KeyComponent::text("alpha")],
263 vec![KeyComponent::text("a"), KeyComponent::text("b")],
264 vec![KeyComponent::Int(1), KeyComponent::text("x")],
265 ];
266 for case in cases {
267 let encoded = encode_pk(&case);
268 assert_eq!(decode_pk(&encoded), case, "round-trip failed for {encoded}");
269 }
270 }
271
272 #[test]
273 fn decode_handles_typed_prefixes() {
274 assert_eq!(decode_pk("i:5"), vec![KeyComponent::Int(5)]);
275 assert_eq!(decode_pk("s:hi"), vec![KeyComponent::text("hi")]);
276 assert_eq!(decode_pk("n:null"), vec![KeyComponent::Null]);
277 }
278
279 #[test]
280 fn decode_does_not_panic_on_malformed_multibyte_input() {
281 let _ = decode_pk("中");
284 let _ = decode_pk("中:foo");
285 let _ = decode_pk("");
286 assert_eq!(decode_pk("中"), vec![KeyComponent::text("中")]);
287 assert_eq!(decode_pk("i:abc"), vec![KeyComponent::text("i:abc")]);
288 }
289}