1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! The IMAP ENABLE Extension
//!
//! This extension extends ...
//!
//! * the [Capability](crate::response::Capability) enum with a new variant [Capability::Enable](crate::response::Capability#variant.Enable),
//! * the [CommandBody](crate::command::CommandBody) enum with a new variant [CommandBody::Enable](crate::command::CommandBody#variant.Enable), and
//! * the [Data](crate::response::Data) enum with a new variant [Data::Enabled](crate::response::Data#variant.Enabled).

#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[cfg(feature = "bounded-static")]
use bounded_static::ToStatic;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{
    command::CommandBody,
    core::{Atom, NonEmptyVec},
    response::Data,
};

impl<'a> CommandBody<'a> {
    pub fn enable<C>(capabilities: C) -> Result<Self, C::Error>
    where
        C: TryInto<NonEmptyVec<CapabilityEnable<'a>>>,
    {
        Ok(CommandBody::Enable {
            capabilities: capabilities.try_into()?,
        })
    }
}

impl<'a> Data<'a> {
    // TODO
    // pub fn enable() -> Self {
    //     unimplemented!()
    // }
}

#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CapabilityEnable<'a> {
    Utf8(Utf8Kind),
    #[cfg(feature = "ext_condstore_qresync")]
    #[cfg_attr(docsrs, doc(cfg(feature = "ext_condstore_qresync")))]
    CondStore,
    Other(CapabilityEnableOther<'a>),
}

impl<'a> From<Atom<'a>> for CapabilityEnable<'a> {
    fn from(atom: Atom<'a>) -> Self {
        match atom.as_ref().to_ascii_lowercase().as_ref() {
            "utf8=accept" => Self::Utf8(Utf8Kind::Accept),
            "utf8=only" => Self::Utf8(Utf8Kind::Only),
            #[cfg(feature = "ext_condstore_qresync")]
            "condstore" => Self::CondStore,
            _ => Self::Other(CapabilityEnableOther(atom)),
        }
    }
}

#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CapabilityEnableOther<'a>(Atom<'a>);

impl<'a> CapabilityEnableOther<'a> {
    pub fn inner(&self) -> &Atom<'a> {
        &self.0
    }
}

impl<'a> TryFrom<Atom<'a>> for CapabilityEnableOther<'a> {
    type Error = CapabilityEnableOtherError;

    fn try_from(value: Atom<'a>) -> Result<Self, Self::Error> {
        match value.as_ref().to_ascii_lowercase().as_ref() {
            "utf8=accept" | "utf8=only" => Err(CapabilityEnableOtherError::Reserved),
            #[cfg(feature = "ext_condstore_qresync")]
            "condstore" => Err(CapabilityEnableOtherError::Reserved),
            _ => Ok(Self(value)),
        }
    }
}

#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
pub enum CapabilityEnableOtherError {
    #[error("Reserved: Please use one of the typed variants")]
    Reserved,
}

#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Utf8Kind {
    Accept,
    Only,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_conversion_capability_enable_other() {
        assert_eq!(
            CapabilityEnable::from(Atom::try_from("utf8=only").unwrap()),
            CapabilityEnable::Utf8(Utf8Kind::Only)
        );
        assert_eq!(
            CapabilityEnable::from(Atom::try_from("utf8=accept").unwrap()),
            CapabilityEnable::Utf8(Utf8Kind::Accept)
        );
        assert_eq!(
            CapabilityEnableOther::try_from(Atom::try_from("utf8=only").unwrap()),
            Err(CapabilityEnableOtherError::Reserved)
        );
        assert_eq!(
            CapabilityEnableOther::try_from(Atom::try_from("utf8=accept").unwrap()),
            Err(CapabilityEnableOtherError::Reserved)
        );
    }
}