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}