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 } else {
181 panic!();
182 }
183 }
184
185 #[test]
186 fn invalid_category() {
187 if let Err(ParseStatusError::InvalidCategory) = Status::from_str("170") {
188 } else {
190 panic!();
191 }
192 }
193
194 #[test]
195 fn missing_errno() {
196 if let Err(ParseStatusError::MissingErrNr) = Status::from_str("17") {
197 } else {
199 panic!();
200 }
201 }
202}