Repository: duesee/imap-codec (language: python)

Issue:
Resolve test-related `TODO`s.
* [ ] Resolve `TODO`s in fuzz-targets, i.e., do not skip variants.
  * [ ] `response_to_bytes_and_back` remaining
    * [ ] `BodyStructure`
    * [ ] `Continue`
* [x] Resolve `TODO`s in `Arbitrary` implementations, i.e., do not generate static values.


Discussion / hints:

Fix patch:
diff --git a/fuzz/README.md b/fuzz/README.md
index 5ebe3377..a72fd3f4 100644
--- a/fuzz/README.md
+++ b/fuzz/README.md
@@ -44,7 +44,7 @@ cargo +nightly fuzz run <target>
 | `command`                          | Test parsing           | Must not fail.                             |
 | `response`                         | Test parsing           | Must not fail.                             |
 | `greeting_to_bytes_and_back`       | Test misuse-resistance | Must not fail.                             |
-| `command_to_bytes_and_back`        | Test misuse-resistance | Must not fail (but has remaining `TODO`s). |
+| `command_to_bytes_and_back`        | Test misuse-resistance | Must not fail.                             |
 | `response_to_bytes_and_back`       | Test misuse-resistance | Must not fail (but has remaining `TODO`s). |
 
 Three first three fuzz targets are used to test the parsing routines.
diff --git a/fuzz/fuzz_targets/command_to_bytes_and_back.rs b/fuzz/fuzz_targets/command_to_bytes_and_back.rs
index e45fc835..a3daef90 100644
--- a/fuzz/fuzz_targets/command_to_bytes_and_back.rs
+++ b/fuzz/fuzz_targets/command_to_bytes_and_back.rs
@@ -4,33 +4,11 @@
 use imap_codec::utils::escape_byte_string;
 use imap_codec::{
     codec::{Decode, Encode},
-    command::{search::SearchKey, Command, CommandBody},
+    command::Command,
 };
 use libfuzzer_sys::fuzz_target;
 
