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}