#[cfg(feature = "ext_condstore_qresync")]
use std::num::NonZeroU64;
use std::{
borrow::Cow,
fmt::{Debug, Display, Formatter},
num::{NonZeroU32, TryFromIntError},
};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use base64::{Engine, engine::general_purpose::STANDARD as _base64};
use bounded_static_derive::ToStatic;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "ext_id")]
use crate::core::{IString, NString};
#[cfg(feature = "ext_metadata")]
use crate::extensions::metadata::{MetadataCode, MetadataResponse};
#[cfg(feature = "ext_namespace")]
use crate::extensions::namespace::Namespaces;
#[cfg(feature = "ext_utf8")]
use crate::extensions::utf8::Utf8Kind;
#[cfg(feature = "ext_condstore_qresync")]
use crate::sequence::SequenceSet;
use crate::{
auth::AuthMechanism,
core::{AString, Atom, Charset, QuotedChar, Tag, Text, Vec1, impl_try_from},
error::ValidationError,
extensions::{
compress::CompressionAlgorithm,
enable::CapabilityEnable,
quota::{QuotaGet, Resource},
sort::SortAlgorithm,
thread::{Thread, ThreadingAlgorithm},
uidplus::UidSet,
},
fetch::MessageDataItem,
flag::{Flag, FlagNameAttribute, FlagPerm},
mailbox::Mailbox,
response::error::{ContinueError, FetchError},
status::StatusDataItem,
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct Greeting<'a> {
pub kind: GreetingKind,
pub code: Option<Code<'a>>,
pub text: Text<'a>,
}
impl<'a> Greeting<'a> {
pub fn new(
kind: GreetingKind,
code: Option<Code<'a>>,
text: &'a str,
) -> Result<Self, ValidationError> {
Ok(Greeting {
kind,
code,
text: text.try_into()?,
})
}
pub fn ok(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ValidationError> {
Ok(Greeting {
kind: GreetingKind::Ok,
code,
text: text.try_into()?,
})
}
pub fn preauth(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ValidationError> {
Ok(Greeting {
kind: GreetingKind::PreAuth,
code,
text: text.try_into()?,
})
}
pub fn bye(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ValidationError> {
Ok(Greeting {
kind: GreetingKind::Bye,
code,
text: text.try_into()?,
})
}
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ToStatic)]
pub enum GreetingKind {
Ok,
PreAuth,
Bye,
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "content"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub enum Response<'a> {
CommandContinuationRequest(CommandContinuationRequest<'a>),
Data(Data<'a>),
Status(Status<'a>),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "content"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub enum Status<'a> {
Untagged(StatusBody<'a>),
Tagged(Tagged<'a>),
Bye(Bye<'a>),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct StatusBody<'a> {
pub kind: StatusKind,
pub code: Option<Code<'a>>,
pub text: Text<'a>,
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ToStatic)]
pub enum StatusKind {
Ok,
No,
Bad,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct Tagged<'a> {
pub tag: Tag<'a>,
pub body: StatusBody<'a>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct Bye<'a> {
pub code: Option<Code<'a>>,
pub text: Text<'a>,
}
impl<'a> Status<'a> {
pub fn new<T>(
tag: Option<Tag<'a>>,
kind: StatusKind,
code: Option<Code<'a>>,
text: T,
) -> Result<Self, T::Error>
where
T: TryInto<Text<'a>>,
{
let body = StatusBody {
kind,
code,
text: text.try_into()?,
};
match tag {
Some(tag) => Ok(Self::Tagged(Tagged { tag, body })),
None => Ok(Self::Untagged(body)),
}
}
pub fn ok<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
where
T: TryInto<Text<'a>>,
{
Self::new(tag, StatusKind::Ok, code, text)
}
pub fn no<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
where
T: TryInto<Text<'a>>,
{
Self::new(tag, StatusKind::No, code, text)
}
pub fn bad<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
where
T: TryInto<Text<'a>>,
{
Self::new(tag, StatusKind::Bad, code, text)
}
pub fn bye<T>(code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
where
T: TryInto<Text<'a>>,
{
Ok(Self::Bye(Bye {
code,
text: text.try_into()?,
}))
}
pub fn tag(&self) -> Option<&Tag<'_>> {
match self {
Self::Tagged(Tagged { tag, .. }) => Some(tag),
_ => None,
}
}
pub fn code(&self) -> Option<&Code<'_>> {
match self {
Self::Untagged(StatusBody { code, .. })
| Self::Tagged(Tagged {
body: StatusBody { code, .. },
..
})
| Self::Bye(Bye { code, .. }) => code.as_ref(),
}
}
pub fn text(&self) -> &Text<'_> {
match self {
Self::Untagged(StatusBody { text, .. })
| Self::Tagged(Tagged {
body: StatusBody { text, .. },
..
})
| Self::Bye(Bye { text, .. }) => text,
}
}
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "content"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub enum Data<'a> {
Capability(Vec1<Capability<'a>>),
List {
items: Vec<FlagNameAttribute<'a>>,
delimiter: Option<QuotedChar>,
mailbox: Mailbox<'a>,
},
Lsub {
items: Vec<FlagNameAttribute<'a>>,
delimiter: Option<QuotedChar>,
mailbox: Mailbox<'a>,
},
Status {
mailbox: Mailbox<'a>,
items: Cow<'a, [StatusDataItem]>,
},
Search(
Vec<NonZeroU32>,
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
Option<NonZeroU64>,
),
Sort(
Vec<NonZeroU32>,
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
Option<NonZeroU64>,
),
Thread(Vec<Thread>),
Flags(Vec<Flag<'a>>),
Exists(u32),
Recent(u32),
Expunge(NonZeroU32),
Fetch {
seq: NonZeroU32,
items: Vec1<MessageDataItem<'a>>,
},
Enabled {
capabilities: Vec<CapabilityEnable<'a>>,
},
Quota {
root: AString<'a>,
quotas: Vec1<QuotaGet<'a>>,
},
QuotaRoot {
mailbox: Mailbox<'a>,
roots: Vec<AString<'a>>,
},
#[cfg(feature = "ext_id")]
Id {
parameters: Option<Vec<(IString<'a>, NString<'a>)>>,
},
#[cfg(feature = "ext_metadata")]
Metadata {
mailbox: Mailbox<'a>,
items: MetadataResponse<'a>,
},
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
Vanished {
earlier: bool,
known_uids: SequenceSet,
},
#[cfg(feature = "ext_namespace")]
Namespace {
personal: Namespaces<'a>,
other: Namespaces<'a>,
shared: Namespaces<'a>,
},
}
impl<'a> Data<'a> {
pub fn capability<C>(caps: C) -> Result<Self, C::Error>
where
C: TryInto<Vec1<Capability<'a>>>,
{
Ok(Self::Capability(caps.try_into()?))
}
pub fn expunge(seq: u32) -> Result<Self, TryFromIntError> {
Ok(Self::Expunge(NonZeroU32::try_from(seq)?))
}
pub fn fetch<S, I>(seq: S, items: I) -> Result<Self, FetchError<S::Error, I::Error>>
where
S: TryInto<NonZeroU32>,
I: TryInto<Vec1<MessageDataItem<'a>>>,
{
let seq = seq.try_into().map_err(FetchError::SeqOrUid)?;
let items = items.try_into().map_err(FetchError::InvalidItems)?;
Ok(Self::Fetch { seq, items })
}
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "content"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
#[doc(alias = "Continue")]
#[doc(alias = "Continuation")]
#[doc(alias = "ContinuationRequest")]
pub enum CommandContinuationRequest<'a> {
Basic(CommandContinuationRequestBasic<'a>),
Base64(Cow<'a, [u8]>),
}
impl<'a> CommandContinuationRequest<'a> {
pub fn basic<T>(code: Option<Code<'a>>, text: T) -> Result<Self, ContinueError<T::Error>>
where
T: TryInto<Text<'a>>,
{
Ok(Self::Basic(CommandContinuationRequestBasic::new(
code, text,
)?))
}
pub fn base64<'data: 'a, D>(data: D) -> Self
where
D: Into<Cow<'data, [u8]>>,
{
Self::Base64(data.into())
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(try_from = "CommandContinuationRequestBasicShadow")
)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct CommandContinuationRequestBasic<'a> {
code: Option<Code<'a>>,
text: Text<'a>,
}
#[cfg(feature = "serde")]
#[derive(Deserialize, Debug)]
struct CommandContinuationRequestBasicShadow<'a> {
code: Option<Code<'a>>,
text: Text<'a>,
}
#[cfg(feature = "serde")]
impl<'a> TryFrom<CommandContinuationRequestBasicShadow<'a>>
for CommandContinuationRequestBasic<'a>
{
type Error = ContinueError<std::convert::Infallible>;
fn try_from(value: CommandContinuationRequestBasicShadow<'a>) -> Result<Self, Self::Error> {
Self::new(value.code, value.text)
}
}
impl<'a> CommandContinuationRequestBasic<'a> {
pub fn new<T>(code: Option<Code<'a>>, text: T) -> Result<Self, ContinueError<T::Error>>
where
T: TryInto<Text<'a>>,
{
let text = text.try_into().map_err(ContinueError::Text)?;
if code.is_none() && text.as_ref().starts_with('[') {
return Err(ContinueError::Ambiguity);
}
if code.is_none() && _base64.decode(text.inner()).is_ok() {
return Err(ContinueError::Ambiguity);
}
Ok(Self { code, text })
}
pub fn code(&self) -> Option<&Code<'a>> {
self.code.as_ref()
}
pub fn text(&self) -> &Text<'a> {
&self.text
}
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "content"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub enum Code<'a> {
Alert,
BadCharset {
allowed: Vec<Charset<'a>>,
},
Capability(Vec1<Capability<'a>>),
Parse,
PermanentFlags(Vec<FlagPerm<'a>>),
ReadOnly,
ReadWrite,
TryCreate,
UidNext(NonZeroU32),
UidValidity(NonZeroU32),
Unseen(NonZeroU32),
#[cfg(any(feature = "ext_mailbox_referrals", feature = "ext_login_referrals"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "ext_mailbox_referrals", feature = "ext_login_referrals")))
)]
Referral(Cow<'a, str>),
CompressionActive,
OverQuota,
TooBig,
#[cfg(feature = "ext_metadata")]
Metadata(MetadataCode),
UnknownCte,
AppendUid {
uid_validity: NonZeroU32,
uid: NonZeroU32,
},
CopyUid {
uid_validity: NonZeroU32,
source: UidSet,
destination: UidSet,
},
UidNotSticky,
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
HighestModSeq(NonZeroU64),
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
Modified(SequenceSet),
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
NoModSeq,
#[cfg(feature = "ext_condstore_qresync")]
#[cfg_attr(docsrs, doc(cfg("ext_condstore_qresync")))]
Closed,
Other(CodeOther<'a>),
}
impl<'a> Code<'a> {
pub fn badcharset(allowed: Vec<Charset<'a>>) -> Self {
Self::BadCharset { allowed }
}
pub fn capability<C>(caps: C) -> Result<Self, C::Error>
where
C: TryInto<Vec1<Capability<'a>>>,
{
Ok(Self::Capability(caps.try_into()?))
}
pub fn permanentflags(flags: Vec<FlagPerm<'a>>) -> Self {
Self::PermanentFlags(flags)
}
pub fn uidnext(uidnext: u32) -> Result<Self, TryFromIntError> {
Ok(Self::UidNext(NonZeroU32::try_from(uidnext)?))
}
pub fn uidvalidity(uidnext: u32) -> Result<Self, TryFromIntError> {
Ok(Self::UidValidity(NonZeroU32::try_from(uidnext)?))
}
pub fn unseen(uidnext: u32) -> Result<Self, TryFromIntError> {
Ok(Self::Unseen(NonZeroU32::try_from(uidnext)?))
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct CodeOther<'a>(Cow<'a, [u8]>);
impl Debug for CodeOther<'_> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
struct BStr<'a>(&'a Cow<'a, [u8]>);
impl Debug for BStr<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"b\"{}\"",
crate::utils::escape_byte_string(self.0.as_ref())
)
}
}
f.debug_tuple("CodeOther").field(&BStr(&self.0)).finish()
}
}
impl<'a> CodeOther<'a> {
pub fn unvalidated<D>(data: D) -> Self
where
D: Into<Cow<'a, [u8]>>,
{
Self(data.into())
}
pub fn inner(&self) -> &[u8] {
self.0.as_ref()
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "content"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
#[non_exhaustive]
pub enum Capability<'a> {
Imap4Rev1,
Auth(AuthMechanism<'a>),
LoginDisabled,
#[cfg(feature = "starttls")]
#[cfg_attr(docsrs, doc(cfg(feature = "starttls")))]
StartTls,
Idle,
#[cfg(feature = "ext_mailbox_referrals")]
#[cfg_attr(docsrs, doc(cfg(feature = "ext_mailbox_referrals")))]
MailboxReferrals,
#[cfg(feature = "ext_login_referrals")]
#[cfg_attr(docsrs, doc(cfg(feature = "ext_login_referrals")))]
LoginReferrals,
SaslIr,
Enable,
Compress {
algorithm: CompressionAlgorithm,
},
Quota,
QuotaRes(Resource<'a>),
QuotaSet,
LiteralPlus,
LiteralMinus,
Move,
#[cfg(feature = "ext_id")]
Id,
Unselect,
Sort(Option<SortAlgorithm<'a>>),
Thread(ThreadingAlgorithm<'a>),
#[cfg(feature = "ext_metadata")]
Metadata,
#[cfg(feature = "ext_metadata")]
MetadataServer,
Binary,
UidPlus,
#[cfg(feature = "ext_condstore_qresync")]
CondStore,
#[cfg(feature = "ext_condstore_qresync")]
QResync,
#[cfg(feature = "ext_namespace")]
Namespace,
#[cfg(feature = "ext_utf8")]
Utf8(Utf8Kind),
Other(CapabilityOther<'a>),
}
impl Display for Capability<'_> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Self::Imap4Rev1 => write!(f, "IMAP4REV1"),
Self::Auth(mechanism) => write!(f, "AUTH={mechanism}"),
Self::LoginDisabled => write!(f, "LOGINDISABLED"),
#[cfg(feature = "starttls")]
Self::StartTls => write!(f, "STARTTLS"),
#[cfg(feature = "ext_mailbox_referrals")]
Self::MailboxReferrals => write!(f, "MAILBOX-REFERRALS"),
#[cfg(feature = "ext_login_referrals")]
Self::LoginReferrals => write!(f, "LOGIN-REFERRALS"),
Self::SaslIr => write!(f, "SASL-IR"),
Self::Idle => write!(f, "IDLE"),
Self::Enable => write!(f, "ENABLE"),
Self::Compress { algorithm } => write!(f, "COMPRESS={algorithm}"),
Self::Quota => write!(f, "QUOTA"),
Self::QuotaRes(resource) => write!(f, "QUOTA=RES-{resource}"),
Self::QuotaSet => write!(f, "QUOTASET"),
Self::LiteralPlus => write!(f, "LITERAL+"),
Self::LiteralMinus => write!(f, "LITERAL-"),
Self::Move => write!(f, "MOVE"),
#[cfg(feature = "ext_id")]
Self::Id => write!(f, "ID"),
Self::Unselect => write!(f, "UNSELECT"),
Self::Sort(None) => write!(f, "SORT"),
Self::Sort(Some(algorithm)) => write!(f, "SORT={algorithm}"),
Self::Thread(algorithm) => write!(f, "THREAD={algorithm}"),
#[cfg(feature = "ext_metadata")]
Self::Metadata => write!(f, "METADATA"),
#[cfg(feature = "ext_metadata")]
Self::MetadataServer => write!(f, "METADATA-SERVER"),
Self::Binary => write!(f, "BINARY"),
Self::UidPlus => write!(f, "UIDPLUS"),
#[cfg(feature = "ext_condstore_qresync")]
Self::CondStore => write!(f, "CONDSTORE"),
#[cfg(feature = "ext_condstore_qresync")]
Self::QResync => write!(f, "QRESYNC"),
#[cfg(feature = "ext_namespace")]
Self::Namespace => write!(f, "NAMESPACE"),
#[cfg(feature = "ext_utf8")]
Self::Utf8(kind) => write!(f, "UTF8={kind}"),
Self::Other(other) => write!(f, "{}", other.0),
}
}
}
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 {
fn split_once_cow<'a>(
cow: Cow<'a, str>,
pattern: &str,
) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
match cow {
Cow::Borrowed(str) => {
if let Some((left, right)) = str.split_once(pattern) {
return Some((Cow::Borrowed(left), Cow::Borrowed(right)));
}
None
}
Cow::Owned(string) => {
if let Some((left, right)) = string.split_once(pattern) {
return Some((Cow::Owned(left.to_owned()), Cow::Owned(right.to_owned())));
}
None
}
}
}
let cow = atom.into_inner();
match cow.to_ascii_lowercase().as_ref() {
"imap4rev1" => Self::Imap4Rev1,
"logindisabled" => Self::LoginDisabled,
#[cfg(feature = "starttls")]
"starttls" => Self::StartTls,
"idle" => Self::Idle,
#[cfg(feature = "ext_mailbox_referrals")]
"mailbox-referrals" => Self::MailboxReferrals,
#[cfg(feature = "ext_login_referrals")]
"login-referrals" => Self::LoginReferrals,
"sasl-ir" => Self::SaslIr,
"enable" => Self::Enable,
"quota" => Self::Quota,
"quotaset" => Self::QuotaSet,
"literal+" => Self::LiteralPlus,
"literal-" => Self::LiteralMinus,
"move" => Self::Move,
#[cfg(feature = "ext_id")]
"id" => Self::Id,
"sort" => Self::Sort(None),
#[cfg(feature = "ext_metadata")]
"metadata" => Self::Metadata,
#[cfg(feature = "ext_metadata")]
"metadata-server" => Self::MetadataServer,
"binary" => Self::Binary,
"unselect" => Self::Unselect,
#[cfg(feature = "ext_condstore_qresync")]
"condstore" => Self::CondStore,
#[cfg(feature = "ext_condstore_qresync")]
"qresync" => Self::QResync,
"uidplus" => Self::UidPlus,
#[cfg(feature = "ext_utf8")]
"utf8=accept" => Self::Utf8(Utf8Kind::Accept),
#[cfg(feature = "ext_utf8")]
"utf8=only" => Self::Utf8(Utf8Kind::Only),
_ => {
if let Some((left, right)) = split_once_cow(cow.clone(), "=") {
match left.as_ref().to_ascii_lowercase().as_ref() {
"auth" => {
if let Ok(mechanism) = AuthMechanism::try_from(right) {
return Self::Auth(mechanism);
}
}
"compress" => {
if let Ok(atom) = Atom::try_from(right) {
if let Ok(algorithm) = CompressionAlgorithm::try_from(atom) {
return Self::Compress { algorithm };
}
}
}
"quota" => {
if let Some((_, right)) =
right.as_ref().to_ascii_lowercase().split_once("res-")
{
if let Ok(resource) = Resource::try_from(right.to_owned()) {
return Self::QuotaRes(resource);
}
}
}
"sort" => {
if let Ok(atom) = Atom::try_from(right) {
return Self::Sort(Some(SortAlgorithm::from(atom)));
}
}
"thread" => {
if let Ok(atom) = Atom::try_from(right) {
return Self::Thread(ThreadingAlgorithm::from(atom));
}
}
_ => {}
}
}
Self::Other(CapabilityOther(Atom(cow)))
}
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct CapabilityOther<'a>(Atom<'a>);
pub mod error {
use thiserror::Error;
#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
pub enum ContinueError<T> {
#[error("invalid text")]
Text(T),
#[error("ambiguity detected")]
Ambiguity,
}
#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
pub enum FetchError<S, I> {
#[error("Invalid sequence or UID: {0:?}")]
SeqOrUid(S),
#[error("Invalid items: {0:?}")]
InvalidItems(I),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conversion_data() {
let _ = Data::capability(vec![Capability::Imap4Rev1]).unwrap();
let _ = Data::fetch(1, vec![MessageDataItem::Rfc822Size(123)]).unwrap();
}
#[test]
fn test_conversion_continue_failing() {
let tests = [
CommandContinuationRequest::basic(None, ""),
CommandContinuationRequest::basic(Some(Code::ReadWrite), ""),
];
for test in tests {
println!("{test:?}");
assert!(test.is_err());
}
}
#[cfg(feature = "serde")]
#[test]
fn test_deserialization_command_continuation_request_basic() {
let valid_input = r#"{ "text": "Ready for additional command text" }"#;
let invalid_input = r#"{ "text": "[Ready for additional command text" }"#;
let request = serde_json::from_str::<CommandContinuationRequestBasic>(valid_input)
.expect("valid input should deserialize successfully");
assert_eq!(
request,
CommandContinuationRequestBasic {
code: None,
text: Text(Cow::Borrowed("Ready for additional command text"))
}
);
let err = serde_json::from_str::<CommandContinuationRequestBasic>(invalid_input)
.expect_err("invalid input should not deserialize successfully");
assert_eq!(err.to_string(), r"ambiguity detected");
}
}