-fn ignore_search_key_and(sk: &SearchKey) -> bool {
-    match sk {
-        SearchKey::And(list) => match list.as_ref().len() {
-            1 => true,
-            _ => list.as_ref().iter().any(ignore_search_key_and),
-        },
-        SearchKey::Not(sk) => ignore_search_key_and(sk),
-        SearchKey::Or(sk1, sk2) => ignore_search_key_and(sk1) || ignore_search_key_and(sk2),
-        _ => false,
-    }
-}
-
 fuzz_target!(|test: Command| {
-    // TODO(#30): Skip certain generations for now as we know they need to be fixed.
-    //            The goal is to not skip anything eventually.
-    match test.body {
-        CommandBody::Search { ref criteria, .. } if ignore_search_key_and(criteria) => {
-            // FIXME(#30)
-            return;
-        }
-        _ => {}
-    }
-
     #[cfg(feature = "debug")]
     println!("[!] Input: {test:?}");
 
diff --git a/imap-types/Cargo.toml b/imap-types/Cargo.toml
index e3f83215..ec0a4881 100644
--- a/imap-types/Cargo.toml
+++ b/imap-types/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
 edition = "2018"
 
 [features]
-arbitrary = ["dep:arbitrary", "unchecked"]
+arbitrary = ["dep:arbitrary", "unchecked", "chrono/arbitrary", "chrono/std"]
 default = []
 bounded-static = ["dep:bounded-static", "bounded-static/derive"]
 serde = ["dep:serde", "chrono/serde"]
diff --git a/imap-types/src/arbitrary.rs b/imap-types/src/arbitrary.rs
index 57fcc55c..9270784d 100644
--- a/imap-types/src/arbitrary.rs
+++ b/imap-types/src/arbitrary.rs
@@ -1,7 +1,7 @@
 use std::convert::TryFrom;
 
 use arbitrary::{Arbitrary, Unstructured};
-use chrono::{FixedOffset, NaiveDate as ChronoNaiveDate, NaiveDateTime, NaiveTime, TimeZone};
+use chrono::{FixedOffset, TimeZone};
 
 #[cfg(feature = "ext_enable")]
 use crate::extensions::enable::CapabilityEnableOther;
@@ -204,38 +204,37 @@ impl<'a> Arbitrary<'a> for SearchKey<'a> {
 }
 
 impl<'a> Arbitrary<'a> for DateTime {
-    fn arbitrary(_: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
-        // FIXME(#30): make arbitrary :-)
-
-        let local_datetime = NaiveDateTime::new(
-            ChronoNaiveDate::from_ymd_opt(1985, 2, 1).unwrap(),
-            NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        // Note: `chrono`s `NaiveDate::arbitrary` may `panic!`.
+        //       Thus, we implement this manually here.
+        let local_datetime = chrono::NaiveDateTime::new(
+            chrono::NaiveDate::from_ymd_opt(
+                u.int_in_range(0..=9999)?,
+                u.int_in_range(1..=12)?,
+                u.int_in_range(1..=31)?,
+            )
+            .ok_or(arbitrary::Error::IncorrectFormat)?,
+            chrono::NaiveTime::arbitrary(u)?,
         );
 
-        Ok(DateTime(
-            FixedOffset::east_opt(3600)
+        let hours = u.int_in_range(0..=23 * 3600)?;
+        let minutes = u.int_in_range(0..=59)? * 60;
+        // Seconds must be zero due to IMAPs encoding.
+
+        DateTime::try_from(
+            FixedOffset::east_opt(hours + minutes)
                 .unwrap()
                 .from_local_datetime(&local_datetime)
                 .unwrap(),
-        ))
+        )
+        .map_err(|_| arbitrary::Error::IncorrectFormat)
     }
 }
 
 impl<'a> Arbitrary<'a> for NaiveDate {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
-        loop {
-            // This was copied from the `chrono`.
-            const MIN_YEAR: i32 = i32::MIN >> 13;
-            const MAX_YEAR: i32 = i32::MAX >> 13;
-
-            let year: i32 = u.int_in_range(MIN_YEAR..=MAX_YEAR)?;
-            let month: u32 = u.int_in_range(1..=12)?;
-            let day: u32 = u.int_in_range(1..=31)?;
-
-            if let Some(chrono_naive_date) = ChronoNaiveDate::from_ymd_opt(year, month, day) {
-                return Ok(NaiveDate(chrono_naive_date));
-            }
-        }
+        NaiveDate::try_from(chrono::NaiveDate::arbitrary(u)?)
+            .map_err(|_| arbitrary::Error::IncorrectFormat)
     }
 }
 
diff --git a/imap-types/src/codec.rs b/imap-types/src/codec.rs
index 13cb0175..9fd1a110 100644
--- a/imap-types/src/codec.rs
+++ b/imap-types/src/codec.rs
@@ -246,11 +246,7 @@ impl<'a> Encode for CommandBody<'a> {
                     charset.encode(writer)?;
                 }
                 writer.write_all(b" ")?;
-                if let SearchKey::And(search_keys) = criteria {
-                    join_serializable(search_keys.as_ref(), b" ", writer) // TODO: use List1?
-                } else {
-                    criteria.encode(writer)
-                }
+                criteria.encode(writer)
             }
             CommandBody::Fetch {
                 sequence_set,
@@ -510,7 +506,7 @@ impl<'a> Encode for FlagExtension<'a> {
 
 impl Encode for DateTime {
     fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        self.0.encode(writer)
+        self.as_ref().encode(writer)
     }
 }
 
@@ -662,7 +658,7 @@ impl Encode for SeqOrUid {
 
 impl Encode for NaiveDate {
     fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
-        write!(writer, "\"{}\"", self.0.format("%d-%b-%Y"))
+        write!(writer, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
     }
 }
 
diff --git a/imap-types/src/imap4rev1/command.rs b/imap-types/src/imap4rev1/command.rs
index 150a188f..59da7368 100644
--- a/imap-types/src/imap4rev1/command.rs
+++ b/imap-types/src/imap4rev1/command.rs
@@ -1694,18 +1694,26 @@ mod tests {
             CommandBody::append(
                 "inbox",
                 vec![],
-                Some(DateTime(
-                    ChronoDateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200").unwrap(),
-                )),
+                Some(
+                    DateTime::try_from(
+                        ChronoDateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")
+                            .unwrap(),
+                    )
+                    .unwrap(),
+                ),
                 vec![0xff, 0xff, 0xff],
             )
             .unwrap(),
             CommandBody::append(
                 "inbox",
                 vec![Flag::Keyword("test".try_into().unwrap())],
-                Some(DateTime(
-                    ChronoDateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200").unwrap(),
-                )),
+                Some(
+                    DateTime::try_from(
+                        ChronoDateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")
+                            .unwrap(),
+                    )
+                    .unwrap(),
+                ),
                 vec![0xff, 0xff, 0xff],
             )
             .unwrap(),
diff --git a/imap-types/src/imap4rev1/command/search.rs b/imap-types/src/imap4rev1/command/search.rs
index 6f8646e3..51803267 100644
--- a/imap-types/src/imap4rev1/command/search.rs
+++ b/imap-types/src/imap4rev1/command/search.rs
@@ -170,6 +170,10 @@ mod tests {
     #[test]
     fn test_encode_search_key() {
         let tests = [
+            (
+                SearchKey::And(NonEmptyVec::try_from(vec![SearchKey::Answered]).unwrap()),
+                b"(ANSWERED)".as_ref(),
+            ),
             (
                 SearchKey::And(
                     NonEmptyVec::try_from(vec![SearchKey::Answered, SearchKey::Seen]).unwrap(),
@@ -184,9 +188,10 @@ mod tests {
             (SearchKey::Answered, b"ANSWERED"),
             (SearchKey::Bcc(AString::try_from("A").unwrap()), b"BCC A"),
             (
-                SearchKey::Before(NaiveDate(
-                    chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap(),
-                )),
+                SearchKey::Before(
+                    NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap())
+                        .unwrap(),
+                ),
                 b"BEFORE \"12-Apr-2023\"",
             ),
             (SearchKey::Body(AString::try_from("A").unwrap()), b"BODY A"),
@@ -211,9 +216,10 @@ mod tests {
             (SearchKey::Not(Box::new(SearchKey::New)), b"NOT NEW"),
             (SearchKey::Old, b"OLD"),
             (
-                SearchKey::On(NaiveDate(
-                    chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap(),
-                )),
+                SearchKey::On(
+                    NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap())
+                        .unwrap(),
+                ),
                 b"ON \"12-Apr-2023\"",
             ),
             (
@@ -223,27 +229,31 @@ mod tests {
             (SearchKey::Recent, b"RECENT"),
             (SearchKey::Seen, b"SEEN"),
             (
-                SearchKey::SentBefore(NaiveDate(
-                    chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap(),
-                )),
+                SearchKey::SentBefore(
+                    NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap())
+                        .unwrap(),
+                ),
                 b"SENTBEFORE \"12-Apr-2023\"",
             ),
             (
-                SearchKey::SentOn(NaiveDate(
-                    chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap(),
-                )),
+                SearchKey::SentOn(
+                    NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap())
+                        .unwrap(),
+                ),
                 b"SENTON \"12-Apr-2023\"",
             ),
             (
-                SearchKey::SentSince(NaiveDate(
-                    chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap(),
-                )),
+                SearchKey::SentSince(
+                    NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap())
+                        .unwrap(),
+                ),
                 b"SENTSINCE \"12-Apr-2023\"",
             ),
             (
-                SearchKey::Since(NaiveDate(
-                    chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap(),
-                )),
+                SearchKey::Since(
+                    NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 04, 12).unwrap())
+                        .unwrap(),
+                ),
                 b"SINCE \"12-Apr-2023\"",
             ),
             (SearchKey::Smaller(1337), b"SMALLER 1337"),
diff --git a/imap-types/src/imap4rev1/datetime.rs b/imap-types/src/imap4rev1/datetime.rs
index c0f82af1..ba66289e 100644
--- a/imap-types/src/imap4rev1/datetime.rs
+++ b/imap-types/src/imap4rev1/datetime.rs
@@ -1,14 +1,59 @@
-use std::fmt::{Debug, Formatter};
+use std::{
+    convert::TryFrom,
+    fmt::{Debug, Formatter},
+};
 
 #[cfg(feature = "bounded-static")]
 use bounded_static::{IntoBoundedStatic, ToBoundedStatic};
-use chrono::{DateTime as ChronoDateTime, FixedOffset, NaiveDate as ChronoNaiveDate};
+use chrono::{Datelike, FixedOffset};
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Clone, Eq, PartialEq, Hash)]
-pub struct DateTime(pub ChronoDateTime<FixedOffset>);
+pub struct DateTime(chrono::DateTime<FixedOffset>);
+
+impl DateTime {
+    pub fn unchecked(value: chrono::DateTime<FixedOffset>) -> Self {
+        Self(value)
+    }
+}
+
+impl TryFrom<chrono::DateTime<FixedOffset>> for DateTime {
+    type Error = DateTimeError;
+
+    fn try_from(value: chrono::DateTime<FixedOffset>) -> Result<Self, Self::Error> {
+        // Only a subset of `chrono`s `DateTime<FixedOffset>` is valid in IMAP.
+        if !(0..=9999).contains(&value.year()) {
+            return Err(DateTimeError::YearOutOfRange { got: value.year() });
+        }
+
+        if value.timestamp_subsec_nanos() != 0 {
+            return Err(DateTimeError::UnalignedNanoSeconds {
+                got: value.timestamp_subsec_nanos(),
+            });
+        }
+
+        if value.offset().local_minus_utc() % 60 != 0 {
+            return Err(DateTimeError::UnalignedOffset {
+                got: value.offset().local_minus_utc() % 60,
+            });
+        }
+
+        Ok(Self(value))
+    }
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum DateTimeError {
+    #[error("expected `0 <= year <= 9999`, got {got}")]
+    YearOutOfRange { got: i32 },
+    #[error("expected `nanos == 0`, got {got}")]
+    UnalignedNanoSeconds { got: u32 },
+    #[error("expected `offset % 60 == 0`, got {got}")]
+    UnalignedOffset { got: i32 },
+}
 
 impl Debug for DateTime {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -16,6 +61,12 @@ impl Debug for DateTime {
     }
 }
 
+impl AsRef<chrono::DateTime<FixedOffset>> for DateTime {
+    fn as_ref(&self) -> &chrono::DateTime<FixedOffset> {
+        &self.0
+    }
+}
+
 #[cfg(feature = "bounded-static")]
 impl IntoBoundedStatic for DateTime {
     type Static = Self;
@@ -36,7 +87,32 @@ impl ToBoundedStatic for DateTime {
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Clone, Eq, PartialEq, Hash)]
-pub struct NaiveDate(pub ChronoNaiveDate);
+pub struct NaiveDate(chrono::NaiveDate);
+
+impl NaiveDate {
+    pub fn unchecked(value: chrono::NaiveDate) -> Self {
+        Self(value)
+    }
+}
+
+impl TryFrom<chrono::NaiveDate> for NaiveDate {
+    type Error = NaiveDateError;
+
+    fn try_from(value: chrono::NaiveDate) -> Result<Self, Self::Error> {
+        // Only a subset of `chrono`s `NaiveDate` is valid in IMAP.
+        if !(0..=9999).contains(&value.year()) {
+            return Err(NaiveDateError::YearOutOfRange { got: value.year() });
+        }
+
+        Ok(Self(value))
+    }
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum NaiveDateError {
+    #[error("expected `0 <= year <= 9999`, got {got}")]
+    YearOutOfRange { got: i32 },
+}
 
 impl Debug for NaiveDate {
     fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
@@ -44,6 +120,12 @@ impl Debug for NaiveDate {
     }
 }
 
+impl AsRef<chrono::NaiveDate> for NaiveDate {
+    fn as_ref(&self) -> &chrono::NaiveDate {
+        &self.0
+    }
+}
+
 #[cfg(feature = "bounded-static")]
 impl IntoBoundedStatic for NaiveDate {
     type Static = Self;
@@ -61,3 +143,114 @@ impl ToBoundedStatic for NaiveDate {
         self.clone()
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::convert::TryFrom;
+
+    use chrono::{TimeZone, Timelike};
+
+    use crate::{
+        imap4rev1::datetime::DateTimeError, message::DateTime, testing::known_answer_test_encode,
+    };
+
+    #[test]
+    fn test_encode_date_time() {
+        let tests = [
+            (
+                DateTime::try_from(
+                    chrono::DateTime::parse_from_rfc2822("Mon, 7 Feb 1994 21:52:25 -0800 (PST)")
+                        .unwrap(),
+                )
+                .unwrap(),
+                b"\"07-Feb-1994 21:52:25 -0800\"".as_ref(),
+            ),
+            (
+                DateTime::try_from(
+                    chrono::DateTime::parse_from_rfc2822("Mon, 7 Feb 0000 21:52:25 -0800 (PST)")
+                        .unwrap(),
+                )
+                .unwrap(),
+                b"\"07-Feb-0000 21:52:25 -0800\"".as_ref(),
+            ),
+        ];
+
+        for test in tests {
+            known_answer_test_encode(test);
+        }
+    }
+
+    #[test]
+    fn test_conversion_date_time_failing() {
+        let tests = [
+            (
+                DateTime::try_from(
+                    chrono::FixedOffset::east_opt(3600)
+                        .unwrap()
+                        .from_local_datetime(&chrono::NaiveDateTime::new(
+                            chrono::NaiveDate::from_ymd_opt(-1, 2, 1).unwrap(),
+                            chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
+                        ))
+                        .unwrap(),
+                ),
+                DateTimeError::YearOutOfRange { got: -1 },
+            ),
+            (
+                DateTime::try_from(
+                    chrono::FixedOffset::east_opt(3600)
+                        .unwrap()
+                        .from_local_datetime(&chrono::NaiveDateTime::new(
+                            chrono::NaiveDate::from_ymd_opt(10000, 2, 1).unwrap(),
+                            chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
+                        ))
+                        .unwrap(),
+                ),
+                DateTimeError::YearOutOfRange { got: 10000 },
+            ),
+            (
+                DateTime::try_from(
+                    chrono::FixedOffset::east_opt(1)
+                        .unwrap()
+                        .from_local_datetime(&chrono::NaiveDateTime::new(
+                            chrono::NaiveDate::from_ymd_opt(0, 2, 1).unwrap(),
+                            chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
+                        ))
+                        .unwrap(),
+                ),
+                DateTimeError::UnalignedOffset { got: 1 },
+            ),
+            (
+                DateTime::try_from(
+                    chrono::FixedOffset::east_opt(59)
+                        .unwrap()
+                        .from_local_datetime(&chrono::NaiveDateTime::new(
+                            chrono::NaiveDate::from_ymd_opt(9999, 2, 1).unwrap(),
+                            chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
+                        ))
+                        .unwrap(),
+                ),
+                DateTimeError::UnalignedOffset { got: 59 },
+            ),
+            (
+                DateTime::try_from(
+                    chrono::FixedOffset::east_opt(60)
+                        .unwrap()
+                        .from_local_datetime(&chrono::NaiveDateTime::new(
+                            chrono::NaiveDate::from_ymd_opt(0, 2, 1).unwrap(),
+                            chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
+                        ))
+                        .unwrap()
+                        .with_nanosecond(1)
+                        .unwrap(),
+                ),
+                DateTimeError::UnalignedNanoSeconds { got: 1 },
+            ),
+        ];
+
+        for (got, expected) in tests {
+            println!("{}", got.clone().unwrap_err());
+            println!("{:?}", got.clone().unwrap_err());
+            assert_eq!(expected, got.unwrap_err());
+        }
+    }
+}
diff --git a/imap-types/src/imap4rev1/fetch_attributes.rs b/imap-types/src/imap4rev1/fetch_attributes.rs
index c49dd9fd..4af2ea2d 100644
--- a/imap-types/src/imap4rev1/fetch_attributes.rs
+++ b/imap-types/src/imap4rev1/fetch_attributes.rs
@@ -394,9 +394,13 @@ mod tests {
             ),
             (FetchAttributeValue::Flags(vec![]), b"FLAGS ()"),
             (
-                FetchAttributeValue::InternalDate(DateTime(
-                    chrono::DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200").unwrap(),
-                )),
+                FetchAttributeValue::InternalDate(
+                    DateTime::try_from(
+                        chrono::DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")
+                            .unwrap(),
+                    )
+                    .unwrap(),
+                ),
                 b"INTERNALDATE \"01-Jul-2003 10:52:37 +0200\"",
             ),
             (FetchAttributeValue::Rfc822(NString(None)), b"RFC822 NIL"),
diff --git a/imap-types/src/state.rs b/imap-types/src/state.rs
index 877960ac..8eaa7640 100644
--- a/imap-types/src/state.rs
+++ b/imap-types/src/state.rs
@@ -102,6 +102,7 @@ mod tests {
     #[cfg(feature = "ext_idle")]
     use std::convert::TryFrom;
 
+    #[cfg(feature = "bounded-static")]
     use bounded_static::{IntoBoundedStatic, ToBoundedStatic};
 
     #[cfg(feature = "ext_idle")]
diff --git a/src/imap4rev1/command/search.rs b/src/imap4rev1/command/search.rs
index 13039f77..4ab1c287 100644
--- a/src/imap4rev1/command/search.rs
+++ b/src/imap4rev1/command/search.rs
@@ -216,11 +216,7 @@ fn search_key_limited<'a>(
             map(sequence_set, SearchKey::SequenceSet),
             map(
                 delimited(tag(b"("), separated_list1(SP, search_key), tag(b")")),
-                |mut val| match val.len() {
-                    0 => unreachable!(),
-                    1 => val.pop().unwrap(),
-                    _ => SearchKey::And(NonEmptyVec::new_unchecked(val)),
-                },
+                |val| SearchKey::And(NonEmptyVec::new_unchecked(val)),
             ),
         )),
     ))(input)
@@ -243,16 +239,16 @@ mod tests {
             val,
             CommandBody::Search {
                 charset: None,
-                criteria: Uid(SequenceSetData(
+                criteria: And(NonEmptyVec::from(Uid(SequenceSetData(
                     vec![Single(Value(5.try_into().unwrap()))]
                         .try_into()
                         .unwrap()
-                )),
+                )))),
                 uid: false,
             }
         );
 
-        let (_rem, val) = search(b"search (uid 5 or uid 5 (uid 1 uid 2) not (uid 5))???").unwrap();
+        let (_rem, val) = search(b"search (uid 5 or uid 5 (uid 1 uid 2) not uid 5)???").unwrap();
         let expected = CommandBody::Search {
             charset: None,
             criteria: And(vec![
diff --git a/src/imap4rev1/datetime.rs b/src/imap4rev1/datetime.rs
index cc4f8e7d..248e476b 100644
--- a/src/imap4rev1/datetime.rs
+++ b/src/imap4rev1/datetime.rs
@@ -13,12 +13,16 @@ use nom::{
     IResult,
 };
 
-/// `date = date-text / DQUOTE date-text DQUOTE`
+/// ```abnf
+/// date = date-text / DQUOTE date-text DQUOTE
+/// ```
 pub fn date(input: &[u8]) -> IResult<&[u8], Option<NaiveDate>> {
     alt((date_text, delimited(DQUOTE, date_text, DQUOTE)))(input)
 }
 
-/// `date-text = date-day "-" date-month "-" date-year`
+/// ```abnf
+/// date-text = date-day "-" date-month "-" date-year
+/// ```
 pub fn date_text(input: &[u8]) -> IResult<&[u8], Option<NaiveDate>> {
     let mut parser = tuple((date_day, tag(b"-"), date_month, tag(b"-"), date_year));
 
@@ -26,20 +30,24 @@ pub fn date_text(input: &[u8]) -> IResult<&[u8], Option<NaiveDate>> {
 
     Ok((
         remaining,
-        ChronoNaiveDate::from_ymd_opt(y.into(), m.into(), d.into()).map(NaiveDate),
+        ChronoNaiveDate::from_ymd_opt(y.into(), m.into(), d.into()).map(NaiveDate::unchecked),
     ))
 }
 
-/// `date-day = 1*2DIGIT`
+/// Day of month.
 ///
-/// Day of month
+/// ```abnf
+/// date-day = 1*2DIGIT
+/// ```
 pub fn date_day(input: &[u8]) -> IResult<&[u8], u8> {
     digit_1_2(input)
 }
 
-/// `date-month = "Jan" / "Feb" / "Mar" / "Apr" /
+/// ```abnf
+/// date-month = "Jan" / "Feb" / "Mar" / "Apr" /
 ///              "May" / "Jun" / "Jul" / "Aug" /
-///              "Sep" / "Oct" / "Nov" / "Dec"`
+///              "Sep" / "Oct" / "Nov" / "Dec"
+/// ```
 pub fn date_month(input: &[u8]) -> IResult<&[u8], u8> {
     alt((
         value(1, tag_no_case(b"Jan")),
@@ -57,14 +65,18 @@ pub fn date_month(input: &[u8]) -> IResult<&[u8], u8> {
     ))(input)
 }
 
-/// `date-year = 4DIGIT`
+/// ```abnf
+/// date-year = 4DIGIT
+/// ```
 pub fn date_year(input: &[u8]) -> IResult<&[u8], u16> {
     digit_4(input)
 }
 
-/// `time = 2DIGIT ":" 2DIGIT ":" 2DIGIT`
+/// Hours minutes seconds.
 ///
-/// Hours minutes seconds
+/// ```abnf
+/// time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+/// ```
 pub fn time(input: &[u8]) -> IResult<&[u8], Option<NaiveTime>> {
     let mut parser = tuple((digit_2, tag(b":"), digit_2, tag(b":"), digit_2));
 
@@ -76,10 +88,13 @@ pub fn time(input: &[u8]) -> IResult<&[u8], Option<NaiveTime>> {
     ))
 }
 
-/// `date-time = DQUOTE
+/// ```abnf
+/// date-time = DQUOTE
 ///              date-day-fixed "-" date-month "-" date-year SP
-///              time SP zone
-///              DQUOTE`
+///              time SP
+///              zone
+///             DQUOTE
+/// ```
 pub fn date_time(input: &[u8]) -> IResult<&[u8], DateTime> {
     let mut parser = delimited(
         DQUOTE,
@@ -107,7 +122,7 @@ pub fn date_time(input: &[u8]) -> IResult<&[u8], DateTime> {
 
             // TODO: Not sure about that...
             if let LocalResult::Single(datetime) = zone.from_local_datetime(&local_datetime) {
-                Ok((remaining, DateTime(datetime)))
+                Ok((remaining, DateTime::unchecked(datetime)))
             } else {
                 Err(nom::Err::Failure(nom::error::Error::new(
                     remaining,
@@ -122,9 +137,11 @@ pub fn date_time(input: &[u8]) -> IResult<&[u8], DateTime> {
     }
 }
 
-/// `date-day-fixed = (SP DIGIT) / 2DIGIT`
+/// Fixed-format version of date-day.
 ///
-/// Fixed-format version of date-day
+/// ```abnf
+/// date-day-fixed = (SP DIGIT) / 2DIGIT
+/// ```
 pub fn date_day_fixed(input: &[u8]) -> IResult<&[u8], u8> {
     alt((
         map(preceded(SP, take_while_m_n(1, 1, is_DIGIT)), |bytes| {
@@ -134,14 +151,15 @@ pub fn date_day_fixed(input: &[u8]) -> IResult<&[u8], u8> {
     ))(input)
 }
 
-/// `zone = ("+" / "-") 4DIGIT`
+/// Signed four-digit value of hhmm representing hours and minutes east of Greenwich (that is, the
+/// amount that the given time differs from Universal Time).
 ///
-/// Signed four-digit value of hhmm representing
-/// hours and minutes east of Greenwich (that is,
-/// the amount that the given time differs from
-/// Universal Time).  Subtracting the timezone
-/// from the given time will give the UT form.
-/// The Universal Time zone is "+0000".
+/// Subtracting the timezone from the given time will give the UT form. The Universal Time zone is
+/// "+0000".
+///
+/// ```abnf
+/// zone = ("+" / "-") 4DIGIT
+/// ```
 pub fn zone(input: &[u8]) -> IResult<&[u8], Option<FixedOffset>> {
     let mut parser = tuple((alt((char('+'), char('-'))), digit_2, digit_2));
 
@@ -158,20 +176,6 @@ pub fn zone(input: &[u8]) -> IResult<&[u8], Option<FixedOffset>> {
     Ok((remaining, zone))
 }
 
-//fn digit_min_max(min: usize, max: usize) -> impl Fn(&[u8]) -> IResult<&[u8], ...> { // u8, u16, ...
-//    move |input| {
-//        map_res(
-//            map(take_while_m_n(min, max, is_DIGIT), |bytes|
-//                // # Safety
-//                //
-//                // `bytes` is always UTF-8.
-//                std::str::from_utf8(bytes).unwrap()
-//            ),
-//            str::parse::<u8>,
-//        )(input)
-//    }
-//}
-
 fn digit_1_2(input: &[u8]) -> IResult<&[u8], u8> {
     map_res(
         map(take_while_m_n(1, 2, is_DIGIT), |bytes| {
@@ -210,7 +214,7 @@ fn digit_4(input: &[u8]) -> IResult<&[u8], u16> {
 
 #[cfg(test)]
 mod tests {
-    use std::str::from_utf8;
+    use std::{convert::TryFrom, str::from_utf8};
 
     use super::*;
 
@@ -220,21 +224,21 @@ mod tests {
         assert_eq!(rem, b"xxx");
         assert_eq!(
             val,
-            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate)
+            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unchecked)
         );
 
         let (rem, val) = date(b"\"1-Feb-2020\"xxx").unwrap();
         assert_eq!(rem, b"xxx");
         assert_eq!(
             val,
-            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate)
+            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unchecked)
         );
 
         let (rem, val) = date(b"\"01-Feb-2020\"xxx").unwrap();
         assert_eq!(rem, b"xxx");
         assert_eq!(
             val,
-            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate)
+            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unchecked)
         );
     }
 
@@ -244,7 +248,7 @@ mod tests {
         assert_eq!(rem, b"");
         assert_eq!(
             val,
-            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate)
+            ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unchecked)
         );
     }
 
@@ -337,12 +341,13 @@ mod tests {
             NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
         );
 
-        let datetime = DateTime(
+        let datetime = DateTime::try_from(
             FixedOffset::east_opt(3600)
                 .unwrap()
                 .from_local_datetime(&local_datetime)
                 .unwrap(),
-        );
+        )
+        .unwrap();
 
         println!("{:?} == \n{:?}", val, datetime);
 


Test patch:
diff --git a/tests/trace.rs b/tests/trace.rs
index 882a64f3..28e1bde6 100644
--- a/tests/trace.rs
+++ b/tests/trace.rs
@@ -962,10 +962,10 @@ fn test_transcript_from_rfc() {
                         12,
                         vec![
                             FetchAttributeValue::Flags(vec![FlagFetch::Flag(Flag::Seen)]),
-                            FetchAttributeValue::InternalDate(DateTime(
+                            FetchAttributeValue::InternalDate(DateTime::try_from(
                                 chrono::DateTime::parse_from_rfc3339("1996-07-17T02:44:25-07:00")
                                     .unwrap(),
-                            )),
+                            ).unwrap()),
                             FetchAttributeValue::Rfc822Size(4286),
                             FetchAttributeValue::Envelope(Envelope {
                                 date: NString::from(