imap_types/
flag.rs

1//! Flag-related types.
2
3use std::fmt::{Display, Formatter};
4
5#[cfg(feature = "arbitrary")]
6use arbitrary::Arbitrary;
7#[cfg(feature = "bounded-static")]
8use bounded_static::ToStatic;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use crate::{core::Atom, error::ValidationError};
13
14/// There are two types of flags in IMAP4rev1: System and keyword flags.
15///
16/// A system flag is a flag name that is pre-defined in RFC3501.
17/// All system flags begin with "\\" and certain system flags (`\Deleted` and `\Seen`) have special semantics.
18/// Flags that begin with "\\" but are not pre-defined system flags, are extension flags.
19/// Clients MUST accept them and servers MUST NOT send them except when defined by future standard or standards-track revisions.
20///
21/// A keyword is defined by the server implementation.
22/// Keywords do not begin with "\\" and servers may permit the client to define new ones
23/// in the mailbox by sending the `\*` flag ([`FlagPerm::Asterisk`]) in the PERMANENTFLAGS response..
24///
25/// Note that a flag of either type can be permanent or session-only.
26#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum Flag<'a> {
30    /// Message has been answered (`\Answered`).
31    Answered,
32    /// Message is "deleted" for removal by later EXPUNGE (`\Deleted`).
33    Deleted,
34    /// Message has not completed composition (marked as a draft) (`\Draft`).
35    Draft,
36    /// Message is "flagged" for urgent/special attention (`\Flagged`).
37    Flagged,
38    /// Message has been read (`\Seen`).
39    Seen,
40    /// A future expansion of a system flag.
41    Extension(FlagExtension<'a>),
42    /// A keyword.
43    Keyword(Atom<'a>),
44}
45
46/// An (extension) flag.
47///
48/// It's guaranteed that this type can't represent any flag from [`Flag`].
49#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub struct FlagExtension<'a>(Atom<'a>);
53
54impl<'a> Flag<'a> {
55    pub fn system(atom: Atom<'a>) -> Self {
56        match atom.as_ref().to_ascii_lowercase().as_ref() {
57            "answered" => Self::Answered,
58            "deleted" => Self::Deleted,
59            "draft" => Self::Draft,
60            "flagged" => Self::Flagged,
61            "seen" => Self::Seen,
62            _ => Self::Extension(FlagExtension(atom)),
63        }
64    }
65
66    pub fn keyword(atom: Atom<'a>) -> Self {
67        Self::Keyword(atom)
68    }
69}
70
71impl<'a> TryFrom<&'a str> for Flag<'a> {
72    type Error = ValidationError;
73
74    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
75        Ok(if let Some(value) = value.strip_prefix('\\') {
76            Self::system(Atom::try_from(value)?)
77        } else {
78            Self::keyword(Atom::try_from(value)?)
79        })
80    }
81}
82
83impl<'a> Display for Flag<'a> {
84    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
85        match self {
86            Flag::Answered => f.write_str("\\Answered"),
87            Flag::Deleted => f.write_str("\\Deleted"),
88            Flag::Draft => f.write_str("\\Draft"),
89            Flag::Flagged => f.write_str("\\Flagged"),
90            Flag::Seen => f.write_str("\\Seen"),
91            Flag::Extension(other) => write!(f, "\\{}", other.0),
92            Flag::Keyword(atom) => write!(f, "{}", atom),
93        }
94    }
95}
96
97#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
98#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
99#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub enum FlagFetch<'a> {
102    Flag(Flag<'a>),
103
104    /// Message is "recently" arrived in this mailbox. (`\Recent`)
105    ///
106    /// This session is the first session to have been notified about this message; if the session
107    /// is read-write, subsequent sessions will not see \Recent set for this message.
108    ///
109    /// Note: This flag can not be altered by the client.
110    Recent,
111}
112
113#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
114#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
115#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
116#[derive(Debug, Clone, PartialEq, Eq, Hash)]
117pub enum FlagPerm<'a> {
118    Flag(Flag<'a>),
119
120    /// Indicates that it is possible to create new keywords by
121    /// attempting to store those flags in the mailbox (`\*`).
122    Asterisk,
123}
124
125/// Four name attributes are defined.
126#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
127#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129pub enum FlagNameAttribute<'a> {
130    /// It is not possible for any child levels of hierarchy to exist
131    /// under this name; no child levels exist now and none can be
132    /// created in the future. (`\Noinferiors`)
133    Noinferiors,
134
135    /// It is not possible to use this name as a selectable mailbox. (`\Noselect`)
136    Noselect,
137
138    /// The mailbox has been marked "interesting" by the server; the
139    /// mailbox probably contains messages that have been added since
140    /// the last time the mailbox was selected. (`\Marked`)
141    Marked,
142
143    /// The mailbox does not contain any additional messages since the
144    /// last time the mailbox was selected. (`\Unmarked`)
145    Unmarked,
146
147    /// An extension flags.
148    Extension(FlagNameAttributeExtension<'a>),
149}
150
151/// An extension flag.
152#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
153#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
155pub struct FlagNameAttributeExtension<'a>(Atom<'a>);
156
157impl<'a> FlagNameAttribute<'a> {
158    pub fn is_selectability(&self) -> bool {
159        matches!(
160            self,
161            FlagNameAttribute::Noselect | FlagNameAttribute::Marked | FlagNameAttribute::Unmarked
162        )
163    }
164}
165
166impl<'a> From<Atom<'a>> for FlagNameAttribute<'a> {
167    fn from(atom: Atom<'a>) -> Self {
168        match atom.as_ref().to_ascii_lowercase().as_ref() {
169            "noinferiors" => Self::Noinferiors,
170            "noselect" => Self::Noselect,
171            "marked" => Self::Marked,
172            "unmarked" => Self::Unmarked,
173            _ => Self::Extension(FlagNameAttributeExtension(atom)),
174        }
175    }
176}
177
178impl<'a> Display for FlagNameAttribute<'a> {
179    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
180        match self {
181            Self::Noinferiors => f.write_str("\\Noinferiors"),
182            Self::Noselect => f.write_str("\\Noselect"),
183            Self::Marked => f.write_str("\\Marked"),
184            Self::Unmarked => f.write_str("\\Unmarked"),
185            Self::Extension(extension) => write!(f, "\\{}", extension.0),
186        }
187    }
188}
189
190#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
191#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
192#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
194pub enum StoreType {
195    Replace,
196    Add,
197    Remove,
198}
199
200#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
201#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
202#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
204pub enum StoreResponse {
205    Answer,
206    Silent,
207}