imap_types/extensions/
quota.rs

1//! The IMAP QUOTA Extension
2//!
3//! This extends ...
4//!
5//! * [`Capability`](crate::response::Capability) with new variants:
6//!
7//!     - [`Capability::Quota`](crate::response::Capability::Quota)
8//!     - [`Capability::QuotaRes`](crate::response::Capability::QuotaRes)
9//!     - [`Capability::QuotaSet`](crate::response::Capability::QuotaSet)
10//!
11//! * [`CommandBod`y](crate::command::CommandBody) with new variants:
12//!
13//!     - [`Command::GetQuota`](crate::command::CommandBody::GetQuota)
14//!     - [`Command::GetQuotaRoot`](crate::command::CommandBody::GetQuotaRoot)
15//!     - [`Command::SetQuota`](crate::command::CommandBody::SetQuota)
16//!
17//! * [`Data`](crate::response::Data) with new variants:
18//!
19//!     - [`Data::Quota`](crate::response::Data::Quota)
20//!     - [`Data::QuotaRoot`](crate::response::Data::QuotaRoot)
21//!
22//! * [`Code`](crate::response::Code) with a new variant:
23//!
24//!     - [`Code::OverQuota`](crate::response::Code::OverQuota)
25//!
26//! * [`StatusDataItemName`](crate::status::StatusDataItemName) with new variants:
27//!
28//!     - [`StatusDataItemName::Deleted`](crate::status::StatusDataItemName::Deleted)
29//!     - [`StatusDataItemName::DeletedStorage`](crate::status::StatusDataItemName::DeletedStorage)
30//!
31//! * [`StatusDataItem`](crate::status::StatusDataItem) with new variants:
32//!
33//!     - [`StatusDataItem::Deleted`](crate::status::StatusDataItem::Deleted)
34//!     - [`StatusDataItem::DeletedStorage`](crate::status::StatusDataItem::DeletedStorage)
35
36use std::{
37    borrow::Cow,
38    fmt::{Display, Formatter},
39};
40
41#[cfg(feature = "arbitrary")]
42use arbitrary::Arbitrary;
43#[cfg(feature = "bounded-static")]
44use bounded_static::ToStatic;
45#[cfg(feature = "serde")]
46use serde::{Deserialize, Serialize};
47
48use crate::{
49    command::CommandBody,
50    core::{impl_try_from, AString, Atom, NonEmptyVec},
51    extensions::quota::error::{QuotaError, QuotaRootError, SetQuotaError},
52    mailbox::Mailbox,
53    response::Data,
54};
55
56impl<'a> CommandBody<'a> {
57    pub fn get_quota<A>(root: A) -> Result<Self, A::Error>
58    where
59        A: TryInto<AString<'a>>,
60    {
61        Ok(CommandBody::GetQuota {
62            root: root.try_into()?,
63        })
64    }
65
66    pub fn get_quota_root<M>(mailbox: M) -> Result<Self, M::Error>
67    where
68        M: TryInto<Mailbox<'a>>,
69    {
70        Ok(CommandBody::GetQuotaRoot {
71            mailbox: mailbox.try_into()?,
72        })
73    }
74
75    pub fn set_quota<R, S>(root: R, quotas: S) -> Result<Self, SetQuotaError<R::Error, S::Error>>
76    where
77        R: TryInto<AString<'a>>,
78        S: TryInto<Vec<QuotaSet<'a>>>,
79    {
80        Ok(CommandBody::SetQuota {
81            root: root.try_into().map_err(SetQuotaError::Root)?,
82            quotas: quotas.try_into().map_err(SetQuotaError::QuotaSet)?,
83        })
84    }
85}
86
87impl<'a> Data<'a> {
88    pub fn quota<R, Q>(root: R, quotas: Q) -> Result<Self, QuotaError<R::Error, Q::Error>>
89    where
90        R: TryInto<AString<'a>>,
91        Q: TryInto<NonEmptyVec<QuotaGet<'a>>>,
92    {
93        Ok(Self::Quota {
94            root: root.try_into().map_err(QuotaError::Root)?,
95            quotas: quotas.try_into().map_err(QuotaError::Quotas)?,
96        })
97    }
98
99    pub fn quota_root<M, R>(
100        mailbox: M,
101        roots: R,
102    ) -> Result<Self, QuotaRootError<M::Error, R::Error>>
103    where
104        M: TryInto<Mailbox<'a>>,
105        R: TryInto<Vec<AString<'a>>>,
106    {
107        Ok(Self::QuotaRoot {
108            mailbox: mailbox.try_into().map_err(QuotaRootError::Mailbox)?,
109            roots: roots.try_into().map_err(QuotaRootError::Roots)?,
110        })
111    }
112}
113
114/// A resource type for use in IMAP's QUOTA extension.
115///
116/// Supported resource names MUST be advertised as a capability by prepending the resource name with "QUOTA=RES-".
117#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
118#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
119#[derive(Debug, Clone, PartialEq, Eq, Hash)]
120pub enum Resource<'a> {
121    /// The physical space estimate, in units of 1024 octets, of the mailboxes governed by the quota
122    /// root.
123    ///
124    /// This MAY not be the same as the sum of the RFC822.SIZE of the messages. Some implementations
125    /// MAY include metadata sizes for the messages and mailboxes, and other implementations MAY
126    /// store messages in such a way that the physical space used is smaller, for example, due to
127    /// use of compression. Additional messages might not increase the usage. Clients MUST NOT use
128    /// the usage figure for anything other than informational purposes; for example, they MUST NOT
129    /// refuse to APPEND a message if the limit less the usage is smaller than the RFC822.SIZE
130    /// divided by 1024 octets of the message, but it MAY warn about such condition. The usage
131    /// figure may change as a result of performing actions not associated with adding new messages
132    /// to the mailbox, such as SEARCH, since this may increase the amount of metadata included in
133    /// the calculations.
134    ///
135    /// When the server supports this resource type, it MUST also support the DELETED-STORAGE status
136    /// data item.
137    ///
138    /// Support for this resource MUST be indicated by the server by advertising the
139    /// "QUOTA=RES-STORAGE" capability.
140    Storage,
141
142    /// The number of messages stored within the mailboxes governed by the quota root.
143    ///
144    /// This MUST be an exact number; however, clients MUST NOT assume that a change in the usage
145    /// indicates a change in the number of messages available, since the quota root may include
146    /// mailboxes the client has no access to.
147    ///
148    /// When the server supports this resource type, it MUST also support the DELETED status data
149    /// item.
150    ///
151    /// Support for this resource MUST be indicated by the server by advertising the
152    /// "QUOTA=RES-MESSAGE" capability.
153    Message,
154
155    /// The number of mailboxes governed by the quota root.
156    ///
157    /// This MUST be an exact number; however, clients MUST NOT assume that a change in the usage
158    /// indicates a change in the number of mailboxes, since the quota root may include mailboxes
159    /// the client has no access to.
160    ///
161    /// Support for this resource MUST be indicated by the server by advertising the
162    /// "QUOTA=RES-MAILBOX" capability.
163    Mailbox,
164
165    /// The maximum size of all annotations \[RFC5257\], in units of 1024 octets, associated with all
166    /// messages in the mailboxes governed by the quota root.
167    ///
168    /// Support for this resource MUST be indicated by the server by advertising the
169    /// "QUOTA=RES-ANNOTATION-STORAGE" capability.
170    AnnotationStorage,
171
172    /// An (unknown) resource.
173    Other(ResourceOther<'a>),
174}
175
176/// An (unknown) resource.
177///
178/// It's guaranteed that this type can't represent any resource from [`Resource`].
179#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
180#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
181#[derive(Debug, Clone, PartialEq, Eq, Hash)]
182pub struct ResourceOther<'a>(Atom<'a>);
183
184impl_try_from!(Atom<'a>, 'a, &'a [u8], Resource<'a>);
185impl_try_from!(Atom<'a>, 'a, Vec<u8>, Resource<'a>);
186impl_try_from!(Atom<'a>, 'a, &'a str, Resource<'a>);
187impl_try_from!(Atom<'a>, 'a, String, Resource<'a>);
188impl_try_from!(Atom<'a>, 'a, Cow<'a, str>, Resource<'a>);
189
190impl<'a> From<Atom<'a>> for Resource<'a> {
191    fn from(atom: Atom<'a>) -> Self {
192        match atom.inner().to_ascii_uppercase().as_ref() {
193            "STORAGE" => Resource::Storage,
194            "MESSAGE" => Resource::Message,
195            "MAILBOX" => Resource::Mailbox,
196            "ANNOTATION-STORAGE" => Resource::AnnotationStorage,
197            _ => Resource::Other(ResourceOther(atom)),
198        }
199    }
200}
201
202impl<'a> Display for Resource<'a> {
203    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204        f.write_str(match self {
205            Self::Storage => "STORAGE",
206            Self::Message => "MESSAGE",
207            Self::Mailbox => "MAILBOX",
208            Self::AnnotationStorage => "ANNOTATION-STORAGE",
209            Self::Other(other) => other.0.as_ref(),
210        })
211    }
212}
213
214/// A type that holds a resource name, usage, and limit.
215/// Used in the response of the GETQUOTA command.
216#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
217#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
218#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
220pub struct QuotaGet<'a> {
221    pub resource: Resource<'a>,
222    pub usage: u64,
223    pub limit: u64,
224}
225
226impl<'a> QuotaGet<'a> {
227    pub fn new(resource: Resource<'a>, usage: u64, limit: u64) -> Self {
228        Self {
229            resource,
230            usage,
231            limit,
232        }
233    }
234}
235
236/// A type that holds a resource name and limit.
237/// Used in the SETQUOTA command.
238#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
239#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
240#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241#[derive(Debug, Clone, PartialEq, Eq, Hash)]
242pub struct QuotaSet<'a> {
243    pub resource: Resource<'a>,
244    pub limit: u64,
245}
246
247impl<'a> QuotaSet<'a> {
248    pub fn new(resource: Resource<'a>, limit: u64) -> Self {
249        Self { resource, limit }
250    }
251}
252
253/// Error-related types.
254pub mod error {
255    use thiserror::Error;
256
257    #[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
258    pub enum QuotaError<R, Q> {
259        #[error("Invalid root: {0}")]
260        Root(R),
261        #[error("Invalid quotas: {0}")]
262        Quotas(Q),
263    }
264
265    #[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
266    pub enum QuotaRootError<M, R> {
267        #[error("Invalid mailbox: {0}")]
268        Mailbox(M),
269        #[error("Invalid roots: {0}")]
270        Roots(R),
271    }
272
273    #[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
274    pub enum SetQuotaError<R, S> {
275        #[error("Invalid root: {0}")]
276        Root(R),
277        #[error("Invalid quota set: {0}")]
278        QuotaSet(S),
279    }
280}