#[cfg(test)]
#[path = "classification_tests.rs"]
mod tests;
use crate::connection::NotifyFlags;
use crate::types::response::UntaggedResponse;
use crate::types::validated::MailboxName;
use crate::types::CommandKind;
#[derive(Debug)]
pub(crate) enum SolicitationRule {
OnlySolicited,
OnlyUnsolicited,
Either,
Impossible,
}
pub(crate) struct ClassificationContext<'a> {
pub notify: NotifyFlags,
pub command_target: Option<&'a MailboxName>,
}
fn mailbox_names_eq(a: &str, b: &str) -> bool {
if a.eq_ignore_ascii_case("INBOX") && b.eq_ignore_ascii_case("INBOX") {
return true;
}
a == b
}
pub(crate) fn classify(
cmd: CommandKind,
resp: &UntaggedResponse,
ctx: &ClassificationContext,
) -> SolicitationRule {
use CommandKind as CK;
use SolicitationRule::{Either, Impossible, OnlySolicited, OnlyUnsolicited};
use UntaggedResponse as UR;
#[allow(clippy::match_same_arms, unreachable_patterns)]
match (cmd, resp) {
(CK::Capability, UR::Capability(_)) => OnlySolicited,
(_, UR::Capability(_)) => Either,
(CK::Select | CK::Examine, UR::Exists(_) | UR::Recent(_) | UR::Flags(_)) => OnlySolicited,
(CK::Select | CK::Examine, UR::List(_)) => Either,
(_, UR::Exists(_) | UR::Recent(_)) => Either,
(_, UR::Flags(_)) => Either,
(CK::List | CK::ListStatus, UR::List(_)) => OnlySolicited,
(_, UR::List(_)) if ctx.notify.list => OnlyUnsolicited,
(_, UR::List(_)) => Impossible,
(CK::Lsub, UR::Lsub(_)) => OnlySolicited,
(_, UR::Lsub(_)) => Impossible,
(CK::Status, UR::MailboxStatus { mailbox, .. }) => {
debug_assert!(
ctx.command_target.is_some(),
"STATUS command dispatched without setting command_target"
);
match ctx.command_target {
Some(target) if mailbox_names_eq(mailbox.as_ref(), target.as_ref()) => {
OnlySolicited
}
_ => OnlyUnsolicited,
}
}
(CK::ListStatus, UR::MailboxStatus { .. }) => OnlySolicited,
(_, UR::MailboxStatus { .. }) if ctx.notify.status => OnlyUnsolicited,
(_, UR::MailboxStatus { .. }) => Impossible,
(CK::Close, UR::Expunge(_)) => OnlySolicited,
(CK::Expunge, UR::Expunge(_)) => OnlySolicited,
(_, UR::Expunge(_)) => Either,
(CK::Search | CK::SearchReturn | CK::SearchSave, UR::Search { .. }) => OnlySolicited,
(_, UR::Search { .. }) => Impossible,
(CK::Fetch, UR::Fetch(_)) => Either,
(_, UR::Fetch(_)) => Either,
(_, UR::Status { .. }) => Either,
(CK::Namespace, UR::Namespace { .. }) => OnlySolicited,
(_, UR::Namespace { .. }) => Impossible,
(CK::Id, UR::Id(_)) => OnlySolicited,
(_, UR::Id(_)) => Impossible,
(CK::Enable, UR::Enabled(_)) => OnlySolicited,
(_, UR::Enabled(_)) => Impossible,
(CK::Search | CK::SearchReturn | CK::SearchSave, UR::Esearch(_)) => OnlySolicited,
(_, UR::Esearch(_)) => Impossible,
(CK::Select | CK::Examine, UR::Vanished { earlier: true, .. }) => OnlySolicited,
(CK::Fetch, UR::Vanished { earlier: true, .. }) => OnlySolicited,
(CK::Expunge, UR::Vanished { earlier: true, .. }) => OnlySolicited,
(_, UR::Vanished { earlier: false, .. }) => Either,
(_, UR::Vanished { earlier: true, .. }) => Impossible,
(CK::GetQuota | CK::GetQuotaRoot | CK::SetQuota, UR::Quota { .. }) => OnlySolicited,
(_, UR::Quota { .. }) => Impossible,
(CK::GetQuotaRoot, UR::QuotaRoot { .. }) => OnlySolicited,
(_, UR::QuotaRoot { .. }) => Impossible,
(CK::GetAcl, UR::Acl { .. }) => OnlySolicited,
(_, UR::Acl { .. }) => Impossible,
(CK::ListRights, UR::ListRights { .. }) => OnlySolicited,
(_, UR::ListRights { .. }) => Impossible,
(CK::MyRights, UR::MyRights { .. }) => OnlySolicited,
(_, UR::MyRights { .. }) => Impossible,
(CK::GetMetadata, UR::Metadata { .. }) => OnlySolicited,
(_, UR::Metadata { .. }) if ctx.notify.metadata => OnlyUnsolicited,
(_, UR::Metadata { .. }) => Impossible,
(CK::Thread, UR::Thread(_)) => OnlySolicited,
(_, UR::Thread(_)) => Impossible,
(CK::Sort, UR::Sort { .. }) => OnlySolicited,
(_, UR::Sort { .. }) => Impossible,
(_, UR::Unknown(_)) => OnlyUnsolicited,
_ => {
debug_assert!(
false,
"classify: missing row for cmd={:?}, resp={:?}",
cmd,
std::mem::discriminant(resp)
);
Impossible
}
}
}