dictproto/
status.rs

1use std::convert::TryFrom;
2use std::error::Error;
3use std::fmt::Display;
4use std::str::FromStr;
5
6#[derive(Debug, Eq, PartialEq)]
7pub enum ParseStatusError {
8    InvalidReplyKind,
9    InvalidCategory,
10    MissingErrNr,
11}
12
13impl Display for ParseStatusError {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        write!(f, "{:?}", self)
16    }
17}
18
19impl Error for ParseStatusError {
20    fn description(&self) -> &str {
21        match self {
22            Self::InvalidReplyKind => "Invalid reply kind",
23            Self::InvalidCategory => "Invalid error category",
24            Self::MissingErrNr => "Missing or invalid error number",
25        }
26    }
27}
28
29#[derive(Debug, Eq, PartialEq, Clone)]
30pub enum ReplyKind {
31    PositivePreliminary,
32    PositiveCompletion,
33    PositiveIntermediate,
34    NegativeTransient,
35    NegativePermanent,
36}
37
38impl TryFrom<char> for ReplyKind {
39    type Error = ParseStatusError;
40
41    fn try_from(value: char) -> Result<Self, Self::Error> {
42        match value {
43            '1' => Ok(ReplyKind::PositivePreliminary),
44            '2' => Ok(ReplyKind::PositiveCompletion),
45            '3' => Ok(ReplyKind::PositiveIntermediate),
46            '4' => Ok(ReplyKind::NegativeTransient),
47            '5' => Ok(ReplyKind::NegativePermanent),
48            _ => Err(ParseStatusError::InvalidReplyKind),
49        }
50    }
51}
52
53impl Display for ReplyKind {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(
56            f,
57            "{}",
58            match self {
59                ReplyKind::PositivePreliminary => '1',
60                ReplyKind::PositiveCompletion => '2',
61                ReplyKind::PositiveIntermediate => '3',
62                ReplyKind::NegativeTransient => '4',
63                ReplyKind::NegativePermanent => '5',
64            }
65        )
66    }
67}
68
69#[derive(Debug, Eq, PartialEq, Clone)]
70pub enum Category {
71    Syntax,
72    Information,
73    Connection,
74    Authentication,
75    Unspecified,
76    System,
77    Nonstandard,
78}
79
80impl TryFrom<char> for Category {
81    type Error = ParseStatusError;
82
83    fn try_from(value: char) -> Result<Self, Self::Error> {
84        match value {
85            '0' => Ok(Category::Syntax),
86            '1' => Ok(Category::Information),
87            '2' => Ok(Category::Connection),
88            '3' => Ok(Category::Authentication),
89            '4' => Ok(Category::Unspecified),
90            '5' => Ok(Category::System),
91            '8' => Ok(Category::Nonstandard),
92            _ => Err(ParseStatusError::InvalidCategory),
93        }
94    }
95}
96
97impl Display for Category {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(
100            f,
101            "{}",
102            match self {
103                Category::Syntax => '0',
104                Category::Information => '1',
105                Category::Connection => '2',
106                Category::Authentication => '3',
107                Category::Unspecified => '4',
108                Category::System => '5',
109                Category::Nonstandard => '8',
110            }
111        )
112    }
113}
114
115#[derive(Debug, Eq, PartialEq, Clone)]
116pub struct Status(pub ReplyKind, pub Category, pub u8);
117
118impl FromStr for Status {
119    type Err = ParseStatusError;
120    fn from_str(s: &str) -> Result<Self, Self::Err> {
121        let chars: Vec<char> = s.chars().collect();
122
123        let reply = chars.get(0).ok_or(ParseStatusError::InvalidReplyKind)?;
124        let category = chars.get(1).ok_or(ParseStatusError::InvalidCategory)?;
125
126        let errnr: u8 = if let Some(ref c) = chars.get(2) {
127            let tmp = c.to_digit(10).ok_or(ParseStatusError::MissingErrNr)?;
128            u8::try_from(tmp).map_err(|_| ParseStatusError::MissingErrNr)?
129        } else {
130            return Err(ParseStatusError::MissingErrNr);
131        };
132
133        Ok(Status(
134            ReplyKind::try_from(*reply)?,
135            Category::try_from(*category)?,
136            errnr,
137        ))
138    }
139}
140
141impl Display for Status {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(f, "{}{}{}", self.0, self.1, self.2)
144    }
145}
146
147impl Status {
148    pub fn is_positive(&self) -> bool {
149        match self.0 {
150            ReplyKind::PositiveCompletion
151            | ReplyKind::PositiveIntermediate
152            | ReplyKind::PositivePreliminary => true,
153            _ => false,
154        }
155    }
156
157    pub fn is_start(&self) -> bool {
158        *self == Status(ReplyKind::PositiveCompletion, Category::Connection, 0)
159    }
160}
161
162#[cfg(test)]
163mod test {
164    use super::*;
165
166    #[test]
167    fn basic_parsing() {
168        let ok = Status::from_str("250").unwrap();
169
170        assert_eq!(
171            ok,
172            Status(ReplyKind::PositiveCompletion, Category::System, 0)
173        );
174    }
175
176    #[test]
177    fn invalid_reply() {
178        if let Err(ParseStatusError::InvalidReplyKind) = Status::from_str("700") {
179            // Do nothing
180        } else {
181            panic!();
182        }
183    }
184
185    #[test]
186    fn invalid_category() {
187        if let Err(ParseStatusError::InvalidCategory) = Status::from_str("170") {
188            // Do nothing
189        } else {
190            panic!();
191        }
192    }
193
194    #[test]
195    fn missing_errno() {
196        if let Err(ParseStatusError::MissingErrNr) = Status::from_str("17") {
197            // Do nothing
198        } else {
199            panic!();
200        }
201    }
202}