use crate::error::{Error, ParseError};
use crate::parse::{parse_many_into2, parse_until_done_optional, MapOrNot, MapOrNot2};
use crate::types::UnsolicitedResponse;
use imap_proto::Response;
use ouroboros::self_referencing;
use std::borrow::Cow;
use std::fmt::{Debug, Display, Formatter};
use std::sync::mpsc;
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct QuotaResourceLimit<'a> {
pub name: QuotaResourceName<'a>,
pub amount: u64,
}
impl<'a> QuotaResourceLimit<'a> {
pub fn new(name: impl Into<QuotaResourceName<'a>>, amount: u64) -> Self {
let name = name.into();
Self { name, amount }
}
}
impl Display for QuotaResourceLimit<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.name, self.amount)
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
#[non_exhaustive]
pub enum QuotaResourceName<'a> {
Storage,
Message,
Atom(Cow<'a, str>),
}
impl<'a> From<&'a str> for QuotaResourceName<'a> {
fn from(input: &'a str) -> Self {
match input {
"STORAGE" => QuotaResourceName::Storage,
"MESSAGE" => QuotaResourceName::Message,
_ => QuotaResourceName::Atom(Cow::from(input)),
}
}
}
impl From<String> for QuotaResourceName<'_> {
fn from(input: String) -> Self {
match input.as_str() {
"STORAGE" => QuotaResourceName::Storage,
"MESSAGE" => QuotaResourceName::Message,
_ => QuotaResourceName::Atom(Cow::from(input)),
}
}
}
impl Display for QuotaResourceName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Storage => write!(f, "STORAGE"),
Self::Message => write!(f, "MESSAGE"),
Self::Atom(s) => write!(f, "{}", s),
}
}
}
impl<'a> QuotaResourceName<'a> {
pub fn into_owned(self) -> QuotaResourceName<'static> {
match self {
QuotaResourceName::Storage => QuotaResourceName::Storage,
QuotaResourceName::Message => QuotaResourceName::Message,
QuotaResourceName::Atom(n) => QuotaResourceName::Atom(Cow::Owned(n.into_owned())),
}
}
}
#[self_referencing]
pub struct QuotaResponse {
data: Vec<u8>,
#[borrows(data)]
#[covariant]
pub(crate) quota: Option<Quota<'this>>,
}
impl QuotaResponse {
pub(crate) fn parse(
owned: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> {
QuotaResponseTryBuilder {
data: owned,
quota_builder: |input| {
parse_until_done_optional(input, unsolicited, |response| match response {
Response::Quota(q) => Ok(MapOrNot::Map(Quota::from_imap_proto(q))),
resp => Ok(MapOrNot::Not(resp)),
})
},
}
.try_build()
}
pub fn parsed(&self) -> &Option<Quota<'_>> {
self.borrow_quota()
}
}
#[derive(Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Quota<'a> {
pub root_name: Cow<'a, str>,
pub resources: Vec<QuotaResource<'a>>,
}
impl<'a> Quota<'a> {
fn from_imap_proto(q: imap_proto::Quota<'a>) -> Self {
Self {
root_name: q.root_name,
resources: q
.resources
.into_iter()
.map(|e| QuotaResource {
name: match e.name {
imap_proto::QuotaResourceName::Storage => QuotaResourceName::Storage,
imap_proto::QuotaResourceName::Message => QuotaResourceName::Message,
imap_proto::QuotaResourceName::Atom(e) => QuotaResourceName::Atom(e),
},
usage: e.usage,
limit: e.limit,
})
.collect(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct QuotaResource<'a> {
pub name: QuotaResourceName<'a>,
pub usage: u64,
pub limit: u64,
}
#[self_referencing]
pub struct QuotaRootResponse {
data: Vec<u8>,
#[borrows(data)]
#[covariant]
pub(crate) inner: InnerQuotaRootResponse<'this>,
}
impl Debug for QuotaRootResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.borrow_inner())
}
}
#[derive(Debug)]
pub(crate) struct InnerQuotaRootResponse<'a> {
pub(crate) quota_root: imap_proto::QuotaRoot<'a>,
pub(crate) quotas: Vec<Quota<'a>>,
}
impl QuotaRootResponse {
pub(crate) fn parse(
owned: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> {
QuotaRootResponseTryBuilder {
data: owned,
inner_builder: |input| {
let mut quota_roots = Vec::new();
let mut quotas = Vec::new();
parse_many_into2(
input,
&mut quota_roots,
&mut quotas,
unsolicited,
|response| match response {
Response::QuotaRoot(q) => Ok(MapOrNot2::Map1(q)),
Response::Quota(q) => Ok(MapOrNot2::Map2(Quota::from_imap_proto(q))),
resp => Ok(MapOrNot2::Not(resp)),
},
)?;
match quota_roots.len() {
1 => Ok(InnerQuotaRootResponse {
quota_root: quota_roots.remove(0),
quotas,
}),
_ => Err(Error::Parse(ParseError::Invalid(input.to_vec()))),
}
},
}
.try_build()
}
pub fn mailbox_name(&self) -> &str {
&self.borrow_inner().quota_root.mailbox_name
}
pub fn quota_root_names(&self) -> impl Iterator<Item = &str> {
self.borrow_inner()
.quota_root
.quota_root_names
.iter()
.map(|e| e.as_ref())
}
pub fn quotas(&self) -> &[Quota<'_>] {
&self.borrow_inner().quotas[..]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quota_resource_name_into_owned() {
let name = "TEST";
let borrowed = QuotaResourceName::Atom(Cow::Borrowed(name));
let new_owned = borrowed.into_owned();
assert!(matches!(new_owned, QuotaResourceName::Atom(Cow::Owned(_))));
}
#[test]
fn test_quota_resource_name_from_str() {
let name = "STORAGE";
let name: QuotaResourceName<'_> = name.into();
assert!(matches!(name, QuotaResourceName::Storage));
}
#[test]
fn test_quota_resource_name_from_string() {
let name = "STORAGE".to_string();
let name: QuotaResourceName<'_> = name.into();
assert!(matches!(name, QuotaResourceName::Storage));
}
#[test]
fn test_quota_resource_limit_new() {
let limit = QuotaResourceLimit::new("STORAGE", 1000);
assert_eq!(limit.name, QuotaResourceName::Storage);
assert_eq!(limit.amount, 1000);
}
#[test]
fn test_quota_resource_limit_new_custom() {
let name = "X-NUM-FOLDERS";
let limit = QuotaResourceLimit::new(name, 50);
assert!(matches!(
limit.name,
QuotaResourceName::Atom(x) if x == Cow::from("X-NUM-FOLDERS")
));
assert_eq!(limit.amount, 50);
}
#[test]
fn test_quota_resource_limit_new_from_string() {
let name = "STORAGE".to_string();
fn make_limit(name: String) -> QuotaResourceLimit<'static> {
QuotaResourceLimit::new(name, 1000)
}
let limit = make_limit(name);
assert_eq!(limit.name, QuotaResourceName::Storage);
assert_eq!(limit.amount, 1000);
}
#[test]
fn test_quota_resource_limit_new_custom_from_string() {
let name = "X-NUM-FOLDERS".to_string();
fn make_limit(name: String) -> QuotaResourceLimit<'static> {
QuotaResourceLimit::new(name, 50)
}
let limit = make_limit(name);
assert!(matches!(
limit.name,
QuotaResourceName::Atom(x) if x == Cow::from("X-NUM-FOLDERS")
));
assert_eq!(limit.amount, 50);
}
}