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    /// An error line with a word the client sent quoted in the middle of it.
223    ///
224    /// Several of the search errors read `Unknown argument \`x\`` and name the
225    /// word that was not understood, so the line is the server's own text, then
226    /// the client's bytes, then the server's text again. Only the middle piece
227    /// can carry a line ending and only the middle piece has them taken out,
228    /// for the reason [`Out::error_line`] gives.
229    pub fn error_about(&mut self, before: &[u8], word: &[u8], after: &[u8]) {
230        debug_assert!(
231            !before.contains(&b'\r') && !before.contains(&b'\n'),
232            "an error line cannot carry a line ending"
233        );
234        self.buf
235            .reserve(before.len() + word.len() + after.len() + 3);
236        self.buf.push(b'-');
237        self.buf.extend_from_slice(before);
238        for &b in word {
239            self.buf
240                .push(if b == b'\r' || b == b'\n' { b' ' } else { b });
241        }
242        self.buf.extend_from_slice(after);
243        self.crlf();
244    }
245
246    /// A blob error, RESP3's `!`, which may carry anything including newlines.
247    ///
248    /// Degrades to a normal error line in RESP2, with line endings turned into
249    /// spaces, because a RESP2 error is one line by definition.
250    pub fn blob_error(&mut self, msg: &[u8]) {
251        if self.proto.is_resp3() {
252            self.blob(b'!', msg);
253        } else {
254            self.buf.reserve(msg.len() + 3);
255            self.buf.push(b'-');
256            for &b in msg {
257                self.buf
258                    .push(if b == b'\r' || b == b'\n' { b' ' } else { b });
259            }
260            self.crlf();
261        }
262    }
263
264    /// An integer, `:n\r\n`.
265    #[inline]
266    pub fn int(&mut self, n: i64) {
267        self.buf.reserve(i64_len(n) + 3);
268        self.buf.push(b':');
269        push_i64(&mut self.buf, n);
270        self.crlf();
271    }
272
273    /// An unsigned integer, `:n\r\n`.
274    ///
275    /// Not the same as [`Out::int`] for the numbers with bit 63 set, and that is
276    /// the only reason it exists. `ARLEN` on a key with something at the top of
277    /// the index space is eighteen quintillion, which the signed path would put
278    /// on the wire as a negative number. Redis has the same pair of writers and
279    /// uses the unsigned one for exactly these replies.
280    #[inline]
281    pub fn uint(&mut self, n: u64) {
282        self.buf.reserve(u64_len(n) + 3);
283        self.buf.push(b':');
284        push_u64(&mut self.buf, n);
285        self.crlf();
286    }
287
288    // Strings.
289
290    /// A bulk string, `$len\r\n...\r\n`.
291    #[inline]
292    pub fn bulk(&mut self, s: &[u8]) {
293        self.blob(b'$', s);
294    }
295
296    /// A bulk string holding the decimal form of `n`.
297    ///
298    /// Written straight into the buffer rather than through a temporary, which
299    /// is worth having as its own method because several commands reply with a
300    /// number as a string and every one of them would otherwise allocate.
301    pub fn bulk_int(&mut self, n: i64) {
302        let digits = i64_len(n);
303        self.buf.reserve(digits + 16);
304        self.buf.push(b'$');
305        push_u64(&mut self.buf, digits as u64);
306        self.crlf();
307        push_i64(&mut self.buf, n);
308        self.crlf();
309    }
310
311    /// A bulk string holding the decimal form of `n`, unsigned.
312    ///
313    /// Not the same as [`Out::bulk_int`] for the numbers with bit 63 set, which
314    /// is the only reason it exists: a scan cursor packs a partition count into
315    /// the top bits, so a big enough collection hands back a number that the
316    /// signed path would report as negative and no client would send back.
317    pub fn bulk_u64(&mut self, n: u64) {
318        let mut digits = [0u8; DIGITS_MAX];
319        self.bulk(u64_digits(&mut digits, n));
320    }
321
322    /// A bulk string holding a double in Redis's own formatting.
323    ///
324    /// A score written into a flat RESP2 reply arrives here rather than at
325    /// [`Out::double`], because there is no protocol choice left to make by
326    /// then.
327    ///
328    /// The length has to go in front of the digits and the digits cannot be
329    /// counted without writing them, so they are written first, the header is
330    /// appended behind them, and the two are rotated into place. A double is a
331    /// couple of dozen bytes at most, so the rotate is a few words, and nothing
332    /// is allocated to hold a number on its way into a buffer it is already in.
333    pub fn bulk_double(&mut self, d: f64) {
334        self.bulk_written(|buf| push_double(buf, d));
335    }
336
337    /// A bulk string holding a double the way the two float increments write
338    /// one.
339    ///
340    /// `INCRBYFLOAT` and `HINCRBYFLOAT` go through `ld2string` in its human
341    /// mode where every other double goes through `d2string`, and the two
342    /// disagree about large and small magnitudes: this one never writes an
343    /// exponent. Both of them reply with a bulk string on RESP2 and on RESP3
344    /// alike, so unlike [`Out::double`] there is no protocol branch here.
345    pub fn human_double(&mut self, d: f64) {
346        self.bulk_written(|buf| push_human(buf, d));
347    }
348
349    /// A bulk string holding a distance, which is four places and no exponent.
350    ///
351    /// The geo commands are the only ones that write a number this way, and
352    /// they write it as a bulk string on both protocols rather than as RESP3's
353    /// double, so there is no protocol branch here either. See
354    /// [`yo_common::num::push_fixed4`] for why four.
355    pub fn distance(&mut self, d: f64) {
356        self.bulk_written(|buf| push_fixed4(buf, d));
357    }
358
359    /// A bulk string whose contents are written by `f` and measured after.
360    fn bulk_written(&mut self, f: impl FnOnce(&mut Vec<u8>)) {
361        self.buf.reserve(48);
362        let start = self.buf.len();
363        f(&mut self.buf);
364        let digits = self.buf.len() - start;
365        self.buf.push(b'$');
366        push_u64(&mut self.buf, digits as u64);
367        self.crlf();
368        let header = self.buf.len() - start - digits;
369        self.buf[start..].rotate_right(header);
370        self.crlf();
371    }
372
373    /// A verbatim string, RESP3's `=`, with a three byte format such as `txt`
374    /// or `mkd`.
375    ///
376    /// RESP2 has no such type and gets a plain bulk string of the text, without
377    /// the format prefix, which is what Redis does.
378    pub fn verbatim(&mut self, format: &[u8; 3], text: &[u8]) {
379        if !self.proto.is_resp3() {
380            self.bulk(text);
381            return;
382        }
383        let len = text.len() + 4;
384        self.buf.reserve(len + 16);
385        self.buf.push(b'=');
386        push_u64(&mut self.buf, len as u64);
387        self.crlf();
388        self.buf.extend_from_slice(format);
389        self.buf.push(b':');
390        self.buf.extend_from_slice(text);
391        self.crlf();
392    }
393
394    /// A big number, RESP3's `(`, given as its decimal digits.
395    ///
396    /// RESP2 gets a bulk string of the same digits, which is what Redis does
397    /// and what every client already handles.
398    pub fn big_number(&mut self, digits: &[u8]) {
399        if self.proto.is_resp3() {
400            self.buf.reserve(digits.len() + 3);
401            self.buf.push(b'(');
402            self.buf.extend_from_slice(digits);
403            self.crlf();
404        } else {
405            self.bulk(digits);
406        }
407    }
408
409    // The types RESP3 added.
410
411    /// Nothing, where a string was expected.
412    ///
413    /// RESP3 has one null. RESP2 has two, and this is the one that stands in
414    /// for a missing string, which is what `GET` on a missing key returns.
415    #[inline]
416    pub fn nil(&mut self) {
417        self.buf.extend_from_slice(if self.proto.is_resp3() {
418            b"_\r\n"
419        } else {
420            b"$-1\r\n"
421        });
422    }
423
424    /// Nothing, where an array was expected.
425    ///
426    /// The other RESP2 null. `EXEC` on a dirty `WATCH` returns this one, and a
427    /// client that tells the two apart will notice if the wrong one is sent.
428    #[inline]
429    pub fn nil_array(&mut self) {
430        self.buf.extend_from_slice(if self.proto.is_resp3() {
431            b"_\r\n"
432        } else {
433            b"*-1\r\n"
434        });
435    }
436
437    /// A double, RESP3's `,`.
438    ///
439    /// RESP2 gets a bulk string of the same digits. The infinities and NaN are
440    /// written as words in both.
441    pub fn double(&mut self, d: f64) {
442        if self.proto.is_resp3() {
443            self.buf.reserve(32);
444            self.buf.push(b',');
445            push_double(&mut self.buf, d);
446            self.crlf();
447            return;
448        }
449        // RESP2 has no double and gets the digits as a bulk string, which is
450        // the same thing `INCRBYFLOAT` replies with on both protocols.
451        self.bulk_double(d);
452    }
453
454    /// A boolean, RESP3's `#t` or `#f`.
455    ///
456    /// RESP2 gets `:1` or `:0`, which is what every command that returns a
457    /// boolean has always returned there.
458    #[inline]
459    pub fn bool(&mut self, b: bool) {
460        self.buf
461            .extend_from_slice(match (self.proto.is_resp3(), b) {
462                (true, true) => b"#t\r\n",
463                (true, false) => b"#f\r\n",
464                (false, true) => b":1\r\n",
465                (false, false) => b":0\r\n",
466            });
467    }
468
469    // Aggregates. Each of these writes only the header; the caller then writes
470    // the elements. That is what makes a reply streamable without the codec
471    // needing to hold it.
472
473    /// An array header for `n` elements. The caller writes the elements next.
474    #[inline]
475    pub fn array(&mut self, n: usize) {
476        self.header(b'*', n);
477    }
478
479    /// Move the last `tail` bytes back to `start`, so that something written
480    /// after a reply ends up in front of it.
481    ///
482    /// Not every reply knows how long it is before it has been written. `SSCAN`
483    /// walks a window of the set and drops the members that do not match its
484    /// pattern, so the count is only true once the last member has been looked
485    /// at, and it answers with a cursor that the same walk produced. The
486    /// alternatives are both worse: walking the window twice runs the glob
487    /// twice, and collecting the members first is an allocation per call on a
488    /// thread that must not allocate.
489    ///
490    /// Redis solves this with a linked list of reply nodes it can patch in
491    /// place. There is one flat buffer here, so the piece that belongs in front
492    /// is written behind and the two are rotated past each other, which is the
493    /// trick [`Out::bulk_double`] already uses and costs one move of bytes that
494    /// were about to be moved to a socket anyway.
495    ///
496    /// # Panics
497    ///
498    /// If `start` is past the end, or `tail` is longer than what follows it.
499    pub fn hoist(&mut self, start: usize, tail: usize) {
500        assert!(
501            start + tail <= self.buf.len(),
502            "hoisted more than was written"
503        );
504        self.buf[start..].rotate_right(tail);
505    }
506
507    /// An array header for the elements written since `start`, which has to be
508    /// a length this buffer reported earlier.
509    ///
510    /// [`Out::hoist`] is why this can be called after the elements rather than
511    /// before them.
512    pub fn close_array(&mut self, start: usize, n: usize) {
513        self.close(b'*', start, n);
514    }
515
516    /// The same for a set, which is what the algebra commands answer.
517    ///
518    /// `SINTER` cannot count its own reply in advance any more than `SSCAN`
519    /// can. The answer is however many members survived a walk over the
520    /// smallest set, and finding that out ahead of writing it means running the
521    /// whole operation twice.
522    pub fn close_set(&mut self, start: usize, n: usize) {
523        self.close(if self.proto.is_resp3() { b'~' } else { b'*' }, start, n);
524    }
525
526    /// And for a map whose size is only known once it has been written, which
527    /// is `XREAD`.
528    ///
529    /// `XREAD` names several streams and leaves out the ones that had nothing
530    /// new, so the number of pairs is whatever survived the walk. The count is
531    /// `n` on either protocol and only the tag changes, because the two shapes
532    /// do not agree on what a pair is: RESP3 sends a map of stream name to
533    /// entries and RESP2 sends an array of two element arrays. The caller writes
534    /// one or the other and this closes it.
535    pub fn close_map(&mut self, start: usize, n: usize) {
536        self.close(if self.proto.is_resp3() { b'%' } else { b'*' }, start, n);
537    }
538
539    /// Write a header of `tag` for `n` elements behind the elements, then move
540    /// it in front of them.
541    fn close(&mut self, tag: u8, start: usize, n: usize) {
542        let body = self.buf.len() - start;
543        self.buf.push(tag);
544        push_u64(&mut self.buf, n as u64);
545        self.crlf();
546        let header = self.buf.len() - start - body;
547        self.hoist(start, header);
548    }
549
550    /// A map header for `n` pairs. The caller writes `2 * n` elements next,
551    /// key then value, `n` times.
552    ///
553    /// RESP2 has no map and gets a flat array of twice as many elements, which
554    /// is exactly what a RESP2 client already expects from `HGETALL` and
555    /// `CONFIG GET`. The command does not know which one it wrote.
556    #[inline]
557    pub fn map(&mut self, n: usize) {
558        if self.proto.is_resp3() {
559            self.header(b'%', n);
560        } else {
561            self.header(b'*', n * 2);
562        }
563    }
564
565    /// A set header for `n` elements.
566    ///
567    /// RESP2 has no set and gets an array, which is what `SMEMBERS` has always
568    /// returned there.
569    #[inline]
570    pub fn set(&mut self, n: usize) {
571        self.header(if self.proto.is_resp3() { b'~' } else { b'*' }, n);
572    }
573
574    /// A push header for `n` elements, RESP3's `>`.
575    ///
576    /// This is how pub/sub messages and client side caching invalidations are
577    /// delivered. RESP2 has no out of band type, so they go out as plain
578    /// arrays on the same connection, which is how RESP2 pub/sub has always
579    /// worked and is why a RESP2 connection in subscribe mode can only do a
580    /// handful of things.
581    #[inline]
582    pub fn push(&mut self, n: usize) {
583        self.header(if self.proto.is_resp3() { b'>' } else { b'*' }, n);
584    }
585
586    /// An attribute header for `n` pairs, RESP3's `|`.
587    ///
588    /// Attributes are metadata attached to the frame that follows. RESP2 cannot
589    /// carry them at all, so the caller must check [`Out::proto`] before
590    /// writing one. There is no downgrade, because turning metadata into a
591    /// reply element would corrupt the reply.
592    ///
593    /// # Panics
594    ///
595    /// In debug, if the connection is not speaking RESP3.
596    #[inline]
597    pub fn attribute(&mut self, n: usize) {
598        debug_assert!(
599            self.proto.is_resp3(),
600            "RESP2 has no attributes, check the protocol first"
601        );
602        self.header(b'|', n);
603    }
604
605    // Sizes, for the presize half of Y18.
606
607    /// The exact number of bytes [`Out::bulk`] would write for a value of this
608    /// length.
609    #[inline]
610    pub const fn bulk_len(value_len: usize) -> usize {
611        // `$`, the digits, CRLF, the body, CRLF.
612        1 + digits_of(value_len as u64) + 2 + value_len + 2
613    }
614
615    /// The exact number of bytes [`Out::int`] would write.
616    #[inline]
617    pub const fn int_len(n: i64) -> usize {
618        1 + i64_len(n) + 2
619    }
620
621    /// The exact number of bytes an aggregate header of `n` elements would
622    /// write, in either protocol, since both write one byte and the count.
623    #[inline]
624    pub const fn header_len(n: usize) -> usize {
625        1 + digits_of(n as u64) + 2
626    }
627
628    #[inline]
629    fn header(&mut self, kind: u8, n: usize) {
630        self.buf.reserve(24);
631        self.buf.push(kind);
632        push_u64(&mut self.buf, n as u64);
633        self.crlf();
634    }
635
636    /// A length prefixed blob: `$`, `!` and `=` all have this shape.
637    #[inline]
638    fn blob(&mut self, kind: u8, s: &[u8]) {
639        self.buf.reserve(Out::bulk_len(s.len()));
640        self.buf.push(kind);
641        push_u64(&mut self.buf, s.len() as u64);
642        self.crlf();
643        self.buf.extend_from_slice(s);
644        self.crlf();
645    }
646
647    #[inline]
648    fn crlf(&mut self) {
649        self.buf.extend_from_slice(b"\r\n");
650    }
651}
652
653/// How many decimal digits `n` needs.
654const fn digits_of(n: u64) -> usize {
655    let mut d = 1;
656    let mut v = n;
657    while v >= 10 {
658        v /= 10;
659        d += 1;
660    }
661    d
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667
668    /// Runs `f` on a fresh buffer in both protocols and returns what each one
669    /// produced. Every downgrade test below is written as one call, because the
670    /// point being made is always that the same command wrote both.
671    fn both(f: impl Fn(&mut Out)) -> (String, String) {
672        let mut two = Out::new(Proto::Resp2);
673        let mut three = Out::new(Proto::Resp3);
674        f(&mut two);
675        f(&mut three);
676        (
677            String::from_utf8(two.into_inner()).unwrap(),
678            String::from_utf8(three.into_inner()).unwrap(),
679        )
680    }
681
682    fn one(proto: Proto, f: impl Fn(&mut Out)) -> String {
683        let mut out = Out::new(proto);
684        f(&mut out);
685        String::from_utf8(out.into_inner()).unwrap()
686    }
687
688    #[test]
689    fn the_three_types_both_protocols_share_are_written_the_same_way() {
690        let (two, three) = both(|o| {
691            o.simple(b"PONG");
692            o.error(b"WRONGTYPE Operation against a key holding the wrong kind of value");
693            o.int(-42);
694            o.ok();
695        });
696        assert_eq!(two, three);
697        assert_eq!(
698            two,
699            "+PONG\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n:-42\r\n+OK\r\n"
700        );
701    }
702
703    #[test]
704    fn a_bulk_string_carries_its_length_and_anything_in_it() {
705        let (two, three) = both(|o| {
706            o.bulk(b"hello");
707            o.bulk(b"");
708            o.bulk(b"a\r\nb");
709        });
710        assert_eq!(two, three);
711        assert_eq!(two, "$5\r\nhello\r\n$0\r\n\r\n$4\r\na\r\nb\r\n");
712    }
713
714    #[test]
715    fn a_number_as_a_string_gets_the_right_length() {
716        assert_eq!(one(Proto::Resp2, |o| o.bulk_int(0)), "$1\r\n0\r\n");
717        assert_eq!(one(Proto::Resp2, |o| o.bulk_int(-1234)), "$5\r\n-1234\r\n");
718        assert_eq!(
719            one(Proto::Resp2, |o| o.bulk_int(i64::MIN)),
720            "$20\r\n-9223372036854775808\r\n"
721        );
722    }
723
724    #[test]
725    fn an_array_can_be_headed_after_its_elements_are_written() {
726        let (two, three) = both(|o| {
727            let start = o.len();
728            o.bulk(b"a");
729            o.bulk(b"bb");
730            o.close_array(start, 2);
731        });
732        assert_eq!(two, three);
733        assert_eq!(two, "*2\r\n$1\r\na\r\n$2\r\nbb\r\n");
734
735        // And it leaves whatever was already in the buffer where it was, which
736        // is the part a rotate can get wrong.
737        assert_eq!(
738            one(Proto::Resp2, |o| {
739                o.int(1);
740                let start = o.len();
741                o.bulk(b"x");
742                o.close_array(start, 1);
743            }),
744            ":1\r\n*1\r\n$1\r\nx\r\n"
745        );
746
747        // An empty one, and a header of more than one digit, which is where the
748        // rotate distance stops being a constant.
749        assert_eq!(
750            one(Proto::Resp2, |o| {
751                let start = o.len();
752                o.close_array(start, 0);
753            }),
754            "*0\r\n"
755        );
756        let long = one(Proto::Resp2, |o| {
757            let start = o.len();
758            for _ in 0..100 {
759                o.int(7);
760            }
761            o.close_array(start, 100);
762        });
763        assert!(long.starts_with("*100\r\n:7\r\n"));
764        assert!(long.ends_with(":7\r\n"));
765        assert_eq!(long.len(), "*100\r\n".len() + 100 * ":7\r\n".len());
766    }
767
768    #[test]
769    fn resp2_has_two_nulls_and_resp3_has_one() {
770        let (two, three) = both(|o| {
771            o.nil();
772            o.nil_array();
773        });
774        assert_eq!(two, "$-1\r\n*-1\r\n");
775        assert_eq!(three, "_\r\n_\r\n");
776    }
777
778    #[test]
779    fn a_map_becomes_a_flat_array_on_resp2() {
780        let (two, three) = both(|o| {
781            o.map(2);
782            o.bulk(b"a");
783            o.bulk(b"1");
784            o.bulk(b"b");
785            o.bulk(b"2");
786        });
787        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");
788        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");
789    }
790
791    #[test]
792    fn a_set_and_a_push_become_arrays_on_resp2() {
793        let (two, three) = both(|o| {
794            o.set(1);
795            o.bulk(b"x");
796            o.push(2);
797            o.bulk(b"message");
798            o.bulk(b"ch");
799        });
800        assert_eq!(two, "*1\r\n$1\r\nx\r\n*2\r\n$7\r\nmessage\r\n$2\r\nch\r\n");
801        assert_eq!(
802            three,
803            "~1\r\n$1\r\nx\r\n>2\r\n$7\r\nmessage\r\n$2\r\nch\r\n"
804        );
805    }
806
807    #[test]
808    fn a_boolean_is_an_integer_on_resp2() {
809        let (two, three) = both(|o| {
810            o.bool(true);
811            o.bool(false);
812        });
813        assert_eq!(two, ":1\r\n:0\r\n");
814        assert_eq!(three, "#t\r\n#f\r\n");
815    }
816
817    #[test]
818    fn a_double_is_a_bulk_string_on_resp2() {
819        let (two, three) = both(|o| {
820            o.double(1.5);
821            o.double(3.0);
822            o.double(f64::INFINITY);
823        });
824        assert_eq!(two, "$3\r\n1.5\r\n$1\r\n3\r\n$3\r\ninf\r\n");
825        assert_eq!(three, ",1.5\r\n,3\r\n,inf\r\n");
826    }
827
828    #[test]
829    fn a_verbatim_string_loses_its_format_on_resp2() {
830        let (two, three) = both(|o| o.verbatim(b"txt", b"Some string"));
831        assert_eq!(two, "$11\r\nSome string\r\n");
832        assert_eq!(three, "=15\r\ntxt:Some string\r\n");
833    }
834
835    #[test]
836    fn a_big_number_is_a_bulk_string_on_resp2() {
837        let n = b"3492890328409238509324850943850943825024385";
838        let (two, three) = both(|o| o.big_number(n));
839        assert_eq!(
840            two,
841            format!("${}\r\n{}\r\n", n.len(), str::from_utf8(n).unwrap())
842        );
843        assert_eq!(three, format!("({}\r\n", str::from_utf8(n).unwrap()));
844    }
845
846    #[test]
847    fn a_blob_error_keeps_its_newlines_on_resp3_and_loses_them_on_resp2() {
848        let (two, three) = both(|o| o.blob_error(b"SYNTAX bad\nline two"));
849        assert_eq!(two, "-SYNTAX bad line two\r\n");
850        assert_eq!(three, "!19\r\nSYNTAX bad\nline two\r\n");
851    }
852
853    /// The sizes are what a command presizes from, so a size that is wrong by
854    /// one is a reply that reallocates on every call and nobody notices.
855    #[test]
856    fn the_predicted_sizes_are_the_sizes_actually_written() {
857        for len in [0usize, 1, 9, 10, 99, 100, 1000, 65536] {
858            let value = vec![b'x'; len];
859            let written = one(Proto::Resp2, |o| o.bulk(&value));
860            assert_eq!(Out::bulk_len(len), written.len(), "bulk of {len}");
861        }
862        for n in [0i64, 7, -7, 100, i64::MAX, i64::MIN] {
863            let written = one(Proto::Resp2, |o| o.int(n));
864            assert_eq!(Out::int_len(n), written.len(), "int {n}");
865        }
866        for n in [0usize, 5, 1234] {
867            let written = one(Proto::Resp2, |o| o.array(n));
868            assert_eq!(Out::header_len(n), written.len(), "array header {n}");
869        }
870    }
871
872    #[test]
873    fn hello_switches_the_protocol_for_everything_after_it() {
874        let mut out = Out::new(Proto::Resp2);
875        out.nil();
876        out.set_proto(Proto::Resp3);
877        out.nil();
878        assert_eq!(out.as_slice(), b"$-1\r\n_\r\n");
879    }
880
881    #[test]
882    fn a_partial_write_leaves_the_rest_behind() {
883        let mut out = Out::new(Proto::Resp2);
884        out.ok();
885        out.ok();
886        out.consume(5);
887        assert_eq!(out.as_slice(), b"+OK\r\n");
888        out.clear();
889        assert!(out.is_empty());
890    }
891}