dynomite/msg/keypos.rs
1//! Per-token position records collected by the protocol parsers.
2//!
3//! The Redis and Memcached parsers identify keys and other argument
4//! tokens inside the wire bytes and surface them on [`Msg`] for the
5//! cluster, fragmenter, and repair layers to consume. This module
6//! defines the two record shapes those parsers emit.
7//!
8//! [`KeyPos`] carries a full key plus an inner sub-range that
9//! identifies the routing tag (the portion between the optional
10//! hash-tag delimiters). [`ArgPos`] carries a single non-key
11//! argument or response token.
12//!
13//! [`Msg`]: crate::msg::Msg
14
15use std::ops::Range;
16
17/// Position record for one parsed key.
18///
19/// The full key bytes live in `key`. When a hash tag is configured
20/// and the tag delimiters are present in the key, `tag` indexes the
21/// range inside `key` that should be hashed for routing; otherwise
22/// `tag` covers the whole key and is equal to `0..key.len()`.
23///
24/// # Examples
25///
26/// ```
27/// use dynomite::msg::keypos::KeyPos;
28///
29/// let kp = KeyPos::without_tag(b"user:42".to_vec());
30/// assert_eq!(kp.key(), b"user:42");
31/// assert_eq!(kp.tag_bytes(), b"user:42");
32/// ```
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct KeyPos {
35 /// Full key bytes.
36 key: Vec<u8>,
37 /// Sub-range of `key` that holds the routing tag.
38 tag: Range<usize>,
39}
40
41impl KeyPos {
42 /// Build a [`KeyPos`] with an explicit tag sub-range.
43 ///
44 /// Panics if `tag.end > key.len()` or `tag.start > tag.end`.
45 ///
46 /// # Examples
47 ///
48 /// ```
49 /// use dynomite::msg::keypos::KeyPos;
50 /// let kp = KeyPos::new(b"{abc}xyz".to_vec(), 1..4);
51 /// assert_eq!(kp.tag_bytes(), b"abc");
52 /// ```
53 #[must_use]
54 pub fn new(key: Vec<u8>, tag: Range<usize>) -> Self {
55 assert!(tag.start <= tag.end, "tag range must be valid");
56 assert!(tag.end <= key.len(), "tag range must be within key");
57 Self { key, tag }
58 }
59
60 /// Build a [`KeyPos`] whose tag covers the entire key.
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use dynomite::msg::keypos::KeyPos;
66 /// let kp = KeyPos::without_tag(b"hello".to_vec());
67 /// assert_eq!(kp.tag_bytes(), b"hello");
68 /// ```
69 #[must_use]
70 pub fn without_tag(key: Vec<u8>) -> Self {
71 let len = key.len();
72 Self { key, tag: 0..len }
73 }
74
75 /// Borrow the full key bytes.
76 #[must_use]
77 pub fn key(&self) -> &[u8] {
78 &self.key
79 }
80
81 /// Length of the full key in bytes.
82 #[must_use]
83 pub fn key_len(&self) -> usize {
84 self.key.len()
85 }
86
87 /// Borrow the tag range.
88 #[must_use]
89 pub fn tag(&self) -> Range<usize> {
90 self.tag.clone()
91 }
92
93 /// Borrow the tag bytes.
94 #[must_use]
95 pub fn tag_bytes(&self) -> &[u8] {
96 &self.key[self.tag.clone()]
97 }
98
99 /// Length of the tag in bytes.
100 #[must_use]
101 pub fn tag_len(&self) -> usize {
102 self.tag.end - self.tag.start
103 }
104}
105
106/// Position record for one non-key argument or response token.
107///
108/// # Examples
109///
110/// ```
111/// use dynomite::msg::keypos::ArgPos;
112/// let arg = ArgPos::new(b"value".to_vec());
113/// assert_eq!(arg.bytes(), b"value");
114/// ```
115#[derive(Clone, Debug, Eq, PartialEq)]
116pub struct ArgPos {
117 data: Vec<u8>,
118}
119
120impl ArgPos {
121 /// Build an argument record from owned bytes.
122 #[must_use]
123 pub fn new(data: Vec<u8>) -> Self {
124 Self { data }
125 }
126
127 /// Borrow the argument bytes.
128 #[must_use]
129 pub fn bytes(&self) -> &[u8] {
130 &self.data
131 }
132
133 /// Length of the argument in bytes.
134 #[must_use]
135 pub fn len(&self) -> usize {
136 self.data.len()
137 }
138
139 /// True when the argument is empty.
140 #[must_use]
141 pub fn is_empty(&self) -> bool {
142 self.data.is_empty()
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn keypos_default_tag_is_full_key() {
152 let kp = KeyPos::without_tag(b"abc".to_vec());
153 assert_eq!(kp.tag_bytes(), b"abc");
154 assert_eq!(kp.tag_len(), 3);
155 assert_eq!(kp.key_len(), 3);
156 }
157
158 #[test]
159 fn keypos_inner_tag_indexes_inside_key() {
160 let kp = KeyPos::new(b"{ab}cd".to_vec(), 1..3);
161 assert_eq!(kp.tag_bytes(), b"ab");
162 assert_eq!(kp.key(), b"{ab}cd");
163 }
164
165 #[test]
166 #[should_panic(expected = "tag range must be valid")]
167 fn keypos_rejects_inverted_range() {
168 let bad = std::ops::Range::<usize> { start: 2, end: 1 };
169 let _ = KeyPos::new(b"abc".to_vec(), bad);
170 }
171
172 #[test]
173 #[should_panic(expected = "tag range must be within key")]
174 fn keypos_rejects_overrun_range() {
175 let _ = KeyPos::new(b"abc".to_vec(), 0..4);
176 }
177
178 #[test]
179 fn argpos_round_trips() {
180 let a = ArgPos::new(vec![1, 2, 3]);
181 assert_eq!(a.len(), 3);
182 assert!(!a.is_empty());
183 assert_eq!(a.bytes(), &[1, 2, 3]);
184 }
185}