1pub mod decode;
2pub mod encode;
3
4#[derive(Debug, Default)]
6#[non_exhaustive]
11pub struct GreetingCodec;
12
13#[derive(Debug, Default)]
15#[non_exhaustive]
16pub struct CommandCodec;
17
18#[derive(Debug, Default)]
20#[non_exhaustive]
21pub struct AuthenticateDataCodec;
22
23#[derive(Debug, Default)]
25#[non_exhaustive]
26pub struct ResponseCodec;
27
28#[derive(Debug, Default)]
30#[non_exhaustive]
31pub struct IdleDoneCodec;
32
33macro_rules! impl_codec_new {
34 ($codec:ty) => {
35 impl $codec {
36 pub fn new() -> Self {
38 Self::default()
39 }
40 }
41 };
42}
43
44impl_codec_new!(GreetingCodec);
45impl_codec_new!(CommandCodec);
46impl_codec_new!(AuthenticateDataCodec);
47impl_codec_new!(ResponseCodec);
48impl_codec_new!(IdleDoneCodec);
49
50#[cfg(test)]
51mod tests {
52 use std::num::NonZeroU32;
53
54 use imap_types::{
55 auth::AuthenticateData,
56 command::{Command, CommandBody},
57 core::{IString, Literal, LiteralMode, NString, NonEmptyVec, Tag},
58 fetch::MessageDataItem,
59 mailbox::Mailbox,
60 response::{Data, Greeting, GreetingKind, Response},
61 secret::Secret,
62 };
63
64 use super::*;
65 use crate::{
66 decode::{CommandDecodeError, Decoder, GreetingDecodeError, ResponseDecodeError},
67 testing::{
68 kat_inverse_authenticate_data, kat_inverse_command, kat_inverse_greeting,
69 kat_inverse_response,
70 },
71 };
72
73 #[test]
74 fn test_kat_inverse_greeting() {
75 kat_inverse_greeting(&[
76 (
77 b"* OK ...\r\n".as_ref(),
78 b"".as_ref(),
79 Greeting::new(GreetingKind::Ok, None, "...").unwrap(),
80 ),
81 (
82 b"* ByE .\r\n???",
83 b"???",
84 Greeting::new(GreetingKind::Bye, None, ".").unwrap(),
85 ),
86 (
87 b"* preaUth x\r\n?",
88 b"?",
89 Greeting::new(GreetingKind::PreAuth, None, "x").unwrap(),
90 ),
91 ]);
92 }
93
94 #[test]
95 fn test_kat_inverse_command() {
96 kat_inverse_command(&[
97 (
98 b"a nOOP\r\n".as_ref(),
99 b"".as_ref(),
100 Command::new("a", CommandBody::Noop).unwrap(),
101 ),
102 (
103 b"a NooP\r\n???",
104 b"???",
105 Command::new("a", CommandBody::Noop).unwrap(),
106 ),
107 (
108 b"a SeLECT {5}\r\ninbox\r\n",
109 b"",
110 Command::new(
111 "a",
112 CommandBody::Select {
113 mailbox: Mailbox::Inbox,
114 },
115 )
116 .unwrap(),
117 ),
118 (
119 b"a SElECT {5}\r\ninbox\r\nxxx",
120 b"xxx",
121 Command::new(
122 "a",
123 CommandBody::Select {
124 mailbox: Mailbox::Inbox,
125 },
126 )
127 .unwrap(),
128 ),
129 ]);
130 }
131
132 #[test]
133 fn test_kat_inverse_response() {
134 kat_inverse_response(&[
135 (
136 b"* SEARCH 1\r\n".as_ref(),
137 b"".as_ref(),
138 Response::Data(Data::Search(vec![NonZeroU32::new(1).unwrap()])),
139 ),
140 (
141 b"* SEARCH 1\r\n???",
142 b"???",
143 Response::Data(Data::Search(vec![NonZeroU32::new(1).unwrap()])),
144 ),
145 (
146 b"* 1 FETCH (RFC822 {5}\r\nhello)\r\n",
147 b"",
148 Response::Data(Data::Fetch {
149 seq: NonZeroU32::new(1).unwrap(),
150 items: NonEmptyVec::from(MessageDataItem::Rfc822(NString(Some(
151 IString::Literal(Literal::try_from(b"hello".as_ref()).unwrap()),
152 )))),
153 }),
154 ),
155 ]);
156 }
157
158 #[test]
159 fn test_kat_inverse_authenticate_data() {
160 kat_inverse_authenticate_data(&[(
161 b"VGVzdA==\r\n".as_ref(),
162 b"".as_ref(),
163 AuthenticateData(Secret::new(b"Test".to_vec())),
164 )]);
165 }
166
167 #[test]
168 fn test_greeting_incomplete_failed() {
169 let tests = [
170 (b"*".as_ref(), Err(GreetingDecodeError::Incomplete)),
172 (b"* ".as_ref(), Err(GreetingDecodeError::Incomplete)),
173 (b"* O".as_ref(), Err(GreetingDecodeError::Incomplete)),
174 (b"* OK".as_ref(), Err(GreetingDecodeError::Incomplete)),
175 (b"* OK ".as_ref(), Err(GreetingDecodeError::Incomplete)),
176 (b"* OK .".as_ref(), Err(GreetingDecodeError::Incomplete)),
177 (b"* OK .\r".as_ref(), Err(GreetingDecodeError::Incomplete)),
178 (b"**".as_ref(), Err(GreetingDecodeError::Failed)),
180 (b"* NO x\r\n".as_ref(), Err(GreetingDecodeError::Failed)),
181 ];
182
183 for (test, expected) in tests {
184 let got = GreetingCodec::default().decode(test);
185 dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
186 assert_eq!(expected, got);
187
188 #[cfg(feature = "bounded-static")]
189 {
190 let got = GreetingCodec::default().decode_static(test);
191 assert_eq!(expected, got);
192 }
193 }
194 }
195
196 #[test]
197 fn test_command_incomplete_failed() {
198 let tests = [
199 (b"a".as_ref(), Err(CommandDecodeError::Incomplete)),
201 (b"a ".as_ref(), Err(CommandDecodeError::Incomplete)),
202 (b"a n".as_ref(), Err(CommandDecodeError::Incomplete)),
203 (b"a no".as_ref(), Err(CommandDecodeError::Incomplete)),
204 (b"a noo".as_ref(), Err(CommandDecodeError::Incomplete)),
205 (b"a noop".as_ref(), Err(CommandDecodeError::Incomplete)),
206 (b"a noop\r".as_ref(), Err(CommandDecodeError::Incomplete)),
207 (
209 b"a select {5}\r\n".as_ref(),
210 Err(CommandDecodeError::LiteralFound {
211 tag: Tag::try_from("a").unwrap(),
212 length: 5,
213 mode: LiteralMode::Sync,
214 }),
215 ),
216 (
217 b"a select {5+}\r\n".as_ref(),
218 Err(CommandDecodeError::LiteralFound {
219 tag: Tag::try_from("a").unwrap(),
220 length: 5,
221 mode: LiteralMode::NonSync,
222 }),
223 ),
224 (
226 b"a select {5}\r\nxxx".as_ref(),
227 Err(CommandDecodeError::Incomplete),
228 ),
229 (b"* noop\r\n".as_ref(), Err(CommandDecodeError::Failed)),
231 (b"A noop\r\n".as_ref(), Err(CommandDecodeError::Failed)),
232 ];
233
234 for (test, expected) in tests {
235 let got = CommandCodec::default().decode(test);
236 dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
237 assert_eq!(expected, got);
238
239 #[cfg(feature = "bounded-static")]
240 {
241 let got = CommandCodec::default().decode_static(test);
242 assert_eq!(expected, got);
243 }
244 }
245 }
246
247 #[test]
248 fn test_response_incomplete_failed() {
249 let tests = [
250 (b"".as_ref(), Err(ResponseDecodeError::Incomplete)),
252 (b"*".as_ref(), Err(ResponseDecodeError::Incomplete)),
253 (b"* ".as_ref(), Err(ResponseDecodeError::Incomplete)),
254 (b"* S".as_ref(), Err(ResponseDecodeError::Incomplete)),
255 (b"* SE".as_ref(), Err(ResponseDecodeError::Incomplete)),
256 (b"* SEA".as_ref(), Err(ResponseDecodeError::Incomplete)),
257 (b"* SEAR".as_ref(), Err(ResponseDecodeError::Incomplete)),
258 (b"* SEARC".as_ref(), Err(ResponseDecodeError::Incomplete)),
259 (b"* SEARCH".as_ref(), Err(ResponseDecodeError::Incomplete)),
260 (b"* SEARCH ".as_ref(), Err(ResponseDecodeError::Incomplete)),
261 (b"* SEARCH 1".as_ref(), Err(ResponseDecodeError::Incomplete)),
262 (
263 b"* SEARCH 1\r".as_ref(),
264 Err(ResponseDecodeError::Incomplete),
265 ),
266 (
268 b"* 1 FETCH (RFC822 {5}\r\n".as_ref(),
269 Err(ResponseDecodeError::LiteralFound { length: 5 }),
270 ),
271 (
273 b"* search 1 2 3\r\n".as_ref(),
274 Err(ResponseDecodeError::Failed),
275 ),
276 (b"A search\r\n".as_ref(), Err(ResponseDecodeError::Failed)),
277 ];
278
279 for (test, expected) in tests {
280 let got = ResponseCodec::default().decode(test);
281 dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
282 assert_eq!(expected, got);
283
284 #[cfg(feature = "bounded-static")]
285 {
286 let got = ResponseCodec::default().decode_static(test);
287 assert_eq!(expected, got);
288 }
289 }
290 }
291}