imap_codec/codec/
decode.rs

1//! # Decoding of messages.
2//!
3//! You can use [`Decoder`]s to parse messages.
4//!
5//! IMAP literals make separating the parsing logic from the application logic difficult.
6//! When a server recognizes a literal (e.g. `{42}\r\n`) in a command, it first needs to agree to receive more data by sending a so-called "command continuation request" (`+ ...`).
7//! Without a command continuation request, a client won't send more data, and the command parser on the server would always return `LiteralFound { length: 42, .. }`.
8//! This makes real-world decoding of IMAP more elaborate.
9//!
10//! Have a look at the [parse_command](https://github.com/duesee/imap-codec/blob/main/imap-codec/examples/parse_command.rs) example to see how a real-world application could decode IMAP.
11
12use std::num::{ParseIntError, TryFromIntError};
13
14#[cfg(feature = "bounded-static")]
15use bounded_static::{IntoBoundedStatic, ToStatic};
16use imap_types::{
17    auth::AuthenticateData,
18    command::Command,
19    core::{LiteralMode, Tag},
20    extensions::idle::IdleDone,
21    response::{Greeting, Response},
22};
23use nom::error::{ErrorKind, FromExternalError, ParseError};
24
25use crate::{
26    auth::authenticate_data,
27    command::command,
28    extensions::idle::idle_done,
29    response::{greeting, response},
30    AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec,
31};
32
33/// An extended version of [`nom::IResult`].
34pub(crate) type IMAPResult<'a, I, O> = Result<(I, O), nom::Err<IMAPParseError<'a, I>>>;
35
36/// An extended version of [`nom::error::Error`].
37#[derive(Debug)]
38pub(crate) struct IMAPParseError<'a, I> {
39    #[allow(unused)]
40    pub input: I,
41    pub kind: IMAPErrorKind<'a>,
42}
43
44/// An extended version of [`nom::error::ErrorKind`].
45#[derive(Debug)]
46pub(crate) enum IMAPErrorKind<'a> {
47    Literal {
48        tag: Option<Tag<'a>>,
49        length: u32,
50        mode: LiteralMode,
51    },
52    BadNumber,
53    BadBase64,
54    BadDateTime,
55    LiteralContainsNull,
56    RecursionLimitExceeded,
57    Nom(ErrorKind),
58}
59
60impl<'a, I> ParseError<I> for IMAPParseError<'a, I> {
61    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
62        Self {
63            input,
64            kind: IMAPErrorKind::Nom(kind),
65        }
66    }
67
68    fn append(input: I, kind: ErrorKind, _: Self) -> Self {
69        Self {
70            input,
71            kind: IMAPErrorKind::Nom(kind),
72        }
73    }
74}
75
76impl<'a, I> FromExternalError<I, ParseIntError> for IMAPParseError<'a, I> {
77    fn from_external_error(input: I, _: ErrorKind, _: ParseIntError) -> Self {
78        Self {
79            input,
80            kind: IMAPErrorKind::BadNumber,
81        }
82    }
83}
84
85impl<'a, I> FromExternalError<I, TryFromIntError> for IMAPParseError<'a, I> {
86    fn from_external_error(input: I, _: ErrorKind, _: TryFromIntError) -> Self {
87        Self {
88            input,
89            kind: IMAPErrorKind::BadNumber,
90        }
91    }
92}
93
94impl<'a, I> FromExternalError<I, base64::DecodeError> for IMAPParseError<'a, I> {
95    fn from_external_error(input: I, _: ErrorKind, _: base64::DecodeError) -> Self {
96        Self {
97            input,
98            kind: IMAPErrorKind::BadBase64,
99        }
100    }
101}
102
103/// Decoder.
104///
105/// Implemented for types that know how to decode a specific IMAP message. See [implementors](trait.Decoder.html#implementors).
106pub trait Decoder {
107    type Message<'a>: Sized;
108    type Error<'a>;
109
110    fn decode<'a>(&self, input: &'a [u8])
111        -> Result<(&'a [u8], Self::Message<'a>), Self::Error<'a>>;
112
113    #[cfg(feature = "bounded-static")]
114    #[cfg_attr(docsrs, doc(cfg(feature = "bounded-static")))]
115    fn decode_static<'a>(
116        &self,
117        input: &'a [u8],
118    ) -> Result<(&'a [u8], Self::Message<'static>), Self::Error<'static>>
119    where
120        Self::Message<'a>: IntoBoundedStatic<Static = Self::Message<'static>>,
121        Self::Error<'a>: IntoBoundedStatic<Static = Self::Error<'static>>,
122    {
123        let (remaining, value) = self.decode(input).map_err(IntoBoundedStatic::into_static)?;
124        Ok((remaining, value.into_static()))
125    }
126}
127
128/// Error during greeting decoding.
129#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
130#[derive(Clone, Debug, Eq, PartialEq)]
131pub enum GreetingDecodeError {
132    /// More data is needed.
133    Incomplete,
134
135    /// Decoding failed.
136    Failed,
137}
138
139/// Error during command decoding.
140#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
141#[derive(Clone, Debug, Eq, PartialEq)]
142pub enum CommandDecodeError<'a> {
143    /// More data is needed.
144    Incomplete,
145
146    /// More data is needed (and further action may be necessary).
147    ///
148    /// The decoder stopped at the beginning of literal data. Typically, a server MUST send a
149    /// command continuation request to agree to the receival of the remaining data. This behaviour
150    /// is different when `LITERAL+/LITERAL-` is used.
151    ///
152    /// # With `LITERAL+/LITERAL-`
153    ///
154    /// When the `mode` is sync, everything is the same as above.
155    ///
156    /// When the `mode` is non-sync, *and* the server advertised the LITERAL+ capability,
157    /// it MUST NOT send a command continuation request and accept the data right away.
158    ///
159    /// When the `mode` is non-sync, *and* the server advertised the LITERAL- capability,
160    /// *and* the literal length is smaller or equal than 4096,
161    /// it MUST NOT send a command continuation request and accept the data right away.
162    ///
163    /// When the `mode` is non-sync, *and* the server advertised the LITERAL- capability,
164    /// *and* the literal length is greater than 4096,
165    /// it MUST be handled as sync.
166    ///
167    /// ```rust,ignore
168    /// match mode {
169    ///     LiteralMode::Sync => /* Same as sync. */
170    ///     LiteralMode::Sync => match advertised {
171    ///         Capability::LiteralPlus => /* Accept data right away. */
172    ///         Capability::LiteralMinus => {
173    ///             if literal_length <= 4096 {
174    ///                 /* Accept data right away. */
175    ///             } else {
176    ///                 /* Same as sync. */
177    ///             }
178    ///         }
179    ///     }
180    /// }
181    /// ```
182    LiteralFound {
183        /// The corresponding command (tag) to which this literal is bound.
184        ///
185        /// This is required to reject literals, e.g., when their size exceeds a limit.
186        tag: Tag<'a>,
187
188        /// Literal length.
189        length: u32,
190
191        /// Literal mode, i.e., sync or non-sync.
192        mode: LiteralMode,
193    },
194
195    /// Decoding failed.
196    Failed,
197}
198
199/// Error during authenticate data line decoding.
200#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
201#[derive(Clone, Debug, Eq, PartialEq)]
202pub enum AuthenticateDataDecodeError {
203    /// More data is needed.
204    Incomplete,
205
206    /// Decoding failed.
207    Failed,
208}
209
210/// Error during response decoding.
211#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
212#[derive(Clone, Debug, Eq, PartialEq)]
213pub enum ResponseDecodeError {
214    /// More data is needed.
215    Incomplete,
216
217    /// The decoder stopped at the beginning of literal data.
218    ///
219    /// The client *MUST* accept the literal and has no option to reject it.
220    /// However, when the client ultimately does not want to handle the literal, it can do something
221    /// similar to <https://datatracker.ietf.org/doc/html/rfc7888#section-4>.
222    ///
223    /// It can implement a discarding mechanism, basically, consuming the whole literal but not
224    /// saving the bytes in memory. Or, it can close the connection.
225    LiteralFound {
226        /// Literal length.
227        length: u32,
228    },
229
230    /// Decoding failed.
231    Failed,
232}
233
234/// Error during idle done decoding.
235#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
236#[derive(Clone, Debug, Eq, PartialEq)]
237pub enum IdleDoneDecodeError {
238    /// More data is needed.
239    Incomplete,
240
241    /// Decoding failed.
242    Failed,
243}
244
245// -------------------------------------------------------------------------------------------------
246
247impl Decoder for GreetingCodec {
248    type Message<'a> = Greeting<'a>;
249    type Error<'a> = GreetingDecodeError;
250
251    fn decode<'a>(
252        &self,
253        input: &'a [u8],
254    ) -> Result<(&'a [u8], Self::Message<'a>), Self::Error<'static>> {
255        match greeting(input) {
256            Ok((rem, grt)) => Ok((rem, grt)),
257            Err(nom::Err::Incomplete(_)) => Err(GreetingDecodeError::Incomplete),
258            Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => Err(GreetingDecodeError::Failed),
259        }
260    }
261}
262
263impl Decoder for CommandCodec {
264    type Message<'a> = Command<'a>;
265    type Error<'a> = CommandDecodeError<'a>;
266
267    fn decode<'a>(
268        &self,
269        input: &'a [u8],
270    ) -> Result<(&'a [u8], Self::Message<'a>), Self::Error<'a>> {
271        match command(input) {
272            Ok((rem, cmd)) => Ok((rem, cmd)),
273            Err(nom::Err::Incomplete(_)) => Err(CommandDecodeError::Incomplete),
274            Err(nom::Err::Failure(error)) => match error {
275                IMAPParseError {
276                    input: _,
277                    kind: IMAPErrorKind::Literal { tag, length, mode },
278                } => Err(CommandDecodeError::LiteralFound {
279                    // Unwrap: We *must* receive a `tag` during command parsing.
280                    tag: tag.expect("Expected `Some(tag)` in `IMAPErrorKind::Literal`, got `None`"),
281                    length,
282                    mode,
283                }),
284                _ => Err(CommandDecodeError::Failed),
285            },
286            Err(nom::Err::Error(_)) => Err(CommandDecodeError::Failed),
287        }
288    }
289}
290
291impl Decoder for ResponseCodec {
292    type Message<'a> = Response<'a>;
293    type Error<'a> = ResponseDecodeError;
294
295    fn decode<'a>(
296        &self,
297        input: &'a [u8],
298    ) -> Result<(&'a [u8], Self::Message<'a>), Self::Error<'static>> {
299        match response(input) {
300            Ok((rem, rsp)) => Ok((rem, rsp)),
301            Err(nom::Err::Incomplete(_)) => Err(ResponseDecodeError::Incomplete),
302            Err(nom::Err::Error(error) | nom::Err::Failure(error)) => match error {
303                IMAPParseError {
304                    kind: IMAPErrorKind::Literal { length, .. },
305                    ..
306                } => Err(ResponseDecodeError::LiteralFound { length }),
307                _ => Err(ResponseDecodeError::Failed),
308            },
309        }
310    }
311}
312
313impl Decoder for AuthenticateDataCodec {
314    type Message<'a> = AuthenticateData;
315    type Error<'a> = AuthenticateDataDecodeError;
316
317    fn decode<'a>(
318        &self,
319        input: &'a [u8],
320    ) -> Result<(&'a [u8], Self::Message<'a>), Self::Error<'static>> {
321        match authenticate_data(input) {
322            Ok((rem, rsp)) => Ok((rem, rsp)),
323            Err(nom::Err::Incomplete(_)) => Err(AuthenticateDataDecodeError::Incomplete),
324            Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
325                Err(AuthenticateDataDecodeError::Failed)
326            }
327        }
328    }
329}
330
331impl Decoder for IdleDoneCodec {
332    type Message<'a> = IdleDone;
333    type Error<'a> = IdleDoneDecodeError;
334
335    fn decode<'a>(
336        &self,
337        input: &'a [u8],
338    ) -> Result<(&'a [u8], Self::Message<'a>), Self::Error<'static>> {
339        match idle_done(input) {
340            Ok((rem, rsp)) => Ok((rem, rsp)),
341            Err(nom::Err::Incomplete(_)) => Err(IdleDoneDecodeError::Incomplete),
342            Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => Err(IdleDoneDecodeError::Failed),
343        }
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use std::num::NonZeroU32;
350
351    use imap_types::{
352        command::{Command, CommandBody},
353        core::{IString, Literal, NString, NonEmptyVec},
354        extensions::idle::IdleDone,
355        fetch::MessageDataItem,
356        mailbox::Mailbox,
357        response::{Data, Greeting, GreetingKind, Response},
358        secret::Secret,
359    };
360
361    use super::*;
362
363    #[test]
364    fn test_decode_greeting() {
365        let tests = [
366            // Ok
367            (
368                b"* OK ...\r\n".as_ref(),
369                Ok((
370                    b"".as_ref(),
371                    Greeting::new(GreetingKind::Ok, None, "...").unwrap(),
372                )),
373            ),
374            (
375                b"* ByE .\r\n???".as_ref(),
376                Ok((
377                    b"???".as_ref(),
378                    Greeting::new(GreetingKind::Bye, None, ".").unwrap(),
379                )),
380            ),
381            (
382                b"* preaUth x\r\n?".as_ref(),
383                Ok((
384                    b"?".as_ref(),
385                    Greeting::new(GreetingKind::PreAuth, None, "x").unwrap(),
386                )),
387            ),
388            // Incomplete
389            (b"*".as_ref(), Err(GreetingDecodeError::Incomplete)),
390            (b"* ".as_ref(), Err(GreetingDecodeError::Incomplete)),
391            (b"* O".as_ref(), Err(GreetingDecodeError::Incomplete)),
392            (b"* OK".as_ref(), Err(GreetingDecodeError::Incomplete)),
393            (b"* OK ".as_ref(), Err(GreetingDecodeError::Incomplete)),
394            (b"* OK .".as_ref(), Err(GreetingDecodeError::Incomplete)),
395            (b"* OK .\r".as_ref(), Err(GreetingDecodeError::Incomplete)),
396            // Failed
397            (b"**".as_ref(), Err(GreetingDecodeError::Failed)),
398            (b"* NO x\r\n".as_ref(), Err(GreetingDecodeError::Failed)),
399        ];
400
401        for (test, expected) in tests {
402            let got = GreetingCodec::default().decode(test);
403            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
404            assert_eq!(expected, got);
405
406            #[cfg(feature = "bounded-static")]
407            {
408                let got = GreetingCodec::default().decode_static(test);
409                assert_eq!(expected, got);
410            }
411        }
412    }
413
414    #[test]
415    fn test_decode_command() {
416        let tests = [
417            // Ok
418            (
419                b"a noop\r\n".as_ref(),
420                Ok((b"".as_ref(), Command::new("a", CommandBody::Noop).unwrap())),
421            ),
422            (
423                b"a noop\r\n???".as_ref(),
424                Ok((
425                    b"???".as_ref(),
426                    Command::new("a", CommandBody::Noop).unwrap(),
427                )),
428            ),
429            (
430                b"a select {5}\r\ninbox\r\n".as_ref(),
431                Ok((
432                    b"".as_ref(),
433                    Command::new(
434                        "a",
435                        CommandBody::Select {
436                            mailbox: Mailbox::Inbox,
437                        },
438                    )
439                    .unwrap(),
440                )),
441            ),
442            (
443                b"a select {5}\r\ninbox\r\nxxx".as_ref(),
444                Ok((
445                    b"xxx".as_ref(),
446                    Command::new(
447                        "a",
448                        CommandBody::Select {
449                            mailbox: Mailbox::Inbox,
450                        },
451                    )
452                    .unwrap(),
453                )),
454            ),
455            // Incomplete
456            (b"a".as_ref(), Err(CommandDecodeError::Incomplete)),
457            (b"a ".as_ref(), Err(CommandDecodeError::Incomplete)),
458            (b"a n".as_ref(), Err(CommandDecodeError::Incomplete)),
459            (b"a no".as_ref(), Err(CommandDecodeError::Incomplete)),
460            (b"a noo".as_ref(), Err(CommandDecodeError::Incomplete)),
461            (b"a noop".as_ref(), Err(CommandDecodeError::Incomplete)),
462            (b"a noop\r".as_ref(), Err(CommandDecodeError::Incomplete)),
463            // LiteralAckRequired
464            (
465                b"a select {5}\r\n".as_ref(),
466                Err(CommandDecodeError::LiteralFound {
467                    tag: Tag::try_from("a").unwrap(),
468                    length: 5,
469                    mode: LiteralMode::Sync,
470                }),
471            ),
472            // Incomplete (after literal)
473            (
474                b"a select {5}\r\nxxx".as_ref(),
475                Err(CommandDecodeError::Incomplete),
476            ),
477            // Failed
478            (b"* noop\r\n".as_ref(), Err(CommandDecodeError::Failed)),
479            (b"A  noop\r\n".as_ref(), Err(CommandDecodeError::Failed)),
480        ];
481
482        for (test, expected) in tests {
483            let got = CommandCodec::default().decode(test);
484            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
485            assert_eq!(expected, got);
486
487            #[cfg(feature = "bounded-static")]
488            {
489                let got = CommandCodec::default().decode_static(test);
490                assert_eq!(expected, got);
491            }
492        }
493    }
494
495    #[test]
496    fn test_decode_authenticate_data() {
497        let tests = [
498            // Ok
499            (
500                b"VGVzdA==\r\n".as_ref(),
501                Ok((
502                    b"".as_ref(),
503                    AuthenticateData(Secret::new(b"Test".to_vec())),
504                )),
505            ),
506            (
507                b"VGVzdA==\r\nx".as_ref(),
508                Ok((
509                    b"x".as_ref(),
510                    AuthenticateData(Secret::new(b"Test".to_vec())),
511                )),
512            ),
513            // Incomplete
514            (b"V".as_ref(), Err(AuthenticateDataDecodeError::Incomplete)),
515            (b"VG".as_ref(), Err(AuthenticateDataDecodeError::Incomplete)),
516            (
517                b"VGV".as_ref(),
518                Err(AuthenticateDataDecodeError::Incomplete),
519            ),
520            (
521                b"VGVz".as_ref(),
522                Err(AuthenticateDataDecodeError::Incomplete),
523            ),
524            (
525                b"VGVzd".as_ref(),
526                Err(AuthenticateDataDecodeError::Incomplete),
527            ),
528            (
529                b"VGVzdA".as_ref(),
530                Err(AuthenticateDataDecodeError::Incomplete),
531            ),
532            (
533                b"VGVzdA=".as_ref(),
534                Err(AuthenticateDataDecodeError::Incomplete),
535            ),
536            (
537                b"VGVzdA==".as_ref(),
538                Err(AuthenticateDataDecodeError::Incomplete),
539            ),
540            (
541                b"VGVzdA==\r".as_ref(),
542                Err(AuthenticateDataDecodeError::Incomplete),
543            ),
544            (
545                b"VGVzdA==\r\n".as_ref(),
546                Ok((
547                    b"".as_ref(),
548                    AuthenticateData(Secret::new(b"Test".to_vec())),
549                )),
550            ),
551            // Failed
552            (
553                b"VGVzdA== \r\n".as_ref(),
554                Err(AuthenticateDataDecodeError::Failed),
555            ),
556            (
557                b" VGVzdA== \r\n".as_ref(),
558                Err(AuthenticateDataDecodeError::Failed),
559            ),
560            (
561                b" V GVzdA== \r\n".as_ref(),
562                Err(AuthenticateDataDecodeError::Failed),
563            ),
564            (
565                b" V GVzdA= \r\n".as_ref(),
566                Err(AuthenticateDataDecodeError::Failed),
567            ),
568        ];
569
570        for (test, expected) in tests {
571            let got = AuthenticateDataCodec::default().decode(test);
572            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
573            assert_eq!(expected, got);
574
575            #[cfg(feature = "bounded-static")]
576            {
577                let got = AuthenticateDataCodec::default().decode_static(test);
578                assert_eq!(expected, got);
579            }
580        }
581    }
582
583    #[test]
584    fn test_decode_idle_done() {
585        let tests = [
586            // Ok
587            (b"done\r\n".as_ref(), Ok((b"".as_ref(), IdleDone))),
588            (b"done\r\n?".as_ref(), Ok((b"?".as_ref(), IdleDone))),
589            // Incomplete
590            (b"d".as_ref(), Err(IdleDoneDecodeError::Incomplete)),
591            (b"do".as_ref(), Err(IdleDoneDecodeError::Incomplete)),
592            (b"don".as_ref(), Err(IdleDoneDecodeError::Incomplete)),
593            (b"done".as_ref(), Err(IdleDoneDecodeError::Incomplete)),
594            (b"done\r".as_ref(), Err(IdleDoneDecodeError::Incomplete)),
595            // Failed
596            (b"donee\r\n".as_ref(), Err(IdleDoneDecodeError::Failed)),
597            (b" done\r\n".as_ref(), Err(IdleDoneDecodeError::Failed)),
598            (b"done \r\n".as_ref(), Err(IdleDoneDecodeError::Failed)),
599            (b" done \r\n".as_ref(), Err(IdleDoneDecodeError::Failed)),
600        ];
601
602        for (test, expected) in tests {
603            let got = IdleDoneCodec::default().decode(test);
604            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
605            assert_eq!(expected, got);
606
607            #[cfg(feature = "bounded-static")]
608            {
609                let got = IdleDoneCodec::default().decode_static(test);
610                assert_eq!(expected, got);
611            }
612        }
613    }
614
615    #[test]
616    fn test_decode_response() {
617        let tests = [
618            // Incomplete
619            (b"".as_ref(), Err(ResponseDecodeError::Incomplete)),
620            (b"*".as_ref(), Err(ResponseDecodeError::Incomplete)),
621            (b"* ".as_ref(), Err(ResponseDecodeError::Incomplete)),
622            (b"* S".as_ref(), Err(ResponseDecodeError::Incomplete)),
623            (b"* SE".as_ref(), Err(ResponseDecodeError::Incomplete)),
624            (b"* SEA".as_ref(), Err(ResponseDecodeError::Incomplete)),
625            (b"* SEAR".as_ref(), Err(ResponseDecodeError::Incomplete)),
626            (b"* SEARC".as_ref(), Err(ResponseDecodeError::Incomplete)),
627            (b"* SEARCH".as_ref(), Err(ResponseDecodeError::Incomplete)),
628            (b"* SEARCH ".as_ref(), Err(ResponseDecodeError::Incomplete)),
629            (b"* SEARCH 1".as_ref(), Err(ResponseDecodeError::Incomplete)),
630            (
631                b"* SEARCH 1\r".as_ref(),
632                Err(ResponseDecodeError::Incomplete),
633            ),
634            // Ok
635            (
636                b"* SEARCH 1\r\n".as_ref(),
637                Ok((
638                    b"".as_ref(),
639                    Response::Data(Data::Search(vec![NonZeroU32::new(1).unwrap()])),
640                )),
641            ),
642            (
643                b"* SEARCH 1\r\n???".as_ref(),
644                Ok((
645                    b"???".as_ref(),
646                    Response::Data(Data::Search(vec![NonZeroU32::new(1).unwrap()])),
647                )),
648            ),
649            (
650                b"* 1 FETCH (RFC822 {5}\r\nhello)\r\n".as_ref(),
651                Ok((
652                    b"".as_ref(),
653                    Response::Data(Data::Fetch {
654                        seq: NonZeroU32::new(1).unwrap(),
655                        items: NonEmptyVec::from(MessageDataItem::Rfc822(NString(Some(
656                            IString::Literal(Literal::try_from(b"hello".as_ref()).unwrap()),
657                        )))),
658                    }),
659                )),
660            ),
661            (
662                b"* 1 FETCH (RFC822 {5}\r\n".as_ref(),
663                Err(ResponseDecodeError::LiteralFound { length: 5 }),
664            ),
665            // Failed
666            (
667                b"*  search 1 2 3\r\n".as_ref(),
668                Err(ResponseDecodeError::Failed),
669            ),
670            (b"A search\r\n".as_ref(), Err(ResponseDecodeError::Failed)),
671        ];
672
673        for (test, expected) in tests {
674            let got = ResponseCodec::default().decode(test);
675            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
676            assert_eq!(expected, got);
677
678            #[cfg(feature = "bounded-static")]
679            {
680                let got = ResponseCodec::default().decode_static(test);
681                assert_eq!(expected, got);
682            }
683        }
684    }
685}