Skip to main content

yo_resp/
reply.rs

1//! Replies out: wire bytes, written once.
2//!
3//! This is Y18 and it is a locked decision rather than a preference. A reply is
4//! built directly as the bytes that go on the socket. There is no intermediate
5//! value, no enum to match on later, no boxed trait object, and no second pass
6//! that turns a structure into bytes. Redis 8.8 gained forty percent on `SCAN`
7//! by fixing exactly this in its own reply path, which is the best available
8//! evidence that the shape matters more than the constant factors inside it.
9//!
10//! The other half of Y18 is presizing. A reply whose size is known should
11//! reserve once, before the first byte, from whichever side of the operation is
12//! smaller. [`Out::reserve`] and the `*_len` helpers are there so that a command
13//! can do that arithmetic without writing anything twice.
14//!
15//! # The protocol lives here
16//!
17//! A command writes a map. Whether that map goes out as RESP3's `%` or as
18//! RESP2's flattened array is this module's problem and not the command's. Every
19//! command in the engine is written once, against the richer protocol, and the
20//! downgrade happens in one place where it can be tested. The alternative is a
21//! protocol check in three hundred command implementations, and the failure
22//! mode of that is a command that works on one protocol and not the other.
23
24use crate::proto::Proto;
25use yo_common::num::{
26    DIGITS_MAX, i64_len, push_double, push_human, push_i64, push_u64, u64_digits, u64_len,
27};
28
29/// A reply buffer for one connection.
30///
31/// Owns its bytes so that a connection can fill it across several commands and
32/// hand the whole thing to one write, which is `04` section 5: one `writev` per
33/// connection per batch, not one per reply.
34#[derive(Debug, Clone)]
35pub struct Out {
36    buf: Vec<u8>,
37    proto: Proto,
38}
39
40impl Out {
41    /// An empty buffer speaking `proto`.
42    pub fn new(proto: Proto) -> Out {
43        Out {
44            buf: Vec::new(),
45            proto,
46        }
47    }
48
49    /// An empty buffer with room already reserved.
50    pub fn with_capacity(proto: Proto, cap: usize) -> Out {
51        Out {
52            buf: Vec::with_capacity(cap),
53            proto,
54        }
55    }
56
57    /// The protocol this connection is speaking.
58    #[inline]
59    pub const fn proto(&self) -> Proto {
60        self.proto
61    }
62
63    /// Switches protocol, which is what `HELLO` does.
64    ///
65    /// Takes effect from the next reply written. `HELLO`'s own reply is written
66    /// in the new protocol, which is why this is called before it rather than
67    /// after.
68    #[inline]
69    pub const fn set_proto(&mut self, proto: Proto) {
70        self.proto = proto;
71    }
72
73    /// The bytes written so far.
74    #[inline]
75    pub fn as_slice(&self) -> &[u8] {
76        &self.buf
77    }
78
79    /// How many bytes are pending.
80    #[inline]
81    pub fn len(&self) -> usize {
82        self.buf.len()
83    }
84
85    /// How much room it is holding, which is what it costs the process.
86    ///
87    /// A reply buffer keeps its capacity between batches on purpose, so `len`
88    /// is what a client is owed and this is what the memory report owes.
89    #[inline]
90    pub fn capacity(&self) -> usize {
91        self.buf.capacity()
92    }
93
94    /// Whether nothing is pending.
95    #[inline]
96    pub fn is_empty(&self) -> bool {
97        self.buf.is_empty()
98    }
99
100    /// Drops everything written, keeping the capacity.
101    ///
102    /// Called after the batch has been written to the socket. The capacity is
103    /// what stops a busy connection from allocating again.
104    #[inline]
105    pub fn clear(&mut self) {
106        self.buf.clear();
107    }
108
109    /// Drops the first `n` bytes, which is what a partial write leaves behind.
110    ///
111    /// # Panics
112    ///
113    /// If `n` is past the end of what has been written.
114    pub fn consume(&mut self, n: usize) {
115        assert!(n <= self.buf.len(), "consumed past the end of the reply");
116        self.buf.drain(..n);
117    }
118
119    /// Drops everything written after `len`, which has to be a length this
120    /// buffer reported earlier.
121    ///
122    /// The dispatcher takes the length before it runs a command and rolls back
123    /// to it when the command answers with an error, so a command that writes
124    /// half a reply and then fails cannot leave the half on the wire. Every
125    /// command is written to check its arguments before it writes anything,
126    /// and this is what makes that a property of the dispatcher rather than a
127    /// rule three hundred commands have to keep to.
128    #[inline]
129    pub fn truncate(&mut self, len: usize) {
130        self.buf.truncate(len);
131    }
132
133    /// Reserves room for `n` more bytes.
134    ///
135    /// The presize half of Y18. Call it once with the whole reply's size before
136    /// writing any of it.
137    #[inline]
138    pub fn reserve(&mut self, n: usize) {
139        self.buf.reserve(n);
140    }
141
142    /// The buffer, taken.
143    pub fn into_inner(self) -> Vec<u8> {
144        self.buf
145    }
146
147    /// Raw bytes, appended as they are.
148    ///
149    /// For a reply that was assembled elsewhere, such as a cached `COMMAND
150    /// DOCS` payload or a replicated frame passing through. Nothing checks that
151    /// what goes in is a valid frame, which is the point.
152    #[inline]
153    pub fn raw(&mut self, bytes: &[u8]) {
154        self.buf.extend_from_slice(bytes);
155    }
156
157    // Simple strings, errors and integers. These three are the same in both
158    // protocols, which is why none of them looks at `self.proto`.
159
160    /// A simple string, `+s\r\n`. No CR or LF may appear in `s`.
161    #[inline]
162    pub fn simple(&mut self, s: &[u8]) {
163        debug_assert!(
164            !s.contains(&b'\r') && !s.contains(&b'\n'),
165            "a simple string cannot carry a line ending, use a bulk string"
166        );
167        self.buf.reserve(s.len() + 3);
168        self.buf.push(b'+');
169        self.buf.extend_from_slice(s);
170        self.crlf();
171    }
172
173    /// `+OK\r\n`, which is most of what a write command replies.
174    #[inline]
175    pub fn ok(&mut self) {
176        self.buf.extend_from_slice(b"+OK\r\n");
177    }
178
179    /// An error, `-msg\r\n`.
180    ///
181    /// `msg` carries its own prefix, because the prefix is part of the
182    /// contract: a client branches on `WRONGTYPE` or `MOVED` or `NOAUTH`, and
183    /// which one applies is the command's decision and not the codec's. The
184    /// full taxonomy is in `12` section 1.
185    #[inline]
186    pub fn error(&mut self, msg: &[u8]) {
187        debug_assert!(
188            !msg.contains(&b'\r') && !msg.contains(&b'\n'),
189            "an error line cannot carry a line ending"
190        );
191        self.buf.reserve(msg.len() + 3);
192        self.buf.push(b'-');
193        self.buf.extend_from_slice(msg);
194        self.crlf();
195    }
196
197    /// An error built from a prefix and a message that are not next to each
198    /// other in memory, with any line ending in the message turned into a
199    /// space.
200    ///
201    /// The prefix carries its own trailing space, so this is called with
202    /// `b"ERR "` or `b"WRONGTYPE "`. Joining the two halves first would mean
203    /// allocating a string on the failure path of a thread that is not allowed
204    /// to allocate, which is the whole reason this exists.
205    ///
206    /// The mapping of `\r` and `\n` to spaces is Redis's, and it is not
207    /// cosmetic: an error message can quote what the client sent, and a client
208    /// that sends a command name with a newline in it would otherwise be
209    /// writing its own frames into somebody's reply stream.
210    pub fn error_line(&mut self, prefix: &[u8], msg: &[u8]) {
211        self.buf.reserve(prefix.len() + msg.len() + 4);
212        self.buf.push(b'-');
213        self.buf.extend_from_slice(prefix);
214        for &b in msg {
215            self.buf
216                .push(if b == b'\r' || b == b'\n' { b' ' } else { b });
217        }
218        self.crlf();
219    }
220
221    /// A blob error, RESP3's `!`, which may carry anything including newlines.
222    ///
223    /// Degrades to a normal error line in RESP2, with line endings turned into
224    /// spaces, because a RESP2 error is one line by definition.
225    pub fn blob_error(&mut self, msg: &[u8]) {
226        if self.proto.is_resp3() {
227            self.blob(b'!', msg);
228        } else {
229            self.buf.reserve(msg.len() + 3);
230            self.buf.push(b'-');
231            for &b in msg {
232                self.buf
233                    .push(if b == b'\r' || b == b'\n' { b' ' } else { b });
234            }
235            self.crlf();
236        }
237    }
238
239    /// An integer, `:n\r\n`.
240    #[inline]
241    pub fn int(&mut self, n: i64) {
242        self.buf.reserve(i64_len(n) + 3);
243        self.buf.push(b':');
244        push_i64(&mut self.buf, n);
245        self.crlf();
246    }
247
248    /// An unsigned integer, `:n\r\n`.
249    ///
250    /// Not the same as [`Out::int`] for the numbers with bit 63 set, and that is
251    /// the only reason it exists. `ARLEN` on a key with something at the top of
252    /// the index space is eighteen quintillion, which the signed path would put
253    /// on the wire as a negative number. Redis has the same pair of writers and
254    /// uses the unsigned one for exactly these replies.
255    #[inline]
256    pub fn uint(&mut self, n: u64) {
257        self.buf.reserve(u64_len(n) + 3);
258        self.buf.push(b':');
259        push_u64(&mut self.buf, n);
260        self.crlf();
261    }
262
263    // Strings.
264
265    /// A bulk string, `$len\r\n...\r\n`.
266    #[inline]
267    pub fn bulk(&mut self, s: &[u8]) {
268        self.blob(b'$', s);
269    }
270
271    /// A bulk string holding the decimal form of `n`.
272    ///
273    /// Written straight into the buffer rather than through a temporary, which
274    /// is worth having as its own method because several commands reply with a
275    /// number as a string and every one of them would otherwise allocate.
276    pub fn bulk_int(&mut self, n: i64) {
277        let digits = i64_len(n);
278        self.buf.reserve(digits + 16);
279        self.buf.push(b'$');
280        push_u64(&mut self.buf, digits as u64);
281        self.crlf();
282        push_i64(&mut self.buf, n);
283        self.crlf();
284    }
285
286    /// A bulk string holding the decimal form of `n`, unsigned.
287    ///
288    /// Not the same as [`Out::bulk_int`] for the numbers with bit 63 set, which
289    /// is the only reason it exists: a scan cursor packs a partition count into
290    /// the top bits, so a big enough collection hands back a number that the
291    /// signed path would report as negative and no client would send back.
292    pub fn bulk_u64(&mut self, n: u64) {
293        let mut digits = [0u8; DIGITS_MAX];
294        self.bulk(u64_digits(&mut digits, n));
295    }
296
297    /// A bulk string holding a double in Redis's own formatting.
298    ///
299    /// A score written into a flat RESP2 reply arrives here rather than at
300    /// [`Out::double`], because there is no protocol choice left to make by
301    /// then.
302    ///
303    /// The length has to go in front of the digits and the digits cannot be
304    /// counted without writing them, so they are written first, the header is
305    /// appended behind them, and the two are rotated into place. A double is a
306    /// couple of dozen bytes at most, so the rotate is a few words, and nothing
307    /// is allocated to hold a number on its way into a buffer it is already in.
308    pub fn bulk_double(&mut self, d: f64) {
309        self.bulk_written(|buf| push_double(buf, d));
310    }
311
312    /// A bulk string holding a double the way the two float increments write
313    /// one.
314    ///
315    /// `INCRBYFLOAT` and `HINCRBYFLOAT` go through `ld2string` in its human
316    /// mode where every other double goes through `d2string`, and the two
317    /// disagree about large and small magnitudes: this one never writes an
318    /// exponent. Both of them reply with a bulk string on RESP2 and on RESP3
319    /// alike, so unlike [`Out::double`] there is no protocol branch here.
320    pub fn human_double(&mut self, d: f64) {
321        self.bulk_written(|buf| push_human(buf, d));
322    }
323
324    /// A bulk string whose contents are written by `f` and measured after.
325    fn bulk_written(&mut self, f: impl FnOnce(&mut Vec<u8>)) {
326        self.buf.reserve(48);
327        let start = self.buf.len();
328        f(&mut self.buf);
329        let digits = self.buf.len() - start;
330        self.buf.push(b'$');
331        push_u64(&mut self.buf, digits as u64);
332        self.crlf();
333        let header = self.buf.len() - start - digits;
334        self.buf[start..].rotate_right(header);
335        self.crlf();
336    }
337
338    /// A verbatim string, RESP3's `=`, with a three byte format such as `txt`
339    /// or `mkd`.
340    ///
341    /// RESP2 has no such type and gets a plain bulk string of the text, without
342    /// the format prefix, which is what Redis does.
343    pub fn verbatim(&mut self, format: &[u8; 3], text: &[u8]) {
344        if !self.proto.is_resp3() {
345            self.bulk(text);
346            return;
347        }
348        let len = text.len() + 4;
349        self.buf.reserve(len + 16);
350        self.buf.push(b'=');
351        push_u64(&mut self.buf, len as u64);
352        self.crlf();
353        self.buf.extend_from_slice(format);
354        self.buf.push(b':');
355        self.buf.extend_from_slice(text);
356        self.crlf();
357    }
358
359    /// A big number, RESP3's `(`, given as its decimal digits.
360    ///
361    /// RESP2 gets a bulk string of the same digits, which is what Redis does
362    /// and what every client already handles.
363    pub fn big_number(&mut self, digits: &[u8]) {
364        if self.proto.is_resp3() {
365            self.buf.reserve(digits.len() + 3);
366            self.buf.push(b'(');
367            self.buf.extend_from_slice(digits);
368            self.crlf();
369        } else {
370            self.bulk(digits);
371        }
372    }
373
374    // The types RESP3 added.
375
376    /// Nothing, where a string was expected.
377    ///
378    /// RESP3 has one null. RESP2 has two, and this is the one that stands in
379    /// for a missing string, which is what `GET` on a missing key returns.
380    #[inline]
381    pub fn nil(&mut self) {
382        self.buf.extend_from_slice(if self.proto.is_resp3() {
383            b"_\r\n"
384        } else {
385            b"$-1\r\n"
386        });
387    }
388
389    /// Nothing, where an array was expected.
390    ///
391    /// The other RESP2 null. `EXEC` on a dirty `WATCH` returns this one, and a
392    /// client that tells the two apart will notice if the wrong one is sent.
393    #[inline]
394    pub fn nil_array(&mut self) {
395        self.buf.extend_from_slice(if self.proto.is_resp3() {
396            b"_\r\n"
397        } else {
398            b"*-1\r\n"
399        });
400    }
401
402    /// A double, RESP3's `,`.
403    ///
404    /// RESP2 gets a bulk string of the same digits. The infinities and NaN are
405    /// written as words in both.
406    pub fn double(&mut self, d: f64) {
407        if self.proto.is_resp3() {
408            self.buf.reserve(32);
409            self.buf.push(b',');
410            push_double(&mut self.buf, d);
411            self.crlf();
412            return;
413        }
414        // RESP2 has no double and gets the digits as a bulk string, which is
415        // the same thing `INCRBYFLOAT` replies with on both protocols.
416        self.bulk_double(d);
417    }
418
419    /// A boolean, RESP3's `#t` or `#f`.
420    ///
421    /// RESP2 gets `:1` or `:0`, which is what every command that returns a
422    /// boolean has always returned there.
423    #[inline]
424    pub fn bool(&mut self, b: bool) {
425        self.buf
426            .extend_from_slice(match (self.proto.is_resp3(), b) {
427                (true, true) => b"#t\r\n",
428                (true, false) => b"#f\r\n",
429                (false, true) => b":1\r\n",
430                (false, false) => b":0\r\n",
431            });
432    }
433
434    // Aggregates. Each of these writes only the header; the caller then writes
435    // the elements. That is what makes a reply streamable without the codec
436    // needing to hold it.
437
438    /// An array header for `n` elements. The caller writes the elements next.
439    #[inline]
440    pub fn array(&mut self, n: usize) {
441        self.header(b'*', n);
442    }
443
444    /// Move the last `tail` bytes back to `start`, so that something written
445    /// after a reply ends up in front of it.
446    ///
447    /// Not every reply knows how long it is before it has been written. `SSCAN`
448    /// walks a window of the set and drops the members that do not match its
449    /// pattern, so the count is only true once the last member has been looked
450    /// at, and it answers with a cursor that the same walk produced. The
451    /// alternatives are both worse: walking the window twice runs the glob
452    /// twice, and collecting the members first is an allocation per call on a
453    /// thread that must not allocate.
454    ///
455    /// Redis solves this with a linked list of reply nodes it can patch in
456    /// place. There is one flat buffer here, so the piece that belongs in front
457    /// is written behind and the two are rotated past each other, which is the
458    /// trick [`Out::bulk_double`] already uses and costs one move of bytes that
459    /// were about to be moved to a socket anyway.
460    ///
461    /// # Panics
462    ///
463    /// If `start` is past the end, or `tail` is longer than what follows it.
464    pub fn hoist(&mut self, start: usize, tail: usize) {
465        assert!(
466            start + tail <= self.buf.len(),
467            "hoisted more than was written"
468        );
469        self.buf[start..].rotate_right(tail);
470    }
471
472    /// An array header for the elements written since `start`, which has to be
473    /// a length this buffer reported earlier.
474    ///
475    /// [`Out::hoist`] is why this can be called after the elements rather than
476    /// before them.
477    pub fn close_array(&mut self, start: usize, n: usize) {
478        self.close(b'*', start, n);
479    }
480
481    /// The same for a set, which is what the algebra commands answer.
482    ///
483    /// `SINTER` cannot count its own reply in advance any more than `SSCAN`
484    /// can. The answer is however many members survived a walk over the
485    /// smallest set, and finding that out ahead of writing it means running the
486    /// whole operation twice.
487    pub fn close_set(&mut self, start: usize, n: usize) {
488        self.close(if self.proto.is_resp3() { b'~' } else { b'*' }, start, n);
489    }
490
491    /// Write a header of `tag` for `n` elements behind the elements, then move
492    /// it in front of them.
493    fn close(&mut self, tag: u8, start: usize, n: usize) {
494        let body = self.buf.len() - start;
495        self.buf.push(tag);
496        push_u64(&mut self.buf, n as u64);
497        self.crlf();
498        let header = self.buf.len() - start - body;
499        self.hoist(start, header);
500    }
501
502    /// A map header for `n` pairs. The caller writes `2 * n` elements next,
503    /// key then value, `n` times.
504    ///
505    /// RESP2 has no map and gets a flat array of twice as many elements, which
506    /// is exactly what a RESP2 client already expects from `HGETALL` and
507    /// `CONFIG GET`. The command does not know which one it wrote.
508    #[inline]
509    pub fn map(&mut self, n: usize) {
510        if self.proto.is_resp3() {
511            self.header(b'%', n);
512        } else {
513            self.header(b'*', n * 2);
514        }
515    }
516
517    /// A set header for `n` elements.
518    ///
519    /// RESP2 has no set and gets an array, which is what `SMEMBERS` has always
520    /// returned there.
521    #[inline]
522    pub fn set(&mut self, n: usize) {
523        self.header(if self.proto.is_resp3() { b'~' } else { b'*' }, n);
524    }
525
526    /// A push header for `n` elements, RESP3's `>`.
527    ///
528    /// This is how pub/sub messages and client side caching invalidations are
529    /// delivered. RESP2 has no out of band type, so they go out as plain
530    /// arrays on the same connection, which is how RESP2 pub/sub has always
531    /// worked and is why a RESP2 connection in subscribe mode can only do a
532    /// handful of things.
533    #[inline]
534    pub fn push(&mut self, n: usize) {
535        self.header(if self.proto.is_resp3() { b'>' } else { b'*' }, n);
536    }
537
538    /// An attribute header for `n` pairs, RESP3's `|`.
539    ///
540    /// Attributes are metadata attached to the frame that follows. RESP2 cannot
541    /// carry them at all, so the caller must check [`Out::proto`] before
542    /// writing one. There is no downgrade, because turning metadata into a
543    /// reply element would corrupt the reply.
544    ///
545    /// # Panics
546    ///
547    /// In debug, if the connection is not speaking RESP3.
548    #[inline]
549    pub fn attribute(&mut self, n: usize) {
550        debug_assert!(
551            self.proto.is_resp3(),
552            "RESP2 has no attributes, check the protocol first"
553        );
554        self.header(b'|', n);
555    }
556
557    // Sizes, for the presize half of Y18.
558
559    /// The exact number of bytes [`Out::bulk`] would write for a value of this
560    /// length.
561    #[inline]
562    pub const fn bulk_len(value_len: usize) -> usize {
563        // `$`, the digits, CRLF, the body, CRLF.
564        1 + digits_of(value_len as u64) + 2 + value_len + 2
565    }
566
567    /// The exact number of bytes [`Out::int`] would write.
568    #[inline]
569    pub const fn int_len(n: i64) -> usize {
570        1 + i64_len(n) + 2
571    }
572
573    /// The exact number of bytes an aggregate header of `n` elements would
574    /// write, in either protocol, since both write one byte and the count.
575    #[inline]
576    pub const fn header_len(n: usize) -> usize {
577        1 + digits_of(n as u64) + 2
578    }
579
580    #[inline]
581    fn header(&mut self, kind: u8, n: usize) {
582        self.buf.reserve(24);
583        self.buf.push(kind);
584        push_u64(&mut self.buf, n as u64);
585        self.crlf();
586    }
587
588    /// A length prefixed blob: `$`, `!` and `=` all have this shape.
589    #[inline]
590    fn blob(&mut self, kind: u8, s: &[u8]) {
591        self.buf.reserve(Out::bulk_len(s.len()));
592        self.buf.push(kind);
593        push_u64(&mut self.buf, s.len() as u64);
594        self.crlf();
595        self.buf.extend_from_slice(s);
596        self.crlf();
597    }
598
599    #[inline]
600    fn crlf(&mut self) {
601        self.buf.extend_from_slice(b"\r\n");
602    }
603}
604
605/// How many decimal digits `n` needs.
606const fn digits_of(n: u64) -> usize {
607    let mut d = 1;
608    let mut v = n;
609    while v >= 10 {
610        v /= 10;
611        d += 1;
612    }
613    d
614}
615
616#[cfg(test)]
617mod tests {
618    use super::*;
619
620    /// Runs `f` on a fresh buffer in both protocols and returns what each one
621    /// produced. Every downgrade test below is written as one call, because the
622    /// point being made is always that the same command wrote both.
623    fn both(f: impl Fn(&mut Out)) -> (String, String) {
624        let mut two = Out::new(Proto::Resp2);
625        let mut three = Out::new(Proto::Resp3);
626        f(&mut two);
627        f(&mut three);
628        (
629            String::from_utf8(two.into_inner()).unwrap(),
630            String::from_utf8(three.into_inner()).unwrap(),
631        )
632    }
633
634    fn one(proto: Proto, f: impl Fn(&mut Out)) -> String {
635        let mut out = Out::new(proto);
636        f(&mut out);
637        String::from_utf8(out.into_inner()).unwrap()
638    }
639
640    #[test]
641    fn the_three_types_both_protocols_share_are_written_the_same_way() {
642        let (two, three) = both(|o| {
643            o.simple(b"PONG");
644            o.error(b"WRONGTYPE Operation against a key holding the wrong kind of value");
645            o.int(-42);
646            o.ok();
647        });
648        assert_eq!(two, three);
649        assert_eq!(
650            two,
651            "+PONG\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n:-42\r\n+OK\r\n"
652        );
653    }
654
655    #[test]
656    fn a_bulk_string_carries_its_length_and_anything_in_it() {
657        let (two, three) = both(|o| {
658            o.bulk(b"hello");
659            o.bulk(b"");
660            o.bulk(b"a\r\nb");
661        });
662        assert_eq!(two, three);
663        assert_eq!(two, "$5\r\nhello\r\n$0\r\n\r\n$4\r\na\r\nb\r\n");
664    }
665
666    #[test]
667    fn a_number_as_a_string_gets_the_right_length() {
668        assert_eq!(one(Proto::Resp2, |o| o.bulk_int(0)), "$1\r\n0\r\n");
669        assert_eq!(one(Proto::Resp2, |o| o.bulk_int(-1234)), "$5\r\n-1234\r\n");
670        assert_eq!(
671            one(Proto::Resp2, |o| o.bulk_int(i64::MIN)),
672            "$20\r\n-9223372036854775808\r\n"
673        );
674    }
675
676    #[test]
677    fn an_array_can_be_headed_after_its_elements_are_written() {
678        let (two, three) = both(|o| {
679            let start = o.len();
680            o.bulk(b"a");
681            o.bulk(b"bb");
682            o.close_array(start, 2);
683        });
684        assert_eq!(two, three);
685        assert_eq!(two, "*2\r\n$1\r\na\r\n$2\r\nbb\r\n");
686
687        // And it leaves whatever was already in the buffer where it was, which
688        // is the part a rotate can get wrong.
689        assert_eq!(
690            one(Proto::Resp2, |o| {
691                o.int(1);
692                let start = o.len();
693                o.bulk(b"x");
694                o.close_array(start, 1);
695            }),
696            ":1\r\n*1\r\n$1\r\nx\r\n"
697        );
698
699        // An empty one, and a header of more than one digit, which is where the
700        // rotate distance stops being a constant.
701        assert_eq!(
702            one(Proto::Resp2, |o| {
703                let start = o.len();
704                o.close_array(start, 0);
705            }),
706            "*0\r\n"
707        );
708        let long = one(Proto::Resp2, |o| {
709            let start = o.len();
710            for _ in 0..100 {
711                o.int(7);
712            }
713            o.close_array(start, 100);
714        });
715        assert!(long.starts_with("*100\r\n:7\r\n"));
716        assert!(long.ends_with(":7\r\n"));
717        assert_eq!(long.len(), "*100\r\n".len() + 100 * ":7\r\n".len());
718    }
719
720    #[test]
721    fn resp2_has_two_nulls_and_resp3_has_one() {
722        let (two, three) = both(|o| {
723            o.nil();
724            o.nil_array();
725        });
726        assert_eq!(two, "$-1\r\n*-1\r\n");
727        assert_eq!(three, "_\r\n_\r\n");
728    }
729
730    #[test]
731    fn a_map_becomes_a_flat_array_on_resp2() {
732        let (two, three) = both(|o| {
733            o.map(2);
734            o.bulk(b"a");
735            o.bulk(b"1");
736            o.bulk(b"b");
737            o.bulk(b"2");
738        });
739        assert_eq!(two, "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n");
740        assert_eq!(three, "%2\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n");
741    }
742
743    #[test]
744    fn a_set_and_a_push_become_arrays_on_resp2() {
745        let (two, three) = both(|o| {
746            o.set(1);
747            o.bulk(b"x");
748            o.push(2);
749            o.bulk(b"message");
750            o.bulk(b"ch");
751        });
752        assert_eq!(two, "*1\r\n$1\r\nx\r\n*2\r\n$7\r\nmessage\r\n$2\r\nch\r\n");
753        assert_eq!(
754            three,
755            "~1\r\n$1\r\nx\r\n>2\r\n$7\r\nmessage\r\n$2\r\nch\r\n"
756        );
757    }
758
759    #[test]
760    fn a_boolean_is_an_integer_on_resp2() {
761        let (two, three) = both(|o| {
762            o.bool(true);
763            o.bool(false);
764        });
765        assert_eq!(two, ":1\r\n:0\r\n");
766        assert_eq!(three, "#t\r\n#f\r\n");
767    }
768
769    #[test]
770    fn a_double_is_a_bulk_string_on_resp2() {
771        let (two, three) = both(|o| {
772            o.double(1.5);
773            o.double(3.0);
774            o.double(f64::INFINITY);
775        });
776        assert_eq!(two, "$3\r\n1.5\r\n$1\r\n3\r\n$3\r\ninf\r\n");
777        assert_eq!(three, ",1.5\r\n,3\r\n,inf\r\n");
778    }
779
780    #[test]
781    fn a_verbatim_string_loses_its_format_on_resp2() {
782        let (two, three) = both(|o| o.verbatim(b"txt", b"Some string"));
783        assert_eq!(two, "$11\r\nSome string\r\n");
784        assert_eq!(three, "=15\r\ntxt:Some string\r\n");
785    }
786
787    #[test]
788    fn a_big_number_is_a_bulk_string_on_resp2() {
789        let n = b"3492890328409238509324850943850943825024385";
790        let (two, three) = both(|o| o.big_number(n));
791        assert_eq!(
792            two,
793            format!("${}\r\n{}\r\n", n.len(), str::from_utf8(n).unwrap())
794        );
795        assert_eq!(three, format!("({}\r\n", str::from_utf8(n).unwrap()));
796    }
797
798    #[test]
799    fn a_blob_error_keeps_its_newlines_on_resp3_and_loses_them_on_resp2() {
800        let (two, three) = both(|o| o.blob_error(b"SYNTAX bad\nline two"));
801        assert_eq!(two, "-SYNTAX bad line two\r\n");
802        assert_eq!(three, "!19\r\nSYNTAX bad\nline two\r\n");
803    }
804
805    /// The sizes are what a command presizes from, so a size that is wrong by
806    /// one is a reply that reallocates on every call and nobody notices.
807    #[test]
808    fn the_predicted_sizes_are_the_sizes_actually_written() {
809        for len in [0usize, 1, 9, 10, 99, 100, 1000, 65536] {
810            let value = vec![b'x'; len];
811            let written = one(Proto::Resp2, |o| o.bulk(&value));
812            assert_eq!(Out::bulk_len(len), written.len(), "bulk of {len}");
813        }
814        for n in [0i64, 7, -7, 100, i64::MAX, i64::MIN] {
815            let written = one(Proto::Resp2, |o| o.int(n));
816            assert_eq!(Out::int_len(n), written.len(), "int {n}");
817        }
818        for n in [0usize, 5, 1234] {
819            let written = one(Proto::Resp2, |o| o.array(n));
820            assert_eq!(Out::header_len(n), written.len(), "array header {n}");
821        }
822    }
823
824    #[test]
825    fn hello_switches_the_protocol_for_everything_after_it() {
826        let mut out = Out::new(Proto::Resp2);
827        out.nil();
828        out.set_proto(Proto::Resp3);
829        out.nil();
830        assert_eq!(out.as_slice(), b"$-1\r\n_\r\n");
831    }
832
833    #[test]
834    fn a_partial_write_leaves_the_rest_behind() {
835        let mut out = Out::new(Proto::Resp2);
836        out.ok();
837        out.ok();
838        out.consume(5);
839        assert_eq!(out.as_slice(), b"+OK\r\n");
840        out.clear();
841        assert!(out.is_empty());
842    }
843}