Skip to main content

kevy_resp/
argv.rs

1//! The parsed-command argument vector. Two-allocation layout so a SET command
2//! drops from 4 mallocs (Vec of Vec) to 2 (one buffer, one offset table).
3
4/// A parsed command's argument vector.
5///
6/// Stored in **two allocations** — all argument bytes concatenated in `buf`,
7/// with `ends[i]` the end offset of argument `i` — instead of the `N+1` a
8/// `Vec<Vec<u8>>` needs (one outer `Vec` plus one per argument). Parsing a SET
9/// drops from 4 allocations to 2. It is `Send` (two `Vec`s), so the
10/// thread-per-core runtime still forwards it across cores by value.
11///
12/// Index/`get`/`first`/`iter` return `&[u8]` argument slices. It compares equal
13/// to a `Vec<Vec<u8>>` of the same arguments, so call sites and tests read
14/// naturally.
15#[derive(Clone, Default, Debug, Eq)]
16pub struct Argv {
17    buf: Vec<u8>,
18    ends: Vec<u32>,
19}
20
21impl Argv {
22    /// An empty argv, pre-sizing for `argc` args totalling `bytes` bytes.
23    pub fn with_capacity(argc: usize, bytes: usize) -> Self {
24        Argv {
25            buf: Vec::with_capacity(bytes),
26            ends: Vec::with_capacity(argc),
27        }
28    }
29
30    /// Drop all args while keeping the buf + ends capacity. Used by the
31    /// reactor's per-command scratch `Argv`: `parse_command_into` clears
32    /// then refills, so the hot path's malloc rate drops to ~0.
33    #[inline]
34    pub fn clear(&mut self) {
35        self.buf.clear();
36        self.ends.clear();
37    }
38
39    /// Reserve room for `argc` args totalling `bytes` bytes on top of what is
40    /// already there (no shrink).
41    #[inline]
42    pub fn reserve_for(&mut self, argc: usize, bytes: usize) {
43        self.buf.reserve(bytes);
44        self.ends.reserve(argc);
45    }
46
47    /// Capacity of the concatenated-bytes buffer. Drives [`crate::ArgvPool`]'s
48    /// retention policy (don't keep a huge one-off buffer alive in the pool).
49    pub(crate) fn buf_capacity(&self) -> usize {
50        self.buf.capacity()
51    }
52
53    /// Append one argument.
54    pub fn push(&mut self, arg: &[u8]) {
55        self.buf.extend_from_slice(arg);
56        self.ends.push(self.buf.len() as u32);
57    }
58
59    /// Number of arguments.
60    pub fn len(&self) -> usize {
61        self.ends.len()
62    }
63
64    /// Whether there are no arguments.
65    pub fn is_empty(&self) -> bool {
66        self.ends.is_empty()
67    }
68
69    /// Argument `i` as a byte slice, or `None` if out of range.
70    pub fn get(&self, i: usize) -> Option<&[u8]> {
71        let end = *self.ends.get(i)? as usize;
72        let start = if i == 0 { 0 } else { self.ends[i - 1] as usize };
73        Some(&self.buf[start..end])
74    }
75
76    /// The first argument (the command name), or `None` if empty.
77    pub fn first(&self) -> Option<&[u8]> {
78        self.get(0)
79    }
80
81    /// Iterate the arguments as byte slices.
82    // missing_panics_doc: the expect is bounded by the `0..len` loop range —
83    // unreachable, not a caller-facing panic condition.
84    #[allow(clippy::missing_panics_doc)]
85    pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
86        (0..self.len()).map(move |i| self.get(i).expect("in range"))
87    }
88}
89
90impl core::ops::Index<usize> for Argv {
91    type Output = [u8];
92    fn index(&self, i: usize) -> &[u8] {
93        self.get(i).expect("argv index out of bounds")
94    }
95}
96
97/// Compare to a `Vec<Vec<u8>>` of the same arguments (keeps call sites + tests
98/// that build the expected value as a vec-of-vecs readable).
99impl PartialEq<Vec<Vec<u8>>> for Argv {
100    fn eq(&self, other: &Vec<Vec<u8>>) -> bool {
101        self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a == b.as_slice())
102    }
103}
104
105impl PartialEq for Argv {
106    fn eq(&self, other: &Argv) -> bool {
107        self.buf == other.buf && self.ends == other.ends
108    }
109}
110
111/// Build from a vec-of-vecs (test/embedding convenience; the wire path uses
112/// [`parse_command`](crate::parse_command), which builds an [`Argv`] directly
113/// without the intermediate allocations).
114impl From<Vec<Vec<u8>>> for Argv {
115    fn from(v: Vec<Vec<u8>>) -> Self {
116        let mut a = Argv::with_capacity(v.len(), v.iter().map(Vec::len).sum());
117        for arg in &v {
118            a.push(arg);
119        }
120        a
121    }
122}
123
124/// A parsed command: `argv`, where `argv[0]` is the command name.
125pub type Command = Argv;
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn with_capacity_preallocates_both_buffers() {
133        let a: Argv = Argv::with_capacity(4, 32);
134        assert_eq!(a.len(), 0);
135        assert!(a.is_empty());
136        // Underlying Vecs reserve at least what we asked for (no shrink).
137        assert!(a.buf.capacity() >= 32);
138        assert!(a.ends.capacity() >= 4);
139    }
140
141    #[test]
142    fn clear_keeps_capacity_resets_len() {
143        let mut a = Argv::default();
144        a.push(b"foo");
145        a.push(b"barbaz");
146        assert_eq!(a.len(), 2);
147        let cap_buf = a.buf.capacity();
148        let cap_ends = a.ends.capacity();
149        a.clear();
150        assert_eq!(a.len(), 0);
151        assert!(a.is_empty());
152        // Capacities preserved (the reuse contract the hot reactor relies on).
153        assert_eq!(a.buf.capacity(), cap_buf);
154        assert_eq!(a.ends.capacity(), cap_ends);
155    }
156
157    #[test]
158    fn reserve_for_grows_to_at_least_requested() {
159        let mut a = Argv::with_capacity(1, 4);
160        a.reserve_for(8, 64);
161        assert!(a.buf.capacity() >= 64);
162        assert!(a.ends.capacity() >= 8);
163        // reserve_for never shrinks.
164        a.reserve_for(0, 0);
165        assert!(a.buf.capacity() >= 64);
166        assert!(a.ends.capacity() >= 8);
167    }
168
169    #[test]
170    fn push_get_iter_first_round_trip() {
171        let mut a = Argv::default();
172        a.push(b"SET");
173        a.push(b"key");
174        a.push(b"value");
175        assert_eq!(a.len(), 3);
176        assert_eq!(a.first(), Some(b"SET" as &[u8]));
177        assert_eq!(a.get(0), Some(b"SET" as &[u8]));
178        assert_eq!(a.get(1), Some(b"key" as &[u8]));
179        assert_eq!(a.get(2), Some(b"value" as &[u8]));
180        assert_eq!(a.get(3), None);
181        let collected: Vec<&[u8]> = a.iter().collect();
182        assert_eq!(collected, vec![b"SET" as &[u8], b"key", b"value"]);
183    }
184
185    #[test]
186    fn first_on_empty_returns_none() {
187        let a = Argv::default();
188        assert_eq!(a.first(), None);
189        assert_eq!(a.get(0), None);
190    }
191
192    #[test]
193    fn index_returns_correct_slice() {
194        let mut a = Argv::default();
195        a.push(b"hi");
196        a.push(b"there");
197        assert_eq!(&a[0], b"hi" as &[u8]);
198        assert_eq!(&a[1], b"there" as &[u8]);
199    }
200
201    #[test]
202    #[should_panic(expected = "argv index out of bounds")]
203    fn index_out_of_bounds_panics() {
204        let a = Argv::default();
205        let _ = &a[0];
206    }
207
208    #[test]
209    fn eq_against_vec_of_vec() {
210        let mut a = Argv::default();
211        a.push(b"PING");
212        a.push(b"hello");
213        assert_eq!(a, vec![b"PING".to_vec(), b"hello".to_vec()]);
214        assert_ne!(a, vec![b"PING".to_vec()]);
215        assert_ne!(a, vec![b"PING".to_vec(), b"world".to_vec()]);
216    }
217
218    #[test]
219    fn eq_argv_vs_argv() {
220        let mut a = Argv::default();
221        a.push(b"A");
222        a.push(b"B");
223        let mut b = Argv::default();
224        b.push(b"A");
225        b.push(b"B");
226        let mut c = Argv::default();
227        c.push(b"A");
228        c.push(b"C");
229        assert_eq!(a, b);
230        assert_ne!(a, c);
231    }
232
233    #[test]
234    fn from_vec_of_vec_preserves_args() {
235        let v = vec![b"GET".to_vec(), b"my-key".to_vec()];
236        let a: Argv = Argv::from(v.clone());
237        assert_eq!(a.len(), 2);
238        assert_eq!(a, v);
239        // From<Vec<Vec<u8>>> reserves exactly the right total size.
240        assert_eq!(a.buf.len(), 3 + 6);
241    }
242
243    #[test]
244    fn clone_makes_independent_argv() {
245        let mut a = Argv::default();
246        a.push(b"X");
247        let b = a.clone();
248        assert_eq!(a, b);
249        // mutating original doesn't affect clone.
250        a.push(b"Y");
251        assert_ne!(a, b);
252        assert_eq!(b.len(), 1);
253    }
254}