Skip to main content

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}