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}