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

Issue:
feat(api): Implement error reporting in constructors.



Discussion / hints:

Fix patch:
diff --git a/imap-types/Cargo.toml b/imap-types/Cargo.toml
index c04e02f9..54820513 100644
--- a/imap-types/Cargo.toml
+++ b/imap-types/Cargo.toml
@@ -34,6 +34,7 @@ bounded-static = { version = "0.4", optional = true }
 chrono = { version = "0.4", default-features = false, features = ["alloc"] }
 serde = { version = "1", features = ["derive"], optional = true }
 subtle = "2.5.0"
+thiserror = "1.0.40"
 
 [dev-dependencies]
 criterion = "0.4"
diff --git a/imap-types/src/codec.rs b/imap-types/src/codec.rs
index 3ae134f8..46072404 100644
--- a/imap-types/src/codec.rs
+++ b/imap-types/src/codec.rs
@@ -152,8 +152,8 @@ impl<'a> Encode for CommandBody<'a> {
                 mailbox.encode(writer)
             }
             CommandBody::Rename {
-                mailbox,
-                new_mailbox,
+                from: mailbox,
+                to: new_mailbox,
             } => {
                 writer.write_all(b"RENAME")?;
                 writer.write_all(b" ")?;
diff --git a/imap-types/src/extensions/rfc4987.rs b/imap-types/src/extensions/rfc4987.rs
index 957e73fe..6edbb1a8 100644
--- a/imap-types/src/extensions/rfc4987.rs
+++ b/imap-types/src/extensions/rfc4987.rs
@@ -10,7 +10,7 @@
 //! * the [Command](crate::command::Command) enum with a new variant [Command::Compress](crate::command::Command#variant.Compress), and
 //! * the [Code](crate::response::Code) enum with a new variant [Code::CompressionActive](crate::response::Code#variant.CompressionActive).
 
-use std::{borrow::Cow, convert::TryFrom, io::Write};
+use std::{convert::TryFrom, io::Write};
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::Arbitrary;
@@ -18,8 +18,9 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
-use crate::{codec::Encode, core::Atom, rfc3501::core::impl_try_from_try_from};
+use crate::{codec::Encode, core::Atom};
 
 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 #[cfg_attr(feature = "bounded-static", derive(ToStatic))]
@@ -29,19 +30,13 @@ pub enum CompressionAlgorithm {
     Deflate,
 }
 
-impl_try_from_try_from!(Atom, 'a, &'a str, CompressionAlgorithm);
-impl_try_from_try_from!(Atom, 'a, &'a [u8], CompressionAlgorithm);
-impl_try_from_try_from!(Atom, 'a, Vec<u8>, CompressionAlgorithm);
-impl_try_from_try_from!(Atom, 'a, String, CompressionAlgorithm);
-impl_try_from_try_from!(Atom, 'a, Cow<'a, str>, CompressionAlgorithm);
-
 impl<'a> TryFrom<Atom<'a>> for CompressionAlgorithm {
-    type Error = ();
+    type Error = CompressionAlgorithmError;
 
-    fn try_from(atom: Atom<'a>) -> Result<Self, ()> {
+    fn try_from(atom: Atom<'a>) -> Result<Self, Self::Error> {
         match atom.as_ref().to_ascii_lowercase().as_ref() {
             "deflate" => Ok(Self::Deflate),
-            _ => Err(()),
+            _ => Err(CompressionAlgorithmError::Invalid),
         }
     }
 }
@@ -61,3 +56,9 @@ impl Encode for CompressionAlgorithm {
         }
     }
 }
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum CompressionAlgorithmError {
+    #[error("Invalid compression algorithm. Allowed value: `COMPRESS`.")]
+    Invalid,
+}
diff --git a/imap-types/src/extensions/rfc9208.rs b/imap-types/src/extensions/rfc9208.rs
index f9ae37c3..678a8f23 100644
--- a/imap-types/src/extensions/rfc9208.rs
+++ b/imap-types/src/extensions/rfc9208.rs
@@ -33,11 +33,7 @@
 //!     - [StatusAttributeValue::Deleted](crate::response::data::StatusAttributeValue::Deleted)
 //!     - [StatusAttributeValue::DeletedStorage](crate::response::data::StatusAttributeValue::DeletedStorage)
 
-use std::{
-    borrow::Cow,
-    convert::{TryFrom, TryInto},
-    io::Write,
-};
+use std::{borrow::Cow, convert::TryFrom, io::Write};
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::Arbitrary;
@@ -45,10 +41,11 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 use crate::{
     codec::Encode,
-    rfc3501::core::{impl_try_from, impl_try_from_try_from, Atom},
+    rfc3501::core::{impl_try_from, Atom, AtomError},
 };
 
 /// A resource type for use in IMAP's QUOTA extension.
@@ -107,30 +104,16 @@ pub enum Resource<'a> {
     /// "QUOTA=RES-ANNOTATION-STORAGE" capability.
     AnnotationStorage,
     /// Other.
-    Other(ResourceOther<'a>),
-}
-
-impl<'a> Resource<'a> {
-    /// Try to create a non-standard resource from a value.
     ///
     /// Note: The value must be semantically different from the supported variants.
-    pub fn other<A>(other: A) -> Result<Self, ()>
-    where
-        A: TryInto<Atom<'a>>,
-    {
-        let atom = other.try_into().map_err(|_| ())?;
-
-        Ok(Resource::Other(
-            ResourceOther::try_from(atom).map_err(|_| ())?,
-        ))
-    }
+    Other(ResourceOther<'a>),
 }
 
-impl_try_from!(Atom, 'a, &'a [u8], Resource<'a>);
-impl_try_from!(Atom, 'a, Vec<u8>, Resource<'a>);
-impl_try_from!(Atom, 'a, &'a str, Resource<'a>);
-impl_try_from!(Atom, 'a, String, Resource<'a>);
-impl_try_from!(Atom, 'a, Cow<'a, str>, Resource<'a>);
+impl_try_from!(Atom<'a>, 'a, &'a [u8], Resource<'a>);
+impl_try_from!(Atom<'a>, 'a, Vec<u8>, Resource<'a>);
+impl_try_from!(Atom<'a>, 'a, &'a str, Resource<'a>);
+impl_try_from!(Atom<'a>, 'a, String, Resource<'a>);
+impl_try_from!(Atom<'a>, 'a, Cow<'a, str>, Resource<'a>);
 
 impl<'a> From<Atom<'a>> for Resource<'a> {
     fn from(value: Atom<'a>) -> Self {
@@ -163,29 +146,62 @@ impl<'a> Encode for Resource<'a> {
 pub struct ResourceOther<'a>(Atom<'a>);
 
 impl<'a> ResourceOther<'a> {
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), ResourceOtherError> {
+        if matches!(
+            value.as_ref().to_ascii_lowercase().as_slice(),
+            b"storage" | b"message" | b"mailbox" | b"annotation-storage",
+        ) {
+            return Err(ResourceOtherError::Reserved);
+        }
+
+        Ok(())
+    }
+
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(atom: Atom<'a>) -> Self {
         Self(atom)
     }
 }
 
-impl_try_from_try_from!(Atom, 'a, &'a [u8], ResourceOther<'a>);
-impl_try_from_try_from!(Atom, 'a, Vec<u8>, ResourceOther<'a>);
-impl_try_from_try_from!(Atom, 'a, &'a str, ResourceOther<'a>);
-impl_try_from_try_from!(Atom, 'a, String, ResourceOther<'a>);
-impl_try_from_try_from!(Atom, 'a, Cow<'a, str>, ResourceOther<'a>);
+macro_rules! impl_try_from {
+    ($from:ty) => {
+        impl<'a> TryFrom<$from> for ResourceOther<'a> {
+            type Error = ResourceOtherError;
+
+            fn try_from(value: $from) -> Result<Self, Self::Error> {
+                let atom = Atom::try_from(value)?;
+
+                Self::verify(atom.as_ref())?;
+
+                Ok(Self(atom))
+            }
+        }
+    };
+}
+
+impl_try_from!(&'a [u8]);
+impl_try_from!(Vec<u8>);
+impl_try_from!(&'a str);
+impl_try_from!(String);
 
 impl<'a> TryFrom<Atom<'a>> for ResourceOther<'a> {
-    type Error = ();
+    type Error = ResourceOtherError;
 
     fn try_from(atom: Atom<'a>) -> Result<Self, Self::Error> {
-        match atom.0.to_lowercase().as_ref() {
-            "storage" | "message" | "mailbox" | "annotation-storage" => Err(()),
-            _ => Ok(Self(atom)),
-        }
+        Self::verify(atom.as_ref())?;
+
+        Ok(Self(atom))
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum ResourceOtherError {
+    #[error(transparent)]
+    Atom(#[from] AtomError),
+    #[error("Reserved. Please use one of the typed variants.")]
+    Reserved,
+}
+
 impl<'a> Encode for ResourceOther<'a> {
     fn encode(&self, writer: &mut impl Write) -> std::io::Result<()> {
         self.0.encode(writer)
@@ -245,6 +261,30 @@ impl<'a> Encode for QuotaSet<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum QuotaError<R, Q> {
+    #[error("Invalid root: {0:?}")]
+    Root(R),
+    #[error("Invalid quotas: {0:?}")]
+    Quotas(Q),
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum QuotaRootError<M, R> {
+    #[error("Invalid root: {0:?}")]
+    Mailbox(M),
+    #[error("Invalid quotas: {0:?}")]
+    Roots(R),
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum SetQuotaError<R, S> {
+    #[error("Invalid root: {0:?}")]
+    Root(R),
+    #[error("Invalid quota set: {0:?}")]
+    QuotaSet(S),
+}
+
 #[cfg(test)]
 mod tests {
     use std::convert::TryInto;
@@ -252,9 +292,9 @@ mod tests {
     use super::*;
     use crate::{codec::Encode, response::Data, rfc3501::command::CommandBody};
 
-    fn compare_output(items: Vec<(Result<impl Encode, ()>, &str)>) {
+    fn compare_output(items: Vec<(impl Encode, &str)>) {
         for item in items {
-            let out = item.0.unwrap().encode_detached().unwrap();
+            let out = item.0.encode_detached().unwrap();
             assert_eq!(std::str::from_utf8(&out).unwrap(), item.1);
         }
     }
@@ -262,13 +302,16 @@ mod tests {
     #[test]
     fn test_command_output() {
         let commands = vec![
-            (CommandBody::get_quota("INBOX"), "GETQUOTA INBOX"),
-            (CommandBody::get_quota(""), "GETQUOTA \"\""),
+            (CommandBody::get_quota("INBOX").unwrap(), "GETQUOTA INBOX"),
+            (CommandBody::get_quota("").unwrap(), "GETQUOTA \"\""),
             (
-                CommandBody::get_quota_root("MAILBOX"),
+                CommandBody::get_quota_root("MAILBOX").unwrap(),
                 "GETQUOTAROOT MAILBOX",
             ),
-            (CommandBody::set_quota("INBOX", vec![]), "SETQUOTA INBOX ()"),
+            (
+                CommandBody::set_quota("INBOX", vec![]).unwrap(),
+                "SETQUOTA INBOX ()",
+            ),
             (
                 CommandBody::set_quota(
                     "INBOX",
@@ -276,7 +319,8 @@ mod tests {
                         resource: Resource::Storage,
                         limit: 256,
                     }],
-                ),
+                )
+                .unwrap(),
                 "SETQUOTA INBOX (STORAGE 256)",
             ),
             (
@@ -292,7 +336,8 @@ mod tests {
                             limit: 512,
                         },
                     ],
-                ),
+                )
+                .unwrap(),
                 "SETQUOTA INBOX (MESSAGE 256 STORAGE 512)",
             ),
         ];
@@ -311,15 +356,20 @@ mod tests {
                         usage: 1024,
                         limit: 2048,
                     }],
-                ),
+                )
+                .unwrap(),
                 "* QUOTA INBOX (MESSAGE 1024 2048)\r\n",
             ),
-            (Data::quota_root("INBOX", vec![]), "* QUOTAROOT INBOX\r\n"),
+            (
+                Data::quota_root("INBOX", vec![]).unwrap(),
+                "* QUOTAROOT INBOX\r\n",
+            ),
             (
                 Data::quota_root(
                     "INBOX",
                     vec!["ROOT1".try_into().unwrap(), "ROOT2".try_into().unwrap()],
-                ),
+                )
+                .unwrap(),
                 "* QUOTAROOT INBOX ROOT1 ROOT2\r\n",
             ),
         ];
diff --git a/imap-types/src/lib.rs b/imap-types/src/lib.rs
index fb202046..ad49f63a 100644
--- a/imap-types/src/lib.rs
+++ b/imap-types/src/lib.rs
@@ -215,7 +215,10 @@ pub mod command {
     //! # Types used in commands
 
     pub use crate::rfc3501::{
-        command::{AuthenticateData, Command, CommandBody},
+        command::{
+            AppendError, AuthenticateData, Command, CommandBody, CopyError, ListError, LoginError,
+            RenameError,
+        },
         mailbox::{ListCharString, ListMailbox},
         sequence::{SeqNo, Sequence, SequenceSet, Strategy},
     };
@@ -243,6 +246,11 @@ pub mod command {
         pub use crate::rfc3501::flag::{StoreResponse, StoreType};
     }
 
+    #[cfg(feature = "ext_quota")]
+    pub mod quota {
+        pub use crate::extensions::rfc9208::SetQuotaError;
+    }
+
     #[cfg(feature = "ext_idle")]
     pub mod idle {
         pub use crate::extensions::rfc2177::IdleDone;
@@ -272,6 +280,11 @@ pub mod response {
             status_attributes::StatusAttributeValue,
         };
     }
+
+    #[cfg(feature = "ext_quota")]
+    pub mod quota {
+        pub use crate::extensions::rfc9208::{QuotaError, QuotaRootError};
+    }
 }
 
 #[cfg(any(
diff --git a/imap-types/src/rfc3501/body.rs b/imap-types/src/rfc3501/body.rs
index f82db3de..f1fbe356 100644
--- a/imap-types/src/rfc3501/body.rs
+++ b/imap-types/src/rfc3501/body.rs
@@ -258,6 +258,7 @@ pub struct SinglePartExtensionData<'a> {
 
     /// A parenthesized list with the same content and function as
     /// the body disposition for a multipart body part.
+    #[allow(clippy::type_complexity)]
     pub disposition: Option<Option<(IString<'a>, Vec<(IString<'a>, IString<'a>)>)>>,
 
     /// A string or parenthesized list giving the body language
@@ -301,6 +302,7 @@ pub struct MultiPartExtensionData<'a> {
     /// A parenthesized list, consisting of a disposition type
     /// string, followed by a parenthesized list of disposition
     /// attribute/value pairs as defined in [DISPOSITION].
+    #[allow(clippy::type_complexity)]
     pub disposition: Option<Option<(IString<'a>, Vec<(IString<'a>, IString<'a>)>)>>,
 
     /// `body language`
diff --git a/imap-types/src/rfc3501/command.rs b/imap-types/src/rfc3501/command.rs
index ddef2cb5..a97f08ee 100644
--- a/imap-types/src/rfc3501/command.rs
+++ b/imap-types/src/rfc3501/command.rs
@@ -12,13 +12,14 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 #[cfg(feature = "ext_compress")]
 use crate::extensions::rfc4987::CompressionAlgorithm;
 #[cfg(feature = "ext_enable")]
 use crate::extensions::rfc5161::CapabilityEnable;
 #[cfg(feature = "ext_quota")]
-use crate::extensions::rfc9208::QuotaSet;
+use crate::extensions::rfc9208::{QuotaSet, SetQuotaError};
 use crate::{
     command::{
         fetch::MacroOrFetchAttributes,
@@ -41,12 +42,12 @@ pub struct Command<'a> {
 }
 
 impl<'a> Command<'a> {
-    pub fn new<T>(tag: T, body: CommandBody<'a>) -> Result<Self, ()>
+    pub fn new<T>(tag: T, body: CommandBody<'a>) -> Result<Self, T::Error>
     where
         T: TryInto<Tag<'a>>,
     {
         Ok(Self {
-            tag: tag.try_into().map_err(|_| ())?,
+            tag: tag.try_into()?,
             body,
         })
     }
@@ -513,10 +514,7 @@ pub enum CommandBody<'a> {
     /// leaving INBOX empty.  If the server implementation supports
     /// inferior hierarchical names of INBOX, these are unaffected by a
     /// rename of INBOX.
-    Rename {
-        mailbox: Mailbox<'a>,
-        new_mailbox: Mailbox<'a>,
-    },
+    Rename { from: Mailbox<'a>, to: Mailbox<'a> },
 
     /// ### 6.3.6.  SUBSCRIBE Command
     ///
@@ -1295,12 +1293,12 @@ pub enum CommandBody<'a> {
 }
 
 impl<'a> CommandBody<'a> {
-    pub fn tag<T>(self, tag: T) -> Result<Command<'a>, ()>
+    pub fn tag<T>(self, tag: T) -> Result<Command<'a>, T::Error>
     where
         T: TryInto<Tag<'a>>,
     {
         Ok(Command {
-            tag: tag.try_into().map_err(|_| ())?,
+            tag: tag.try_into()?,
             body: self,
         })
     }
@@ -1320,14 +1318,14 @@ impl<'a> CommandBody<'a> {
         }
     }
 
-    pub fn login<U, P>(username: U, password: P) -> Result<Self, ()>
+    pub fn login<U, P>(username: U, password: P) -> Result<Self, LoginError<U::Error, P::Error>>
     where
         U: TryInto<AString<'a>>,
         P: TryInto<AString<'a>>,
     {
         Ok(CommandBody::Login {
-            username: username.try_into().map_err(|_| ())?,
-            password: Secret::new(password.try_into().map_err(|_| ())?),
+            username: username.try_into().map_err(LoginError::Username)?,
+            password: Secret::new(password.try_into().map_err(LoginError::Password)?),
         })
     }
 
@@ -1367,14 +1365,14 @@ impl<'a> CommandBody<'a> {
         })
     }
 
-    pub fn rename<M1, M2>(mailbox: M1, new_mailbox: M2) -> Result<Self, ()>
+    pub fn rename<F, T>(mailbox: F, new_mailbox: T) -> Result<Self, RenameError<F::Error, T::Error>>
     where
-        M1: TryInto<Mailbox<'a>>,
-        M2: TryInto<Mailbox<'a>>,
+        F: TryInto<Mailbox<'a>>,
+        T: TryInto<Mailbox<'a>>,
     {
         Ok(CommandBody::Rename {
-            mailbox: mailbox.try_into().map_err(|_| ())?,
-            new_mailbox: new_mailbox.try_into().map_err(|_| ())?,
+            from: mailbox.try_into().map_err(RenameError::From)?,
+            to: new_mailbox.try_into().map_err(RenameError::To)?,
         })
     }
 
@@ -1396,25 +1394,31 @@ impl<'a> CommandBody<'a> {
         })
     }
 
-    pub fn list<A, B>(reference: A, mailbox_wildcard: B) -> Result<Self, ()>
+    pub fn list<A, B>(
+        reference: A,
+        mailbox_wildcard: B,
+    ) -> Result<Self, ListError<A::Error, B::Error>>
     where
         A: TryInto<Mailbox<'a>>,
         B: TryInto<ListMailbox<'a>>,
     {
         Ok(CommandBody::List {
-            reference: reference.try_into().map_err(|_| ())?,
-            mailbox_wildcard: mailbox_wildcard.try_into().map_err(|_| ())?,
+            reference: reference.try_into().map_err(ListError::Reference)?,
+            mailbox_wildcard: mailbox_wildcard.try_into().map_err(ListError::Mailbox)?,
         })
     }
 
-    pub fn lsub<A, B>(reference: A, mailbox_wildcard: B) -> Result<Self, ()>
+    pub fn lsub<A, B>(
+        reference: A,
+        mailbox_wildcard: B,
+    ) -> Result<Self, ListError<A::Error, B::Error>>
     where
         A: TryInto<Mailbox<'a>>,
         B: TryInto<ListMailbox<'a>>,
     {
         Ok(CommandBody::Lsub {
-            reference: reference.try_into().map_err(|_| ())?,
-            mailbox_wildcard: mailbox_wildcard.try_into().map_err(|_| ())?,
+            reference: reference.try_into().map_err(ListError::Reference)?,
+            mailbox_wildcard: mailbox_wildcard.try_into().map_err(ListError::Mailbox)?,
         })
     }
 
@@ -1433,16 +1437,16 @@ impl<'a> CommandBody<'a> {
         flags: Vec<Flag<'a>>,
         date: Option<MyDateTime>,
         message: D,
-    ) -> Result<Self, ()>
+    ) -> Result<Self, AppendError<M::Error, D::Error>>
     where
         M: TryInto<Mailbox<'a>>,
         D: TryInto<Literal<'a>>,
     {
         Ok(CommandBody::Append {
-            mailbox: mailbox.try_into().map_err(|_| ())?,
+            mailbox: mailbox.try_into().map_err(AppendError::Mailbox)?,
             flags,
             date,
-            message: message.try_into().map_err(|_| ())?,
+            message: message.try_into().map_err(AppendError::Data)?,
         })
     }
 
@@ -1489,14 +1493,18 @@ impl<'a> CommandBody<'a> {
         })
     }
 
-    pub fn copy<S, M>(sequence_set: S, mailbox: M, uid: bool) -> Result<Self, ()>
+    pub fn copy<S, M>(
+        sequence_set: S,
+        mailbox: M,
+        uid: bool,
+    ) -> Result<Self, CopyError<S::Error, M::Error>>
     where
         S: TryInto<SequenceSet>,
         M: TryInto<Mailbox<'a>>,
     {
         Ok(CommandBody::Copy {
-            sequence_set: sequence_set.try_into().map_err(|_| ())?,
-            mailbox: mailbox.try_into().map_err(|_| ())?,
+            sequence_set: sequence_set.try_into().map_err(CopyError::Sequence)?,
+            mailbox: mailbox.try_into().map_err(CopyError::Mailbox)?,
             uid,
         })
     }
@@ -1537,14 +1545,14 @@ impl<'a> CommandBody<'a> {
     }
 
     #[cfg(feature = "ext_quota")]
-    pub fn set_quota<R, S>(root: R, quotas: S) -> Result<Self, ()>
+    pub fn set_quota<R, S>(root: R, quotas: S) -> Result<Self, SetQuotaError<R::Error, S::Error>>
     where
         R: TryInto<AString<'a>>,
         S: TryInto<Vec<QuotaSet<'a>>>,
     {
         Ok(CommandBody::SetQuota {
-            root: root.try_into().map_err(|_| ())?,
-            quotas: quotas.try_into().map_err(|_| ())?,
+            root: root.try_into().map_err(SetQuotaError::Root)?,
+            quotas: quotas.try_into().map_err(SetQuotaError::QuotaSet)?,
         })
     }
 
@@ -1591,6 +1599,46 @@ impl<'a> CommandBody<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum LoginError<U, P> {
+    #[error("Invalid username: {0:?}")]
+    Username(U),
+    #[error("Invalid password: {0:?}")]
+    Password(P),
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum RenameError<F, T> {
+    #[error("Invalid mailbox (from): {0:?}")]
+    From(F),
+    #[error("Invalid mailbox (to): {0:?}")]
+    To(T),
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum ListError<R, M> {
+    #[error("Invalid reference: {0:?}")]
+    Reference(R),
+    #[error("Invalid mailbox: {0:?}")]
+    Mailbox(M),
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum AppendError<M, D> {
+    #[error("Invalid mailbox: {0:?}")]
+    Mailbox(M),
+    #[error("Invalid data: {0:?}")]
+    Data(D),
+}
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum CopyError<S, M> {
+    #[error("Invalid sequence: {0:?}")]
+    Sequence(S),
+    #[error("Invalid mailbox: {0:?}")]
+    Mailbox(M),
+}
+
 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 #[cfg_attr(feature = "bounded-static", derive(ToStatic))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1763,7 +1811,7 @@ mod tests {
     #[cfg(feature = "ext_enable")]
     use crate::extensions::rfc5161::{CapabilityEnable, Utf8Kind};
     #[cfg(feature = "ext_quota")]
-    use crate::extensions::rfc9208::{QuotaSet, Resource};
+    use crate::extensions::rfc9208::{QuotaSet, Resource, ResourceOther};
     use crate::{
         codec::Encode,
         command::{
@@ -1943,7 +1991,7 @@ mod tests {
                 "",
                 vec![
                     QuotaSet::new(Resource::Message, 1337),
-                    QuotaSet::new(Resource::other("spam").unwrap(), 0),
+                    QuotaSet::new(Resource::Other(ResourceOther::try_from("spam").unwrap()), 0),
                 ],
             )
             .unwrap(),
diff --git a/imap-types/src/rfc3501/core.rs b/imap-types/src/rfc3501/core.rs
index 14ed6447..c336413f 100644
--- a/imap-types/src/rfc3501/core.rs
+++ b/imap-types/src/rfc3501/core.rs
@@ -6,6 +6,7 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 use crate::utils::indicators::{
     is_any_text_char_except_quoted_specials, is_astring_char, is_atom_char, is_char8, is_text_char,
@@ -14,7 +15,7 @@ use crate::utils::indicators::{
 macro_rules! impl_try_from {
     ($via:ty, $lifetime:lifetime, $from:ty, $target:ty) => {
         impl<$lifetime> TryFrom<$from> for $target {
-            type Error = ();
+            type Error = <$via as TryFrom<$from>>::Error;
 
             fn try_from(value: $from) -> Result<Self, Self::Error> {
                 let value = <$via>::try_from(value)?;
@@ -25,22 +26,7 @@ macro_rules! impl_try_from {
     };
 }
 
-macro_rules! impl_try_from_try_from {
-    ($via:ty, $lifetime:lifetime, $from:ty, $target:ty) => {
-        impl<$lifetime> TryFrom<$from> for $target {
-            type Error = ();
-
-            fn try_from(value: $from) -> Result<Self, Self::Error> {
-                let value = <$via>::try_from(value)?;
-
-                Self::try_from(value)
-            }
-        }
-    };
-}
-
 pub(crate) use impl_try_from;
-pub(crate) use impl_try_from_try_from;
 
 /// An atom.
 ///
@@ -51,8 +37,21 @@ pub(crate) use impl_try_from_try_from;
 pub struct Atom<'a>(pub(crate) Cow<'a, str>);
 
 impl<'a> Atom<'a> {
-    pub fn verify(value: &[u8]) -> bool {
-        !value.is_empty() && value.iter().all(|b| is_atom_char(*b))
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), AtomError> {
+        let value = value.as_ref();
+
+        if value.is_empty() {
+            return Err(AtomError::Empty);
+        }
+
+        if let Some(position) = value.iter().position(|b| !is_atom_char(*b)) {
+            return Err(AtomError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     pub fn inner(&self) -> &str {
@@ -66,65 +65,61 @@ impl<'a> Atom<'a> {
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Cow<'a, str>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(inner.as_bytes()));
+        Self::verify(inner.as_bytes()).unwrap();
 
         Self(inner)
     }
 }
 
 impl<'a> TryFrom<&'a [u8]> for Atom<'a> {
-    type Error = ();
+    type Error = AtomError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        let str = from_utf8(value).map_err(|_| ())?;
+        Self::verify(value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Self(Cow::Borrowed(from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<Vec<u8>> for Atom<'a> {
-    type Error = ();
+    type Error = AtomError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        let str = String::from_utf8(value).map_err(|_| ())?;
+        Self::verify(&value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Self(Cow::Owned(String::from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<&'a str> for Atom<'a> {
-    type Error = ();
+    type Error = AtomError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Borrowed(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(Self(Cow::Borrowed(value)))
     }
 }
 
 impl<'a> TryFrom<String> for Atom<'a> {
-    type Error = ();
+    type Error = AtomError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Atom(Cow::Owned(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(&value)?;
+
+        Ok(Atom(Cow::Owned(value)))
     }
 }
 
 impl<'a> TryFrom<Cow<'a, str>> for Atom<'a> {
-    type Error = ();
+    type Error = AtomError;
 
     fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Atom(value))
-        } else {
-            Err(())
-        }
+        Self::verify(value.as_bytes())?;
+
+        Ok(Atom(value))
     }
 }
 
@@ -134,6 +129,14 @@ impl<'a> AsRef<str> for Atom<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum AtomError {
+    #[error("Must not be empty.")]
+    Empty,
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 /// An (extended) atom.
 ///
 /// According to IMAP's formal syntax, an atom with additional allowed chars.
@@ -143,64 +146,79 @@ impl<'a> AsRef<str> for Atom<'a> {
 pub struct AtomExt<'a>(pub(crate) Cow<'a, str>);
 
 impl<'a> AtomExt<'a> {
-    pub fn verify(value: &[u8]) -> bool {
-        !value.is_empty() && value.iter().all(|b| is_astring_char(*b))
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), AtomExtError> {
+        let value = value.as_ref();
+
+        if value.is_empty() {
+            return Err(AtomExtError::Empty);
+        }
+
+        if let Some(position) = value.iter().position(|b| !is_astring_char(*b)) {
+            return Err(AtomExtError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     pub fn inner(&self) -> &str {
         self.0.as_ref()
     }
 
+    pub fn into_inner(self) -> Cow<'a, str> {
+        self.0
+    }
+
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Cow<'a, str>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(inner.as_bytes()));
+        Self::verify(inner.as_bytes()).unwrap();
 
         Self(inner)
     }
 }
 
 impl<'a> TryFrom<&'a [u8]> for AtomExt<'a> {
-    type Error = ();
+    type Error = AtomExtError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        let str = from_utf8(value).map_err(|_| ())?;
+        Self::verify(value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Self(Cow::Borrowed(from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<Vec<u8>> for AtomExt<'a> {
-    type Error = ();
+    type Error = AtomExtError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        let str = String::from_utf8(value).map_err(|_| ())?;
+        Self::verify(&value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Self(Cow::Owned(String::from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<&'a str> for AtomExt<'a> {
-    type Error = ();
+    type Error = AtomExtError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Borrowed(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(Self(Cow::Borrowed(value)))
     }
 }
 
 impl<'a> TryFrom<String> for AtomExt<'a> {
-    type Error = ();
+    type Error = AtomExtError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Owned(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(&value)?;
+
+        Ok(Self(Cow::Owned(value)))
     }
 }
 
@@ -210,6 +228,14 @@ impl<'a> AsRef<str> for AtomExt<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum AtomExtError {
+    #[error("Must not be empty.")]
+    Empty,
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 // ## 4.2. Number
 //
 // A number consists of one or more digit characters, and
@@ -230,68 +256,52 @@ pub enum IString<'a> {
 }
 
 impl<'a> TryFrom<&'a [u8]> for IString<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: &'a [u8]) -> Result<Self, ()> {
+    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
         if let Ok(quoted) = Quoted::try_from(value) {
             return Ok(IString::Quoted(quoted));
         }
 
-        if let Ok(literal) = Literal::try_from(value) {
-            return Ok(IString::Literal(literal));
-        }
-
-        Err(())
+        Ok(IString::Literal(Literal::try_from(value)?))
     }
 }
 
 impl TryFrom<Vec<u8>> for IString<'_> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: Vec<u8>) -> Result<Self, ()> {
+    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
         // TODO(efficiency)
         if let Ok(quoted) = Quoted::try_from(value.clone()) {
             return Ok(IString::Quoted(quoted));
         }
 
-        if let Ok(literal) = Literal::try_from(value) {
-            return Ok(IString::Literal(literal));
-        }
-
-        Err(())
+        Ok(IString::Literal(Literal::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<&'a str> for IString<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: &'a str) -> Result<Self, ()> {
+    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
         if let Ok(quoted) = Quoted::try_from(value) {
             return Ok(IString::Quoted(quoted));
         }
 
-        if let Ok(literal) = Literal::try_from(value) {
-            return Ok(IString::Literal(literal));
-        }
-
-        Err(())
+        Ok(IString::Literal(Literal::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<String> for IString<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: String) -> Result<Self, ()> {
+    fn try_from(value: String) -> Result<Self, Self::Error> {
         // TODO(efficiency)
         if let Ok(quoted) = Quoted::try_from(value.clone()) {
             return Ok(IString::Quoted(quoted));
         }
 
-        if let Ok(literal) = Literal::try_from(value) {
-            return Ok(IString::Literal(literal));
-        }
-
-        Err(())
+        Ok(IString::Literal(Literal::try_from(value)?))
     }
 }
 
@@ -340,8 +350,17 @@ pub struct Literal<'a> {
 }
 
 impl<'a> Literal<'a> {
-    pub fn verify(bytes: &[u8]) -> bool {
-        bytes.iter().all(|b| is_char8(*b))
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), LiteralError> {
+        let value = value.as_ref();
+
+        if let Some(position) = value.iter().position(|b| !is_char8(*b)) {
+            return Err(LiteralError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     pub fn data(&self) -> &[u8] {
@@ -361,7 +380,7 @@ impl<'a> Literal<'a> {
     }
 
     /// Create a literal from a byte sequence without checking
-    /// that it to conforms to IMAP's literal specification.
+    /// that it conforms to IMAP's literal specification.
     ///
     /// # Safety
     ///
@@ -370,7 +389,7 @@ impl<'a> Literal<'a> {
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(data: Cow<'a, [u8]>, #[cfg(feature = "ext_literal")] sync: bool) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(&data));
+        Self::verify(&data).unwrap();
 
         Self {
             data,
@@ -381,50 +400,58 @@ impl<'a> Literal<'a> {
 }
 
 impl<'a> TryFrom<&'a [u8]> for Literal<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        if Literal::verify(value) {
-            Ok(Literal {
-                data: Cow::Borrowed(value),
-                #[cfg(feature = "ext_literal")]
-                sync: true,
-            })
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(Literal {
+            data: Cow::Borrowed(value),
+            #[cfg(feature = "ext_literal")]
+            sync: true,
+        })
     }
 }
 
 impl<'a> TryFrom<Vec<u8>> for Literal<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        if Literal::verify(&value) {
-            Ok(Literal {
-                data: Cow::Owned(value),
-                #[cfg(feature = "ext_literal")]
-                sync: true,
-            })
-        } else {
-            Err(())
-        }
+        Self::verify(&value)?;
+
+        Ok(Literal {
+            data: Cow::Owned(value),
+            #[cfg(feature = "ext_literal")]
+            sync: true,
+        })
     }
 }
 
 impl<'a> TryFrom<&'a str> for Literal<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        Self::try_from(value.as_bytes())
+        Self::verify(value)?;
+
+        Ok(Literal {
+            data: Cow::Borrowed(value.as_bytes()),
+            #[cfg(feature = "ext_literal")]
+            sync: true,
+        })
     }
 }
 
 impl<'a> TryFrom<String> for Literal<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        Self::try_from(value.into_bytes())
+        Self::verify(&value)?;
+
+        Ok(Literal {
+            data: Cow::Owned(value.into_bytes()),
+            #[cfg(feature = "ext_literal")]
+            sync: true,
+        })
     }
 }
 
@@ -434,6 +461,12 @@ impl<'a> AsRef<[u8]> for Literal<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum LiteralError {
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 /// A quoted string.
 ///
 /// "The quoted string form is an alternative that avoids the overhead of processing a literal at the cost of limitations of characters which may be used.
@@ -445,14 +478,27 @@ impl<'a> AsRef<[u8]> for Literal<'a> {
 pub struct Quoted<'a>(pub(crate) Cow<'a, str>);
 
 impl<'a> Quoted<'a> {
-    pub fn verify(value: &[u8]) -> bool {
-        value.iter().all(|b| is_text_char(*b))
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), QuotedError> {
+        let value = value.as_ref();
+
+        if let Some(position) = value.iter().position(|b| !is_text_char(*b)) {
+            return Err(QuotedError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     pub fn inner(&self) -> &str {
         self.0.as_ref()
     }
 
+    pub fn into_inner(self) -> Cow<'a, str> {
+        self.0
+    }
+
     /// Create a quoted from a string without checking
     /// that it to conforms to IMAP's quoted specification.
     ///
@@ -463,56 +509,60 @@ impl<'a> Quoted<'a> {
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Cow<'a, str>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(inner.as_bytes()));
+        Self::verify(inner.as_bytes()).unwrap();
 
         Self(inner)
     }
 }
 
 impl<'a> TryFrom<&'a [u8]> for Quoted<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        let str = from_utf8(value).map_err(|_| ())?;
+        Quoted::verify(value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Quoted(Cow::Borrowed(from_utf8(value).unwrap())))
     }
 }
 
 impl TryFrom<Vec<u8>> for Quoted<'_> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        let str = String::from_utf8(value).map_err(|_| ())?;
+        Quoted::verify(&value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Quoted(Cow::Owned(String::from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<&'a str> for Quoted<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        if Quoted::verify(value.as_bytes()) {
-            Ok(Quoted(Cow::Borrowed(value)))
-        } else {
-            Err(())
-        }
+        Quoted::verify(value)?;
+
+        Ok(Quoted(Cow::Borrowed(value)))
     }
 }
 
 impl<'a> TryFrom<String> for Quoted<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        if Quoted::verify(value.as_bytes()) {
-            Ok(Quoted(Cow::Owned(value)))
-        } else {
-            Err(())
-        }
+        Quoted::verify(&value)?;
+
+        Ok(Quoted(Cow::Owned(value)))
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum QuotedError {
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 impl<'a> AsRef<str> for Quoted<'a> {
     fn as_ref(&self) -> &str {
         &self.0
@@ -544,68 +594,58 @@ pub enum AString<'a> {
 }
 
 impl<'a> TryFrom<&'a [u8]> for AString<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: &'a [u8]) -> Result<Self, ()> {
+    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
         if let Ok(atom) = AtomExt::try_from(value) {
             return Ok(AString::Atom(atom));
         }
 
-        if let Ok(istr) = IString::try_from(value) {
-            return Ok(AString::String(istr));
-        }
-
-        Err(())
+        Ok(AString::String(IString::try_from(value)?))
     }
 }
 
 impl TryFrom<Vec<u8>> for AString<'_> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: Vec<u8>) -> Result<Self, ()> {
+    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
         // TODO(efficiency)
         if let Ok(atom) = AtomExt::try_from(value.clone()) {
             return Ok(AString::Atom(atom));
         }
 
-        if let Ok(istr) = IString::try_from(value) {
-            return Ok(AString::String(istr));
-        }
-
-        Err(())
+        Ok(AString::String(IString::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<&'a str> for AString<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: &'a str) -> Result<Self, ()> {
+    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
         if let Ok(atom) = AtomExt::try_from(value) {
             return Ok(AString::Atom(atom));
         }
 
-        if let Ok(string) = IString::try_from(value) {
-            return Ok(AString::String(string));
-        }
-
-        Err(())
+        Ok(AString::String(IString::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<String> for AString<'a> {
-    type Error = ();
+    type Error = LiteralError;
 
-    fn try_from(value: String) -> Result<Self, ()> {
+    fn try_from(value: String) -> Result<Self, Self::Error> {
         // TODO(efficiency)
         if let Ok(atom) = AtomExt::try_from(value.clone()) {
             return Ok(AString::Atom(atom));
         }
 
-        if let Ok(string) = IString::try_from(value) {
-            return Ok(AString::String(string));
-        }
+        Ok(AString::String(IString::try_from(value)?))
+    }
+}
 
-        Err(())
+impl<'a> From<Quoted<'a>> for AString<'a> {
+    fn from(value: Quoted<'a>) -> Self {
+        AString::String(IString::Quoted(value))
     }
 }
 
@@ -669,8 +709,24 @@ impl<'a> AsRef<[u8]> for AString<'a> {
 pub struct Tag<'a>(pub(crate) Cow<'a, str>);
 
 impl<'a> Tag<'a> {
-    pub fn verify(value: &[u8]) -> bool {
-        !value.is_empty() && value.iter().all(|c| is_astring_char(*c) && *c != b'+')
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), TagError> {
+        let value = value.as_ref();
+
+        if value.is_empty() {
+            return Err(TagError::Empty);
+        }
+
+        if let Some(position) = value
+            .iter()
+            .position(|b| !is_astring_char(*b) || *b == b'+')
+        {
+            return Err(TagError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     pub fn inner(&self) -> &str {
@@ -680,53 +736,51 @@ impl<'a> Tag<'a> {
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Cow<'a, str>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(inner.as_bytes()));
+        Self::verify(inner.as_bytes()).unwrap();
 
         Self(inner)
     }
 }
 
 impl<'a> TryFrom<&'a [u8]> for Tag<'a> {
-    type Error = ();
+    type Error = TagError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        let str = from_utf8(value).map_err(|_| ())?;
+        Self::verify(value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't fail due to `verify`.
+        Ok(Self(Cow::Borrowed(from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<Vec<u8>> for Tag<'a> {
-    type Error = ();
+    type Error = TagError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        let str = String::from_utf8(value).map_err(|_| ())?;
+        Self::verify(&value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't fail due to `verify`.
+        Ok(Self(Cow::Owned(String::from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<&'a str> for Tag<'a> {
-    type Error = ();
+    type Error = TagError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Borrowed(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(Self(Cow::Borrowed(value)))
     }
 }
 
 impl<'a> TryFrom<String> for Tag<'a> {
-    type Error = ();
+    type Error = TagError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Owned(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(&value)?;
+
+        Ok(Self(Cow::Owned(value)))
     }
 }
 
@@ -736,84 +790,120 @@ impl<'a> AsRef<str> for Tag<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum TagError {
+    #[error("Must not be empty.")]
+    Empty,
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 #[cfg_attr(feature = "bounded-static", derive(ToStatic))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Eq, Hash, Clone)]
 pub struct Text<'a>(pub(crate) Cow<'a, str>);
 
 impl<'a> Text<'a> {
-    pub fn verify(value: &[u8]) -> bool {
-        !value.is_empty() && value.iter().all(|b| is_text_char(*b))
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), TextError> {
+        let value = value.as_ref();
+
+        if value.is_empty() {
+            return Err(TextError::Empty);
+        }
+
+        if let Some(position) = value.iter().position(|b| !is_text_char(*b)) {
+            return Err(TextError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     pub fn inner(&self) -> &str {
         self.0.as_ref()
     }
 
+    pub fn into_inner(self) -> Cow<'a, str> {
+        self.0
+    }
+
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Cow<'a, str>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(inner.as_bytes()));
+        Self::verify(inner.as_bytes()).unwrap();
 
         Self(inner)
     }
 }
 
 impl<'a> TryFrom<&'a [u8]> for Text<'a> {
-    type Error = ();
+    type Error = TextError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        let str = from_utf8(value).map_err(|_| ())?;
+        Self::verify(value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Self(Cow::Borrowed(from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<Vec<u8>> for Text<'a> {
-    type Error = ();
+    type Error = TextError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        let str = String::from_utf8(value).map_err(|_| ())?;
+        Self::verify(&value)?;
 
-        Self::try_from(str)
+        // Safety: `unwrap` can't panic due to `verify`.
+        Ok(Self(Cow::Owned(String::from_utf8(value).unwrap())))
     }
 }
 
 impl<'a> TryFrom<&'a str> for Text<'a> {
-    type Error = ();
+    type Error = TextError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Borrowed(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(Self(Cow::Borrowed(value)))
     }
 }
 
 impl<'a> TryFrom<String> for Text<'a> {
-    type Error = ();
+    type Error = TextError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        if Self::verify(value.as_bytes()) {
-            Ok(Self(Cow::Owned(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(&value)?;
+
+        Ok(Self(Cow::Owned(value)))
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum TextError {
+    #[error("Must not be empty.")]
+    Empty,
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 #[cfg_attr(feature = "bounded-static", derive(ToStatic))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Copy, Debug, PartialEq, Eq, Hash, Clone)]
 pub struct QuotedChar(char);
 
 impl QuotedChar {
-    pub fn verify(input: char) -> bool {
-        input.is_ascii()
+    pub fn verify(input: char) -> Result<(), QuotedCharError> {
+        if input.is_ascii()
             && (is_any_text_char_except_quoted_specials(input as u8)
                 || input == '\\'
                 || input == '"')
+        {
+            Ok(())
+        } else {
+            Err(QuotedCharError::Invalid(input))
+        }
     }
 
     pub fn inner(&self) -> char {
@@ -823,24 +913,28 @@ impl QuotedChar {
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: char) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(inner));
+        Self::verify(inner).unwrap();
 
         Self(inner)
     }
 }
 
 impl TryFrom<char> for QuotedChar {
-    type Error = ();
+    type Error = QuotedCharError;
 
     fn try_from(value: char) -> Result<Self, Self::Error> {
-        if Self::verify(value) {
-            Ok(QuotedChar(value))
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(QuotedChar(value))
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum QuotedCharError {
+    #[error("Invalid character `{0}`.")]
+    Invalid(char),
+}
+
 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 #[cfg_attr(feature = "bounded-static", derive(ToStatic))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -851,55 +945,52 @@ pub enum Charset<'a> {
 }
 
 impl<'a> TryFrom<&'a [u8]> for Charset<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        let str = from_utf8(value).map_err(|_| ())?;
+        if let Ok(atom) = Atom::try_from(value) {
+            return Ok(Self::Atom(atom));
+        }
 
-        Self::try_from(str)
+        Ok(Self::Quoted(Quoted::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<Vec<u8>> for Charset<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
-        let str = String::from_utf8(value).map_err(|_| ())?;
+        // TODO(efficiency)
+        if let Ok(atom) = Atom::try_from(value.clone()) {
+            return Ok(Self::Atom(atom));
+        }
 
-        Self::try_from(str)
+        Ok(Self::Quoted(Quoted::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<&'a str> for Charset<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
         if let Ok(atom) = Atom::try_from(value) {
-            return Ok(Charset::Atom(atom));
-        }
-
-        if let Ok(quoted) = Quoted::try_from(value) {
-            return Ok(Charset::Quoted(quoted));
+            return Ok(Self::Atom(atom));
         }
 
-        Err(())
+        Ok(Self::Quoted(Quoted::try_from(value)?))
     }
 }
 
 impl<'a> TryFrom<String> for Charset<'a> {
-    type Error = ();
+    type Error = QuotedError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
         // TODO(efficiency)
         if let Ok(atom) = Atom::try_from(value.clone()) {
-            return Ok(Charset::Atom(atom));
+            return Ok(Self::Atom(atom));
         }
 
-        if let Ok(quoted) = Quoted::try_from(value) {
-            return Ok(Charset::Quoted(quoted));
-        }
-
-        Err(())
+        Ok(Self::Quoted(Quoted::try_from(value)?))
     }
 }
 
@@ -919,31 +1010,39 @@ impl<'a> AsRef<str> for Charset<'a> {
 pub struct NonEmptyVec<T>(pub(crate) Vec<T>);
 
 impl<T> NonEmptyVec<T> {
-    pub fn verify(value: &[T]) -> bool {
-        !value.is_empty()
+    pub fn verify(value: &[T]) -> Result<(), NonEmptyVecError> {
+        if value.is_empty() {
+            return Err(NonEmptyVecError::Empty);
+        }
+
+        Ok(())
     }
 
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Vec<T>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(&inner));
+        Self::verify(&inner).unwrap();
 
         Self(inner)
     }
 }
 
 impl<T> TryFrom<Vec<T>> for NonEmptyVec<T> {
-    type Error = ();
+    type Error = NonEmptyVecError;
 
     fn try_from(inner: Vec<T>) -> Result<Self, Self::Error> {
-        if Self::verify(&inner) {
-            Ok(Self(inner))
-        } else {
-            Err(())
-        }
+        Self::verify(&inner)?;
+
+        Ok(Self(inner))
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum NonEmptyVecError {
+    #[error("Must not be empty.")]
+    Empty,
+}
+
 impl<T> AsRef<[T]> for NonEmptyVec<T> {
     fn as_ref(&self) -> &[T] {
         &self.0
@@ -960,7 +1059,7 @@ mod tests {
     #[test]
     fn test_atom() {
         #[allow(clippy::type_complexity)]
-        let tests: Vec<(&[u8], (Result<Atom, ()>, Result<Atom, ()>))> = vec![
+        let tests: Vec<(&[u8], (Result<Atom, AtomError>, Result<Atom, AtomError>))> = vec![
             (
                 b"A",
                 (
@@ -975,11 +1074,59 @@ mod tests {
                     Ok(Atom(Cow::Owned("ABC".into()))),
                 ),
             ),
-            (b" A", (Err(()), Err(()))),
-            (b"A ", (Err(()), Err(()))),
-            (b"", (Err(()), Err(()))),
-            (b"A\x00", (Err(()), Err(()))),
-            (b"\x00", (Err(()), Err(()))),
+            (
+                b" A",
+                (
+                    Err(AtomError::ByteNotAllowed {
+                        found: b' ',
+                        position: 0,
+                    }),
+                    Err(AtomError::ByteNotAllowed {
+                        found: b' ',
+                        position: 0,
+                    }),
+                ),
+            ),
+            (
+                b"A ",
+                (
+                    Err(AtomError::ByteNotAllowed {
+                        found: b' ',
+                        position: 1,
+                    }),
+                    Err(AtomError::ByteNotAllowed {
+                        found: b' ',
+                        position: 1,
+                    }),
+                ),
+            ),
+            (b"", (Err(AtomError::Empty), Err(AtomError::Empty))),
+            (
+                b"A\x00",
+                (
+                    Err(AtomError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 1,
+                    }),
+                    Err(AtomError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 1,
+                    }),
+                ),
+            ),
+            (
+                b"A\x00",
+                (
+                    Err(AtomError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 1,
+                    }),
+                    Err(AtomError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 1,
+                    }),
+                ),
+            ),
         ];
 
         for (test, (expected, expected_owned)) in tests.into_iter() {
@@ -1014,7 +1161,10 @@ mod tests {
     #[test]
     fn test_atom_ext() {
         #[allow(clippy::type_complexity)]
-        let tests: Vec<(&[u8], (Result<AtomExt, ()>, Result<AtomExt, ()>))> = vec![
+        let tests: Vec<(
+            &[u8],
+            (Result<AtomExt, AtomExtError>, Result<AtomExt, AtomExtError>),
+        )> = vec![
             (
                 b"A",
                 (
@@ -1036,11 +1186,59 @@ mod tests {
                     Ok(AtomExt(Cow::Owned("!partition/sda4".into()))),
                 ),
             ),
-            (b" A", (Err(()), Err(()))),
-            (b"A ", (Err(()), Err(()))),
-            (b"", (Err(()), Err(()))),
-            (b"A\x00", (Err(()), Err(()))),
-            (b"\x00", (Err(()), Err(()))),
+            (
+                b" A",
+                (
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: b' ',
+                        position: 0,
+                    }),
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: b' ',
+                        position: 0,
+                    }),
+                ),
+            ),
+            (
+                b"A ",
+                (
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: b' ',
+                        position: 1,
+                    }),
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: b' ',
+                        position: 1,
+                    }),
+                ),
+            ),
+            (b"", (Err(AtomExtError::Empty), Err(AtomExtError::Empty))),
+            (
+                b"A\x00",
+                (
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 1,
+                    }),
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 1,
+                    }),
+                ),
+            ),
+            (
+                b"\x00",
+                (
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 0,
+                    }),
+                    Err(AtomExtError::ByteNotAllowed {
+                        found: 0x00,
+                        position: 0,
+                    }),
+                ),
+            ),
         ];
 
         for (test, (expected, expected_owned)) in tests.into_iter() {
@@ -1075,7 +1273,10 @@ mod tests {
     #[test]
     fn test_astring() {
         #[allow(clippy::type_complexity)]
-        let tests: Vec<(&[u8], (Result<AString, ()>, Result<AString, ()>))> = vec![
+        let tests: Vec<(
+            &[u8],
+            (Result<AString, LiteralError>, Result<AString, LiteralError>),
+        )> = vec![
             (
                 b"A",
                 (
@@ -1143,8 +1344,32 @@ mod tests {
                     ))))),
                 ),
             ),
-            (b"A\x00", (Err(()), Err(()))),
-            (b"\x00", (Err(()), Err(()))),
+            (
+                b"A\x00",
+                (
+                    Err(LiteralError::ByteNotAllowed {
+                        found: 0,
+                        position: 1,
+                    }),
+                    Err(LiteralError::ByteNotAllowed {
+                        found: 0,
+                        position: 1,
+                    }),
+                ),
+            ),
+            (
+                b"\x00",
+                (
+                    Err(LiteralError::ByteNotAllowed {
+                        found: 0,
+                        position: 0,
+                    }),
+                    Err(LiteralError::ByteNotAllowed {
+                        found: 0,
+                        position: 0,
+                    }),
+                ),
+            ),
         ];
 
         for (test, (expected, expected_owned)) in tests.into_iter() {
diff --git a/imap-types/src/rfc3501/mailbox.rs b/imap-types/src/rfc3501/mailbox.rs
index cfdc9fa0..afb71fe6 100644
--- a/imap-types/src/rfc3501/mailbox.rs
+++ b/imap-types/src/rfc3501/mailbox.rs
@@ -10,10 +10,11 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 use crate::{
     core::{AString, IString},
-    rfc3501::core::{impl_try_from, impl_try_from_try_from},
+    rfc3501::core::{impl_try_from, LiteralError},
     utils::indicators::is_list_char,
 };
 
@@ -23,43 +24,60 @@ use crate::{
 pub struct ListCharString<'a>(pub(crate) Cow<'a, str>);
 
 impl<'a> ListCharString<'a> {
-    pub fn verify(value: &str) -> bool {
-        !value.is_empty() && value.bytes().all(is_list_char)
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), ListCharStringError> {
+        let value = value.as_ref();
+
+        if value.is_empty() {
+            return Err(ListCharStringError::Empty);
+        }
+
+        if let Some(position) = value.iter().position(|b| !is_list_char(*b)) {
+            return Err(ListCharStringError::ByteNotAllowed {
+                found: value[position],
+                position,
+            });
+        };
+
+        Ok(())
     }
 
     #[cfg(feature = "unchecked")]
     pub fn new_unchecked(inner: Cow<'a, str>) -> Self {
         #[cfg(debug_assertions)]
-        assert!(Self::verify(&inner));
+        Self::verify(inner.as_bytes()).unwrap();
 
         Self(inner)
     }
 }
 
 impl<'a> TryFrom<&'a str> for ListCharString<'a> {
-    type Error = ();
+    type Error = ListCharStringError;
 
     fn try_from(value: &'a str) -> Result<Self, Self::Error> {
-        if Self::verify(value) {
-            Ok(Self(Cow::Borrowed(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(value)?;
+
+        Ok(Self(Cow::Borrowed(value)))
     }
 }
 
 impl<'a> TryFrom<String> for ListCharString<'a> {
-    type Error = ();
+    type Error = ListCharStringError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        if Self::verify(&value) {
-            Ok(Self(Cow::Owned(value)))
-        } else {
-            Err(())
-        }
+        Self::verify(&value)?;
+
+        Ok(Self(Cow::Owned(value)))
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum ListCharStringError {
+    #[error("Must not be empty.")]
+    Empty,
+    #[error("Byte \\x{found:02x} at index {position} is not allowed.")]
+    ByteNotAllowed { found: u8, position: usize },
+}
+
 impl<'a> AsRef<[u8]> for ListCharString<'a> {
     fn as_ref(&self) -> &[u8] {
         self.0.as_bytes()
@@ -76,30 +94,37 @@ pub enum ListMailbox<'a> {
 }
 
 impl<'a> TryFrom<&'a str> for ListMailbox<'a> {
-    type Error = ();
-
-    fn try_from(s: &'a str) -> Result<Self, ()> {
-        Ok(if s.is_empty() {
-            ListMailbox::String(IString::Quoted(s.try_into()?))
-        } else if let Ok(lcs) = ListCharString::try_from(s) {
-            ListMailbox::Token(lcs)
-        } else {
-            ListMailbox::String(s.try_into()?)
-        })
+    type Error = LiteralError;
+
+    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+        if s.is_empty() {
+            // Safety: We know that an empty string can always be converted into a quoted string.
+            return Ok(ListMailbox::String(IString::Quoted(s.try_into().unwrap())));
+        }
+
+        if let Ok(lcs) = ListCharString::try_from(s) {
+            return Ok(ListMailbox::Token(lcs));
+        }
+
+        Ok(ListMailbox::String(s.try_into()?))
     }
 }
 
 impl<'a> TryFrom<String> for ListMailbox<'a> {
-    type Error = ();
-
-    fn try_from(s: String) -> Result<Self, ()> {
-        Ok(if s.is_empty() {
-            ListMailbox::String(IString::Quoted(s.try_into().map_err(|_| ())?))
-        } else if let Ok(lcs) = ListCharString::try_from(s.clone()) {
-            ListMailbox::Token(lcs)
-        } else {
-            ListMailbox::String(s.try_into().map_err(|_| ())?)
-        })
+    type Error = LiteralError;
+
+    fn try_from(s: String) -> Result<Self, Self::Error> {
+        if s.is_empty() {
+            // Safety: We know that an empty string can always be converted into a quoted string.
+            return Ok(ListMailbox::String(IString::Quoted(s.try_into().unwrap())));
+        }
+
+        // TODO(efficiency)
+        if let Ok(lcs) = ListCharString::try_from(s.clone()) {
+            return Ok(ListMailbox::Token(lcs));
+        }
+
+        Ok(ListMailbox::String(s.try_into()?))
     }
 }
 
@@ -151,10 +176,10 @@ pub enum Mailbox<'a> {
     Other(MailboxOther<'a>),
 }
 
-impl_try_from!(AString, 'a, &'a [u8], Mailbox<'a>);
-impl_try_from!(AString, 'a, Vec<u8>, Mailbox<'a>);
-impl_try_from!(AString, 'a, &'a str, Mailbox<'a>);
-impl_try_from!(AString, 'a, String, Mailbox<'a>);
+impl_try_from!(AString<'a>, 'a, &'a [u8], Mailbox<'a>);
+impl_try_from!(AString<'a>, 'a, Vec<u8>, Mailbox<'a>);
+impl_try_from!(AString<'a>, 'a, &'a str, Mailbox<'a>);
+impl_try_from!(AString<'a>, 'a, String, Mailbox<'a>);
 
 impl<'a> From<AString<'a>> for Mailbox<'a> {
     fn from(value: AString<'a>) -> Self {
@@ -173,22 +198,44 @@ impl<'a> From<AString<'a>> for Mailbox<'a> {
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct MailboxOther<'a>(pub(crate) AString<'a>);
 
-impl_try_from_try_from!(AString, 'a, &'a [u8], MailboxOther<'a>);
-impl_try_from_try_from!(AString, 'a, Vec<u8>, MailboxOther<'a>);
-impl_try_from_try_from!(AString, 'a, &'a str, MailboxOther<'a>);
-impl_try_from_try_from!(AString, 'a, String, MailboxOther<'a>);
+impl<'a> MailboxOther<'a> {
+    pub fn verify(value: impl AsRef<[u8]>) -> Result<(), MailboxOtherError> {
+        if value.as_ref().to_ascii_lowercase() == b"inbox" {
+            return Err(MailboxOtherError::Reserved);
+        }
 
-impl<'a> TryFrom<AString<'a>> for MailboxOther<'a> {
-    type Error = ();
+        Ok(())
+    }
+}
+
+macro_rules! impl_try_from {
+    ($from:ty) => {
+        impl<'a> TryFrom<$from> for MailboxOther<'a> {
+            type Error = MailboxOtherError;
+
+            fn try_from(value: $from) -> Result<Self, Self::Error> {
+                let astring = AString::try_from(value)?;
+
+                Self::verify(&astring)?;
 
-    fn try_from(mailbox: AString<'a>) -> Result<Self, Self::Error> {
-        if let Ok(str) = from_utf8(mailbox.as_ref()) {
-            if str.to_ascii_lowercase() == "inbox" {
-                return Err(());
+                Ok(Self(astring))
             }
         }
+    };
+}
+
+impl_try_from!(&'a [u8]);
+impl_try_from!(Vec<u8>);
+impl_try_from!(&'a str);
+impl_try_from!(String);
+
+impl<'a> TryFrom<AString<'a>> for MailboxOther<'a> {
+    type Error = MailboxOtherError;
+
+    fn try_from(value: AString<'a>) -> Result<Self, Self::Error> {
+        Self::verify(&value)?;
 
-        Ok(MailboxOther(mailbox))
+        Ok(Self(value))
     }
 }
 
@@ -198,6 +245,14 @@ impl<'a> AsRef<[u8]> for MailboxOther<'a> {
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum MailboxOtherError {
+    #[error(transparent)]
+    Literal(#[from] LiteralError),
+    #[error("Reserved. Please use one of the typed variants.")]
+    Reserved,
+}
+
 #[cfg(test)]
 mod tests {
     use std::{borrow::Cow, convert::TryFrom};
diff --git a/imap-types/src/rfc3501/mod.rs b/imap-types/src/rfc3501/mod.rs
index 56e7d574..acd826c9 100644
--- a/imap-types/src/rfc3501/mod.rs
+++ b/imap-types/src/rfc3501/mod.rs
@@ -6,8 +6,9 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
-use crate::rfc3501::core::{impl_try_from, impl_try_from_try_from, Atom};
+use crate::rfc3501::core::{impl_try_from, Atom, AtomError};
 
 pub mod address;
 pub mod body;
@@ -39,11 +40,11 @@ pub enum AuthMechanism<'a> {
     Other(AuthMechanismOther<'a>),
 }
 
-impl_try_from!(Atom, 'a, &'a [u8], AuthMechanism<'a>);
-impl_try_from!(Atom, 'a, Vec<u8>, AuthMechanism<'a>);
-impl_try_from!(Atom, 'a, &'a str, AuthMechanism<'a>);
-impl_try_from!(Atom, 'a, String, AuthMechanism<'a>);
-impl_try_from!(Atom, 'a, Cow<'a, str>, AuthMechanism<'a>);
+impl_try_from!(Atom<'a>, 'a, &'a [u8], AuthMechanism<'a>);
+impl_try_from!(Atom<'a>, 'a, Vec<u8>, AuthMechanism<'a>);
+impl_try_from!(Atom<'a>, 'a, &'a str, AuthMechanism<'a>);
+impl_try_from!(Atom<'a>, 'a, String, AuthMechanism<'a>);
+impl_try_from!(Atom<'a>, 'a, Cow<'a, str>, AuthMechanism<'a>);
 
 impl<'a> From<Atom<'a>> for AuthMechanism<'a> {
     fn from(inner: Atom<'a>) -> Self {
@@ -61,24 +62,50 @@ impl<'a> From<Atom<'a>> for AuthMechanism<'a> {
 pub struct AuthMechanismOther<'a>(Atom<'a>);
 
 impl<'a> AuthMechanismOther<'a> {
+    pub fn verify(atom: &Atom<'a>) -> Result<(), AuthMechanismOtherError> {
+        if matches!(
+            atom.as_ref().to_ascii_lowercase().as_ref(),
+            "plain" | "login",
+        ) {
+            return Err(AuthMechanismOtherError::Reserved);
+        }
+
+        Ok(())
+    }
+
     pub fn inner(&self) -> &Atom<'a> {
         &self.0
     }
 }
 
-impl_try_from_try_from!(Atom, 'a, &'a [u8], AuthMechanismOther<'a>);
-impl_try_from_try_from!(Atom, 'a, Vec<u8>, AuthMechanismOther<'a>);
-impl_try_from_try_from!(Atom, 'a, &'a str, AuthMechanismOther<'a>);
-impl_try_from_try_from!(Atom, 'a, String, AuthMechanismOther<'a>);
+macro_rules! impl_try_from {
+    ($from:ty) => {
+        impl<'a> TryFrom<$from> for AuthMechanismOther<'a> {
+            type Error = AuthMechanismOtherError;
 
-impl<'a> TryFrom<Atom<'a>> for AuthMechanismOther<'a> {
-    type Error = ();
+            fn try_from(value: $from) -> Result<Self, Self::Error> {
+                let atom = Atom::try_from(value)?;
+
+                Self::verify(&atom)?;
 
-    fn try_from(atom: Atom<'a>) -> Result<Self, ()> {
-        match atom.as_ref().to_ascii_lowercase().as_ref() {
-            "plain" | "login" => Err(()),
-            _ => Ok(Self(atom)),
+                Ok(Self(atom))
+            }
         }
+    };
+}
+
+impl_try_from!(&'a [u8]);
+impl_try_from!(Vec<u8>);
+impl_try_from!(&'a str);
+impl_try_from!(String);
+
+impl<'a> TryFrom<Atom<'a>> for AuthMechanismOther<'a> {
+    type Error = AuthMechanismOtherError;
+
+    fn try_from(atom: Atom<'a>) -> Result<Self, Self::Error> {
+        Self::verify(&atom)?;
+
+        Ok(Self(atom))
     }
 }
 
@@ -87,3 +114,11 @@ impl<'a> AsRef<str> for AuthMechanismOther<'a> {
         self.0.as_ref()
     }
 }
+
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum AuthMechanismOtherError {
+    #[error(transparent)]
+    Atom(#[from] AtomError),
+    #[error("Reserved. Please use one of the typed variants.")]
+    Reserved,
+}
diff --git a/imap-types/src/rfc3501/response.rs b/imap-types/src/rfc3501/response.rs
index 629b0b02..d9c002ff 100644
--- a/imap-types/src/rfc3501/response.rs
+++ b/imap-types/src/rfc3501/response.rs
@@ -12,6 +12,7 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 #[cfg(feature = "ext_compress")]
 use crate::extensions::rfc4987::CompressionAlgorithm;
@@ -22,7 +23,7 @@ use crate::extensions::rfc7888::LiteralCapability;
 #[cfg(feature = "ext_quota")]
 use crate::{
     core::AString,
-    extensions::rfc9208::{QuotaGet, Resource},
+    extensions::rfc9208::{QuotaError, QuotaGet, QuotaRootError, Resource},
 };
 use crate::{
     core::{Atom, NonEmptyVec},
@@ -31,7 +32,7 @@ use crate::{
         data::{FetchAttributeValue, QuotedChar, StatusAttributeValue},
         Text,
     },
-    rfc3501::core::impl_try_from,
+    rfc3501::core::{impl_try_from, TextError},
 };
 
 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
@@ -45,7 +46,11 @@ pub struct Greeting<'a> {
 }
 
 impl<'a> Greeting<'a> {
-    pub fn new(kind: GreetingKind, code: Option<Code<'a>>, text: &'a str) -> Result<Self, ()> {
+    pub fn new(
+        kind: GreetingKind,
+        code: Option<Code<'a>>,
+        text: &'a str,
+    ) -> Result<Self, TextError> {
         Ok(Greeting {
             kind,
             code,
@@ -53,7 +58,7 @@ impl<'a> Greeting<'a> {
         })
     }
 
-    pub fn ok(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ()> {
+    pub fn ok(code: Option<Code<'a>>, text: &'a str) -> Result<Self, TextError> {
         Ok(Greeting {
             kind: GreetingKind::Ok,
             code,
@@ -61,7 +66,7 @@ impl<'a> Greeting<'a> {
         })
     }
 
-    pub fn preauth(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ()> {
+    pub fn preauth(code: Option<Code<'a>>, text: &'a str) -> Result<Self, TextError> {
         Ok(Greeting {
             kind: GreetingKind::PreAuth,
             code,
@@ -69,7 +74,7 @@ impl<'a> Greeting<'a> {
         })
     }
 
-    pub fn bye(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ()> {
+    pub fn bye(code: Option<Code<'a>>, text: &'a str) -> Result<Self, TextError> {
         Ok(Greeting {
             kind: GreetingKind::Bye,
             code,
@@ -220,46 +225,46 @@ pub enum Status<'a> {
 }
 
 impl<'a> Status<'a> {
-    pub fn ok<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, ()>
+    pub fn ok<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
     where
         T: TryInto<Text<'a>>,
     {
         Ok(Status::Ok {
             tag,
             code,
-            text: text.try_into().map_err(|_| ())?,
+            text: text.try_into()?,
         })
     }
 
-    pub fn no<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, ()>
+    pub fn no<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
     where
         T: TryInto<Text<'a>>,
     {
         Ok(Status::No {
             tag,
             code,
-            text: text.try_into().map_err(|_| ())?,
+            text: text.try_into()?,
         })
     }
 
-    pub fn bad<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, ()>
+    pub fn bad<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
     where
         T: TryInto<Text<'a>>,
     {
         Ok(Status::Bad {
             tag,
             code,
-            text: text.try_into().map_err(|_| ())?,
+            text: text.try_into()?,
         })
     }
 
-    pub fn bye<T>(code: Option<Code<'a>>, text: T) -> Result<Self, ()>
+    pub fn bye<T>(code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
     where
         T: TryInto<Text<'a>>,
     {
         Ok(Status::Bye {
             code,
-            text: text.try_into().map_err(|_| ())?,
+            text: text.try_into()?,
         })
     }
 
@@ -566,38 +571,41 @@ impl<'a> Data<'a> {
         Ok(Self::Expunge(NonZeroU32::try_from(seq_or_uid)?))
     }
 
-    pub fn fetch<I, A>(seq_or_uid: I, attributes: A) -> Result<Self, ()>
+    pub fn fetch<I, A>(seq_or_uid: I, attributes: A) -> Result<Self, FetchError<I::Error, A::Error>>
     where
         I: TryInto<NonZeroU32>,
         A: TryInto<NonEmptyVec<FetchAttributeValue<'a>>>,
     {
         Ok(Self::Fetch {
-            seq_or_uid: seq_or_uid.try_into().map_err(|_| ())?, // TODO: better error
-            attributes: attributes.try_into().map_err(|_| ())?, // TODO: better error
+            seq_or_uid: seq_or_uid.try_into().map_err(FetchError::SeqOrUid)?,
+            attributes: attributes.try_into().map_err(FetchError::Attributes)?,
         })
     }
 
     #[cfg(feature = "ext_quota")]
-    pub fn quota<R, Q>(root: R, quotas: Q) -> Result<Self, ()>
+    pub fn quota<R, Q>(root: R, quotas: Q) -> Result<Self, QuotaError<R::Error, Q::Error>>
     where
         R: TryInto<AString<'a>>,
         Q: TryInto<NonEmptyVec<QuotaGet<'a>>>,
     {
         Ok(Self::Quota {
-            root: root.try_into().map_err(|_| ())?,
-            quotas: quotas.try_into().map_err(|_| ())?,
+            root: root.try_into().map_err(QuotaError::Root)?,
+            quotas: quotas.try_into().map_err(QuotaError::Quotas)?,
         })
     }
 
     #[cfg(feature = "ext_quota")]
-    pub fn quota_root<M, R>(mailbox: M, roots: R) -> Result<Self, ()>
+    pub fn quota_root<M, R>(
+        mailbox: M,
+        roots: R,
+    ) -> Result<Self, QuotaRootError<M::Error, R::Error>>
     where
         M: TryInto<Mailbox<'a>>,
         R: TryInto<Vec<AString<'a>>>,
     {
         Ok(Self::QuotaRoot {
-            mailbox: mailbox.try_into().map_err(|_| ())?,
-            roots: roots.try_into().map_err(|_| ())?,
+            mailbox: mailbox.try_into().map_err(QuotaRootError::Mailbox)?,
+            roots: roots.try_into().map_err(QuotaRootError::Roots)?,
         })
     }
 
@@ -608,6 +616,14 @@ impl<'a> Data<'a> {
     // }
 }
 
+#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
+pub enum FetchError<I, A> {
+    #[error("Invalid sequence or UID: {0:?}")]
+    SeqOrUid(I),
+    #[error("Invalid attributes: {0:?}")]
+    Attributes(A),
+}
+
 /// ## 7.5. Server Responses - Command Continuation Request
 ///
 /// The command continuation request response is indicated by a "+" token
@@ -881,10 +897,10 @@ pub enum Capability<'a> {
     Other(CapabilityOther<'a>),
 }
 
-impl_try_from!(Atom, 'a, &'a [u8], Capability<'a>);
-impl_try_from!(Atom, 'a, Vec<u8>, Capability<'a>);
-impl_try_from!(Atom, 'a, &'a str, Capability<'a>);
-impl_try_from!(Atom, 'a, String, Capability<'a>);
+impl_try_from!(Atom<'a>, 'a, &'a [u8], Capability<'a>);
+impl_try_from!(Atom<'a>, 'a, Vec<u8>, Capability<'a>);
+impl_try_from!(Atom<'a>, 'a, &'a str, Capability<'a>);
+impl_try_from!(Atom<'a>, 'a, String, Capability<'a>);
 
 impl<'a> From<Atom<'a>> for Capability<'a> {
     fn from(atom: Atom<'a>) -> Self {
@@ -948,8 +964,10 @@ impl<'a> From<Atom<'a>> for Capability<'a> {
                         }
                         #[cfg(feature = "ext_compress")]
                         "compress" => {
-                            if let Ok(algorithm) = CompressionAlgorithm::try_from(right) {
-                                return Self::Compress { algorithm };
+                            if let Ok(atom) = Atom::try_from(right) {
+                                if let Ok(algorithm) = CompressionAlgorithm::try_from(atom) {
+                                    return Self::Compress { algorithm };
+                                }
                             }
                         }
                         #[cfg(feature = "ext_quota")]
diff --git a/imap-types/src/rfc3501/sequence.rs b/imap-types/src/rfc3501/sequence.rs
index 2ad4f7dd..f38d83ec 100644
--- a/imap-types/src/rfc3501/sequence.rs
+++ b/imap-types/src/rfc3501/sequence.rs
@@ -1,7 +1,8 @@
 use std::{
     convert::{TryFrom, TryInto},
-    num::{NonZeroU32, TryFromIntError},
+    num::{NonZeroU32, ParseIntError, TryFromIntError},
     ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
+    str::FromStr,
 };
 
 #[cfg(feature = "arbitrary")]
@@ -10,6 +11,7 @@ use arbitrary::Arbitrary;
 use bounded_static::ToStatic;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 use crate::core::NonEmptyVec;
 
@@ -49,7 +51,7 @@ impl<'a> SequenceSet {
 }
 
 impl TryFrom<&str> for SequenceSet {
-    type Error = ();
+    type Error = SequenceSetError;
 
     fn try_from(value: &str) -> Result<Self, Self::Error> {
         let mut results = vec![];
@@ -58,24 +60,34 @@ impl TryFrom<&str> for SequenceSet {
             results.push(Sequence::try_from(seq)?);
         }
 
-        Ok(SequenceSet(NonEmptyVec::try_from(results)?))
+        Ok(SequenceSet(
+            NonEmptyVec::try_from(results).map_err(|_| SequenceSetError::Empty)?,
+        ))
     }
 }
 
 impl TryFrom<String> for SequenceSet {
-    type Error = ();
+    type Error = SequenceSetError;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
         value.as_str().try_into()
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
+pub enum SequenceSetError {
+    #[error("Must not be empty.")]
+    Empty,
+    #[error("Sequence: {0}")]
+    Sequence(#[from] SequenceError),
+}
+
 impl TryFrom<&str> for Sequence {
-    type Error = ();
+    type Error = SequenceError;
 
     fn try_from(value: &str) -> Result<Self, Self::Error> {
         match value.split(':').count() {
-            0 => Err(()),
+            0 => Err(SequenceError::Empty),
             1 => Ok(Sequence::Single(SeqNo::try_from(value)?)),
             2 => {
                 let mut split = value.split(':');
@@ -88,7 +100,7 @@ impl TryFrom<&str> for Sequence {
                     SeqNo::try_from(end)?,
                 ))
             }
-            _ => Err(()),
+            _ => Err(SequenceError::Invalid),
         }
     }
 }
@@ -183,6 +195,16 @@ impl From<Sequence> for SequenceSet {
 //     }
 // }
 
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
+pub enum SequenceError {
+    #[error("Sequence must not be empty.")]
+    Empty,
+    #[error("Invalid sequence.")]
+    Invalid,
+    #[error("SeqNo: {0}")]
+    SeqNo(#[from] SeqNoError),
+}
+
 impl SeqNo {
     pub fn expand(&self, largest: NonZeroU32) -> NonZeroU32 {
         match self {
@@ -193,7 +215,7 @@ impl SeqNo {
 }
 
 impl TryFrom<&str> for SeqNo {
-    type Error = ();
+    type Error = SeqNoError;
 
     fn try_from(value: &str) -> Result<Self, Self::Error> {
         if value == "*" {
@@ -203,14 +225,22 @@ impl TryFrom<&str> for SeqNo {
             // Rust's `parse::<NonZeroU32>` function accepts numbers that start with 0.
             // For example, 00001, is interpreted as 1. But this is not allowed in IMAP.
             if value.starts_with('0') {
-                Err(())
+                Err(SeqNoError::LeadingZero)
             } else {
-                Ok(SeqNo::Value(value.parse::<NonZeroU32>().map_err(|_| ())?))
+                Ok(SeqNo::Value(NonZeroU32::from_str(value)?))
             }
         }
     }
 }
 
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
+pub enum SeqNoError {
+    #[error("Must not start with \"0\".")]
+    LeadingZero,
+    #[error("Parse: {0}")]
+    Parse(#[from] ParseIntError),
+}
+
 // -------------------------------------------------------------------------------------------------
 
 #[derive(Debug)]
@@ -415,12 +445,13 @@ mod tests {
     fn creation_of_sequence_set_from_str_negative() {
         let tests = &[
             "", "* ", " *", " * ", "1 ", " 1", " 1 ", "01", " 01", "01 ", " 01 ", "*1", ":", ":*",
-            "*:", "*: ",
+            "*:", "*: ", "1:2:3",
         ];
 
         for test in tests {
             let got = SequenceSet::try_from(*test);
-            assert_eq!(Err(()), got);
+            print!("\"{}\" | {:?} | ", test, got.clone().unwrap_err());
+            println!("{}", got.unwrap_err());
         }
     }
 
diff --git a/src/rfc3501/command.rs b/src/rfc3501/command.rs
index ae3e76b9..76783a9b 100644
--- a/src/rfc3501/command.rs
+++ b/src/rfc3501/command.rs
@@ -218,8 +218,8 @@ pub fn rename(input: &[u8]) -> IResult<&[u8], CommandBody> {
     Ok((
         remaining,
         CommandBody::Rename {
-            mailbox,
-            new_mailbox,
+            from: mailbox,
+            to: new_mailbox,
         },
     ))
 }


Test patch:
diff --git a/imap-types/tests/api.rs b/imap-types/tests/api.rs
index 2ba4336a..2be405ef 100644
--- a/imap-types/tests/api.rs
+++ b/imap-types/tests/api.rs
@@ -1,5 +1,16 @@
+use std::{
+    convert::TryFrom,
+    fmt::{Debug, Display},
+};
+
+use imap_types::{
+    command::{Command, CommandBody, LoginError},
+    core::{Atom, AtomExt},
+    response::Data,
+};
+
 #[test]
-fn construction_of_atom() {
+fn test_construction_of_atom() {
     // `inner` is a private field
     // let atm = Atom {
     //     inner: Cow::Borrowed(" x "),
@@ -16,3 +27,46 @@ fn construction_of_atom() {
     // #[cfg(feature = "unchecked")]
     // let atm = Atom::new_unchecked(" x ");
 }
+
+#[test]
+fn test_construction_of_command() {
+    trait DisplayDebug: Display + Debug {}
+
+    impl<T> DisplayDebug for T where T: Display + Debug {}
+
+    match CommandBody::login("\x00", "") {
+        Err(LoginError::Username(e)) => println!("Oops, bad username: {}", e),
+        Err(LoginError::Password(e)) => println!("Oops, bad password: {:?}", e),
+        _ => {}
+    }
+
+    let tests: Vec<Box<dyn DisplayDebug>> = vec![
+        Box::new(Command::new(b"".as_ref(), CommandBody::Noop).unwrap_err()),
+        Box::new(Command::new(b"A ".as_ref(), CommandBody::Noop).unwrap_err()),
+        Box::new(Command::new(b"\xff".as_ref(), CommandBody::Noop).unwrap_err()),
+        Box::new("---"),
+        Box::new(Command::new("", CommandBody::Noop).unwrap_err()),
+        Box::new(Command::new("A ", CommandBody::Noop).unwrap_err()),
+        Box::new("---"),
+        Box::new(Command::new(String::from(""), CommandBody::Noop).unwrap_err()),
+        Box::new(Command::new(String::from("A "), CommandBody::Noop).unwrap_err()),
+        Box::new("---"),
+        Box::new(Command::new(Vec::from(b"".as_ref()), CommandBody::Noop).unwrap_err()),
+        Box::new(Command::new(Vec::from(b"\xff".as_ref()), CommandBody::Noop).unwrap_err()),
+        Box::new("---"),
+        Box::new(Atom::try_from("").unwrap_err()),
+        Box::new(Atom::try_from("²").unwrap_err()),
+        Box::new("---"),
+        Box::new(AtomExt::try_from("").unwrap_err()),
+        Box::new(AtomExt::try_from("²").unwrap_err()),
+        Box::new("---"),
+        Box::new(CommandBody::login("\x00", "").unwrap_err()),
+        Box::new(CommandBody::login("", b"\x00".as_ref()).unwrap_err()),
+        Box::new("---"),
+        Box::new(Data::capability(vec![]).unwrap_err()),
+    ];
+
+    for test in tests.into_iter() {
+        println!("{test:?} // {test}");
+    }
+}
diff --git a/tests/trace.rs b/tests/trace.rs
index 1e239b35..094772f6 100644
--- a/tests/trace.rs
+++ b/tests/trace.rs
@@ -1,12 +1,14 @@
 use std::convert::TryFrom;
 
+#[cfg(feature = "ext_literal")]
+use imap_codec::core::Literal;
 use imap_codec::{
     codec::{Decode, Encode},
     command::{
         fetch::{FetchAttribute, Macro},
         Command, CommandBody,
     },
-    core::{AString, IString, Literal, Quoted},
+    core::{AString, IString, Quoted},
     message::{AuthMechanism, Flag, Section, Tag},
     response::{
         data::{Capability, FetchAttributeValue},