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