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}