• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

duesee / imap-codec / 9940016014

15 Jul 2024 01:09PM UTC coverage: 92.999% (+0.01%) from 92.985%
9940016014

push

github

duesee
feat: Add translated `imap-codec` Python examples

This adds Python examples `parse_command`, `parse_greeting` and
`parse_response`, which are translated from the Rust examples available
for `imap-codec`.

11212 of 12056 relevant lines covered (93.0%)

978.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.49
/imap-codec/src/codec/encode.rs
1
//! # Encoding of messages.
2
//!
3
//! To facilitates handling of literals, [Encoder::encode] returns an instance of [`Encoded`].
4
//! The idea is that the encoder not only "dumps" the final serialization of a message but can be iterated over.
5
//!
6
//! # Example
7
//!
8
//! ```rust
9
//! use imap_codec::{
10
//!     encode::{Encoder, Fragment},
11
//!     imap_types::{
12
//!         command::{Command, CommandBody},
13
//!         core::LiteralMode,
14
//!     },
15
//!     CommandCodec,
16
//! };
17
//!
18
//! let command = Command::new("A1", CommandBody::login("Alice", "Pa²²W0rD").unwrap()).unwrap();
19
//!
20
//! for fragment in CommandCodec::default().encode(&command) {
21
//!     match fragment {
22
//!         Fragment::Line { data } => {
23
//!             // A line that is ready to be send.
24
//!             println!("C: {}", String::from_utf8(data).unwrap());
25
//!         }
26
//!         Fragment::Literal { data, mode } => match mode {
27
//!             LiteralMode::Sync => {
28
//!                 // Wait for a continuation request.
29
//!                 println!("S: + ...")
30
//!             }
31
//!             LiteralMode::NonSync => {
32
//!                 // We don't need to wait for a continuation request
33
//!                 // as the server will also not send it.
34
//!             }
35
//!         },
36
//!     }
37
//! }
38
//! ```
39
//!
40
//! Output of example:
41
//!
42
//! ```imap
43
//! C: A1 LOGIN alice {10}
44
//! S: + ...
45
//! C: Pa²²W0rD
46
//! ```
47

48
use std::{borrow::Borrow, collections::VecDeque, io::Write, num::NonZeroU32};
49

50
use base64::{engine::general_purpose::STANDARD as base64, Engine};
51
use chrono::{DateTime as ChronoDateTime, FixedOffset};
52
#[cfg(any(feature = "ext_binary", feature = "ext_metadata"))]
53
use imap_types::core::NString8;
54
use imap_types::{
55
    auth::{AuthMechanism, AuthenticateData},
56
    body::{
57
        BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
58
        MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
59
    },
60
    command::{Command, CommandBody},
61
    core::{
62
        AString, Atom, AtomExt, Charset, IString, Literal, LiteralMode, NString, Quoted,
63
        QuotedChar, Tag, Text,
64
    },
65
    datetime::{DateTime, NaiveDate},
66
    envelope::{Address, Envelope},
67
    extensions::idle::IdleDone,
68
    fetch::{
69
        Macro, MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName, Part, Section,
70
    },
71
    flag::{Flag, FlagFetch, FlagNameAttribute, FlagPerm, StoreResponse, StoreType},
72
    mailbox::{ListCharString, ListMailbox, Mailbox, MailboxOther},
73
    response::{
74
        Bye, Capability, Code, CodeOther, CommandContinuationRequest, Data, Greeting, GreetingKind,
75
        Response, Status, StatusBody, StatusKind, Tagged,
76
    },
77
    search::SearchKey,
78
    sequence::{SeqOrUid, Sequence, SequenceSet},
79
    status::{StatusDataItem, StatusDataItemName},
80
    utils::escape_quoted,
81
};
82
#[cfg(feature = "serde")]
83
use serde::{Deserialize, Serialize};
84
use utils::{join_serializable, List1AttributeValueOrNil, List1OrNil};
85

86
use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
87

88
/// Encoder.
89
///
90
/// Implemented for types that know how to encode a specific IMAP message. See [implementors](trait.Encoder.html#implementors).
91
pub trait Encoder {
92
    type Message<'a>;
93

94
    /// Encode this message.
95
    ///
96
    /// This will return an [`Encoded`] message.
97
    fn encode(&self, message: &Self::Message<'_>) -> Encoded;
98
}
99

100
/// An encoded message.
101
///
102
/// This struct facilitates the implementation of IMAP client- and server implementations by
103
/// yielding the encoding of a message through [`Fragment`]s. This is required, because the usage of
104
/// literals (and some other types) may change the IMAP message flow. Thus, in many cases, it is an
105
/// error to just "dump" a message and send it over the network.
106
///
107
/// # Example
108
///
109
/// ```rust
110
/// use imap_codec::{
111
///     encode::{Encoder, Fragment},
112
///     imap_types::command::{Command, CommandBody},
113
///     CommandCodec,
114
/// };
115
///
116
/// let cmd = Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap();
117
///
118
/// for fragment in CommandCodec::default().encode(&cmd) {
119
///     match fragment {
120
///         Fragment::Line { data } => {}
121
///         Fragment::Literal { data, mode } => {}
122
///     }
123
/// }
124
/// ```
125
#[derive(Clone, Debug)]
126
pub struct Encoded {
127
    items: VecDeque<Fragment>,
128
}
129

130
impl Encoded {
131
    /// Dump the (remaining) encoded data without being guided by [`Fragment`]s.
132
    pub fn dump(self) -> Vec<u8> {
3,040✔
133
        let mut out = Vec::new();
3,040✔
134

135
        for fragment in self.items {
6,108✔
136
            match fragment {
3,068✔
137
                Fragment::Line { mut data } => out.append(&mut data),
3,054✔
138
                Fragment::Literal { mut data, .. } => out.append(&mut data),
14✔
139
            }
140
        }
141

142
        out
3,040✔
143
    }
3,040✔
144
}
145

146
impl Iterator for Encoded {
147
    type Item = Fragment;
148

149
    fn next(&mut self) -> Option<Self::Item> {
52✔
150
        self.items.pop_front()
52✔
151
    }
52✔
152
}
153

154
/// The intended action of a client or server.
155
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
26✔
156
#[derive(Clone, Debug, Eq, PartialEq)]
157
pub enum Fragment {
158
    /// A line that is ready to be send.
159
    Line { data: Vec<u8> },
160

161
    /// A literal that may require an action before it should be send.
162
    Literal { data: Vec<u8>, mode: LiteralMode },
163
}
164

165
//--------------------------------------------------------------------------------------------------
166

167
#[derive(Clone, Debug, Default, Eq, PartialEq)]
168
pub(crate) struct EncodeContext {
169
    accumulator: Vec<u8>,
170
    items: VecDeque<Fragment>,
171
}
172

173
impl EncodeContext {
174
    pub fn new() -> Self {
3,506✔
175
        Self::default()
3,506✔
176
    }
3,506✔
177

178
    pub fn push_line(&mut self) {
50✔
179
        self.items.push_back(Fragment::Line {
50✔
180
            data: std::mem::take(&mut self.accumulator),
50✔
181
        })
50✔
182
    }
50✔
183

184
    pub fn push_literal(&mut self, mode: LiteralMode) {
50✔
185
        self.items.push_back(Fragment::Literal {
50✔
186
            data: std::mem::take(&mut self.accumulator),
50✔
187
            mode,
50✔
188
        })
50✔
189
    }
50✔
190

191
    pub fn into_items(self) -> VecDeque<Fragment> {
3,506✔
192
        let Self {
3,506✔
193
            accumulator,
3,506✔
194
            mut items,
3,506✔
195
        } = self;
3,506✔
196

3,506✔
197
        if !accumulator.is_empty() {
3,506✔
198
            items.push_back(Fragment::Line { data: accumulator });
3,506✔
199
        }
3,506✔
200

201
        items
3,506✔
202
    }
3,506✔
203

204
    #[cfg(test)]
205
    pub(crate) fn dump(self) -> Vec<u8> {
448✔
206
        let mut out = Vec::new();
448✔
207

208
        for item in self.into_items() {
504✔
209
            match item {
504✔
210
                Fragment::Line { data } | Fragment::Literal { data, .. } => {
476✔
211
                    out.extend_from_slice(&data)
504✔
212
                }
213
            }
214
        }
215

216
        out
448✔
217
    }
448✔
218
}
219

220
impl Write for EncodeContext {
221
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
27,388✔
222
        self.accumulator.extend_from_slice(buf);
27,388✔
223
        Ok(buf.len())
27,388✔
224
    }
27,388✔
225

226
    fn flush(&mut self) -> std::io::Result<()> {
×
227
        Ok(())
×
228
    }
×
229
}
230

231
macro_rules! impl_encoder_for_codec {
232
    ($codec:ty, $message:ty) => {
233
        impl Encoder for $codec {
234
            type Message<'a> = $message;
235

236
            fn encode(&self, message: &Self::Message<'_>) -> Encoded {
3,058✔
237
                let mut encode_context = EncodeContext::new();
3,058✔
238
                EncodeIntoContext::encode_ctx(message.borrow(), &mut encode_context).unwrap();
3,058✔
239

3,058✔
240
                Encoded {
3,058✔
241
                    items: encode_context.into_items(),
3,058✔
242
                }
3,058✔
243
            }
3,058✔
244
        }
245
    };
246
}
247

248
impl_encoder_for_codec!(GreetingCodec, Greeting<'a>);
249
impl_encoder_for_codec!(CommandCodec, Command<'a>);
250
impl_encoder_for_codec!(AuthenticateDataCodec, AuthenticateData<'a>);
251
impl_encoder_for_codec!(ResponseCodec, Response<'a>);
252
impl_encoder_for_codec!(IdleDoneCodec, IdleDone);
253

254
// -------------------------------------------------------------------------------------------------
255

256
pub(crate) trait EncodeIntoContext {
257
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
258
}
259

260
// ----- Primitive ---------------------------------------------------------------------------------
261

262
impl EncodeIntoContext for u32 {
263
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
52✔
264
        ctx.write_all(self.to_string().as_bytes())
52✔
265
    }
52✔
266
}
267

268
impl EncodeIntoContext for u64 {
269
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
4✔
270
        ctx.write_all(self.to_string().as_bytes())
4✔
271
    }
4✔
272
}
273

274
// ----- Command -----------------------------------------------------------------------------------
275

276
impl<'a> EncodeIntoContext for Command<'a> {
277
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
986✔
278
        self.tag.encode_ctx(ctx)?;
986✔
279
        ctx.write_all(b" ")?;
986✔
280
        self.body.encode_ctx(ctx)?;
986✔
281
        ctx.write_all(b"\r\n")
986✔
282
    }
986✔
283
}
284

285
impl<'a> EncodeIntoContext for Tag<'a> {
286
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,888✔
287
        ctx.write_all(self.inner().as_bytes())
1,888✔
288
    }
1,888✔
289
}
290

291
impl<'a> EncodeIntoContext for CommandBody<'a> {
292
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
986✔
293
        match self {
986✔
294
            CommandBody::Capability => ctx.write_all(b"CAPABILITY"),
72✔
295
            CommandBody::Noop => ctx.write_all(b"NOOP"),
30✔
296
            CommandBody::Logout => ctx.write_all(b"LOGOUT"),
24✔
297
            #[cfg(feature = "starttls")]
298
            CommandBody::StartTLS => ctx.write_all(b"STARTTLS"),
24✔
299
            CommandBody::Authenticate {
300
                mechanism,
10✔
301
                initial_response,
10✔
302
            } => {
10✔
303
                ctx.write_all(b"AUTHENTICATE")?;
10✔
304
                ctx.write_all(b" ")?;
10✔
305
                mechanism.encode_ctx(ctx)?;
10✔
306

307
                if let Some(ir) = initial_response {
10✔
308
                    ctx.write_all(b" ")?;
6✔
309

310
                    // RFC 4959 (https://datatracker.ietf.org/doc/html/rfc4959#section-3)
311
                    // "To send a zero-length initial response, the client MUST send a single pad character ("=").
312
                    // This indicates that the response is present, but is a zero-length string."
313
                    if ir.declassify().is_empty() {
6✔
314
                        ctx.write_all(b"=")?;
2✔
315
                    } else {
316
                        ctx.write_all(base64.encode(ir.declassify()).as_bytes())?;
4✔
317
                    };
318
                };
4✔
319

320
                Ok(())
10✔
321
            }
322
            CommandBody::Login { username, password } => {
76✔
323
                ctx.write_all(b"LOGIN")?;
76✔
324
                ctx.write_all(b" ")?;
76✔
325
                username.encode_ctx(ctx)?;
76✔
326
                ctx.write_all(b" ")?;
76✔
327
                password.declassify().encode_ctx(ctx)
76✔
328
            }
329
            CommandBody::Select { mailbox } => {
28✔
330
                ctx.write_all(b"SELECT")?;
28✔
331
                ctx.write_all(b" ")?;
28✔
332
                mailbox.encode_ctx(ctx)
28✔
333
            }
334
            CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
2✔
335
            CommandBody::Examine { mailbox } => {
12✔
336
                ctx.write_all(b"EXAMINE")?;
12✔
337
                ctx.write_all(b" ")?;
12✔
338
                mailbox.encode_ctx(ctx)
12✔
339
            }
340
            CommandBody::Create { mailbox } => {
24✔
341
                ctx.write_all(b"CREATE")?;
24✔
342
                ctx.write_all(b" ")?;
24✔
343
                mailbox.encode_ctx(ctx)
24✔
344
            }
345
            CommandBody::Delete { mailbox } => {
72✔
346
                ctx.write_all(b"DELETE")?;
72✔
347
                ctx.write_all(b" ")?;
72✔
348
                mailbox.encode_ctx(ctx)
72✔
349
            }
350
            CommandBody::Rename {
351
                from: mailbox,
36✔
352
                to: new_mailbox,
36✔
353
            } => {
36✔
354
                ctx.write_all(b"RENAME")?;
36✔
355
                ctx.write_all(b" ")?;
36✔
356
                mailbox.encode_ctx(ctx)?;
36✔
357
                ctx.write_all(b" ")?;
36✔
358
                new_mailbox.encode_ctx(ctx)
36✔
359
            }
360
            CommandBody::Subscribe { mailbox } => {
12✔
361
                ctx.write_all(b"SUBSCRIBE")?;
12✔
362
                ctx.write_all(b" ")?;
12✔
363
                mailbox.encode_ctx(ctx)
12✔
364
            }
365
            CommandBody::Unsubscribe { mailbox } => {
12✔
366
                ctx.write_all(b"UNSUBSCRIBE")?;
12✔
367
                ctx.write_all(b" ")?;
12✔
368
                mailbox.encode_ctx(ctx)
12✔
369
            }
370
            CommandBody::List {
371
                reference,
156✔
372
                mailbox_wildcard,
156✔
373
            } => {
156✔
374
                ctx.write_all(b"LIST")?;
156✔
375
                ctx.write_all(b" ")?;
156✔
376
                reference.encode_ctx(ctx)?;
156✔
377
                ctx.write_all(b" ")?;
156✔
378
                mailbox_wildcard.encode_ctx(ctx)
156✔
379
            }
380
            CommandBody::Lsub {
381
                reference,
24✔
382
                mailbox_wildcard,
24✔
383
            } => {
24✔
384
                ctx.write_all(b"LSUB")?;
24✔
385
                ctx.write_all(b" ")?;
24✔
386
                reference.encode_ctx(ctx)?;
24✔
387
                ctx.write_all(b" ")?;
24✔
388
                mailbox_wildcard.encode_ctx(ctx)
24✔
389
            }
390
            CommandBody::Status {
391
                mailbox,
14✔
392
                item_names,
14✔
393
            } => {
14✔
394
                ctx.write_all(b"STATUS")?;
14✔
395
                ctx.write_all(b" ")?;
14✔
396
                mailbox.encode_ctx(ctx)?;
14✔
397
                ctx.write_all(b" ")?;
14✔
398
                ctx.write_all(b"(")?;
14✔
399
                join_serializable(item_names, b" ", ctx)?;
14✔
400
                ctx.write_all(b")")
14✔
401
            }
402
            CommandBody::Append {
403
                mailbox,
×
404
                flags,
×
405
                date,
×
406
                message,
×
407
            } => {
×
408
                ctx.write_all(b"APPEND")?;
×
409
                ctx.write_all(b" ")?;
×
410
                mailbox.encode_ctx(ctx)?;
×
411

412
                if !flags.is_empty() {
×
413
                    ctx.write_all(b" ")?;
×
414
                    ctx.write_all(b"(")?;
×
415
                    join_serializable(flags, b" ", ctx)?;
×
416
                    ctx.write_all(b")")?;
×
417
                }
×
418

419
                if let Some(date) = date {
×
420
                    ctx.write_all(b" ")?;
×
421
                    date.encode_ctx(ctx)?;
×
422
                }
×
423

424
                ctx.write_all(b" ")?;
×
425
                message.encode_ctx(ctx)
×
426
            }
427
            CommandBody::Check => ctx.write_all(b"CHECK"),
12✔
428
            CommandBody::Close => ctx.write_all(b"CLOSE"),
12✔
429
            CommandBody::Expunge => ctx.write_all(b"EXPUNGE"),
24✔
430
            #[cfg(feature = "ext_uidplus")]
431
            CommandBody::ExpungeUid { sequence_set } => {
6✔
432
                ctx.write_all(b"UID EXPUNGE ")?;
6✔
433
                sequence_set.encode_ctx(ctx)
6✔
434
            }
435
            CommandBody::Search {
436
                charset,
24✔
437
                criteria,
24✔
438
                uid,
24✔
439
            } => {
24✔
440
                if *uid {
24✔
441
                    ctx.write_all(b"UID SEARCH")?;
×
442
                } else {
443
                    ctx.write_all(b"SEARCH")?;
24✔
444
                }
445
                if let Some(charset) = charset {
24✔
446
                    ctx.write_all(b" CHARSET ")?;
×
447
                    charset.encode_ctx(ctx)?;
×
448
                }
24✔
449
                ctx.write_all(b" ")?;
24✔
450
                join_serializable(criteria.as_ref(), b" ", ctx)
24✔
451
            }
452
            #[cfg(feature = "ext_sort_thread")]
453
            CommandBody::Sort {
454
                sort_criteria,
36✔
455
                charset,
36✔
456
                search_criteria,
36✔
457
                uid,
36✔
458
            } => {
36✔
459
                if *uid {
36✔
460
                    ctx.write_all(b"UID SORT (")?;
×
461
                } else {
462
                    ctx.write_all(b"SORT (")?;
36✔
463
                }
464
                join_serializable(sort_criteria.as_ref(), b" ", ctx)?;
36✔
465
                ctx.write_all(b") ")?;
36✔
466
                charset.encode_ctx(ctx)?;
36✔
467
                ctx.write_all(b" ")?;
36✔
468
                join_serializable(search_criteria.as_ref(), b" ", ctx)
36✔
469
            }
470
            #[cfg(feature = "ext_sort_thread")]
471
            CommandBody::Thread {
472
                algorithm,
36✔
473
                charset,
36✔
474
                search_criteria,
36✔
475
                uid,
36✔
476
            } => {
36✔
477
                if *uid {
36✔
478
                    ctx.write_all(b"UID THREAD ")?;
×
479
                } else {
480
                    ctx.write_all(b"THREAD ")?;
36✔
481
                }
482
                algorithm.encode_ctx(ctx)?;
36✔
483
                ctx.write_all(b" ")?;
36✔
484
                charset.encode_ctx(ctx)?;
36✔
485
                ctx.write_all(b" ")?;
36✔
486
                join_serializable(search_criteria.as_ref(), b" ", ctx)
36✔
487
            }
488
            CommandBody::Fetch {
489
                sequence_set,
48✔
490
                macro_or_item_names,
48✔
491
                uid,
48✔
492
            } => {
48✔
493
                if *uid {
48✔
494
                    ctx.write_all(b"UID FETCH ")?;
12✔
495
                } else {
496
                    ctx.write_all(b"FETCH ")?;
36✔
497
                }
498

499
                sequence_set.encode_ctx(ctx)?;
48✔
500
                ctx.write_all(b" ")?;
48✔
501
                macro_or_item_names.encode_ctx(ctx)
48✔
502
            }
503
            CommandBody::Store {
504
                sequence_set,
24✔
505
                kind,
24✔
506
                response,
24✔
507
                flags,
24✔
508
                uid,
24✔
509
            } => {
24✔
510
                if *uid {
24✔
511
                    ctx.write_all(b"UID STORE ")?;
×
512
                } else {
513
                    ctx.write_all(b"STORE ")?;
24✔
514
                }
515

516
                sequence_set.encode_ctx(ctx)?;
24✔
517
                ctx.write_all(b" ")?;
24✔
518

519
                match kind {
24✔
520
                    StoreType::Add => ctx.write_all(b"+")?,
24✔
521
                    StoreType::Remove => ctx.write_all(b"-")?,
×
522
                    StoreType::Replace => {}
×
523
                }
524

525
                ctx.write_all(b"FLAGS")?;
24✔
526

527
                match response {
24✔
528
                    StoreResponse::Answer => {}
24✔
529
                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
×
530
                }
531

532
                ctx.write_all(b" (")?;
24✔
533
                join_serializable(flags, b" ", ctx)?;
24✔
534
                ctx.write_all(b")")
24✔
535
            }
536
            CommandBody::Copy {
537
                sequence_set,
36✔
538
                mailbox,
36✔
539
                uid,
36✔
540
            } => {
36✔
541
                if *uid {
36✔
542
                    ctx.write_all(b"UID COPY ")?;
×
543
                } else {
544
                    ctx.write_all(b"COPY ")?;
36✔
545
                }
546
                sequence_set.encode_ctx(ctx)?;
36✔
547
                ctx.write_all(b" ")?;
36✔
548
                mailbox.encode_ctx(ctx)
36✔
549
            }
550
            CommandBody::Idle => ctx.write_all(b"IDLE"),
4✔
551
            CommandBody::Enable { capabilities } => {
30✔
552
                ctx.write_all(b"ENABLE ")?;
30✔
553
                join_serializable(capabilities.as_ref(), b" ", ctx)
30✔
554
            }
555
            CommandBody::Compress { algorithm } => {
4✔
556
                ctx.write_all(b"COMPRESS ")?;
4✔
557
                algorithm.encode_ctx(ctx)
4✔
558
            }
559
            CommandBody::GetQuota { root } => {
10✔
560
                ctx.write_all(b"GETQUOTA ")?;
10✔
561
                root.encode_ctx(ctx)
10✔
562
            }
563
            CommandBody::GetQuotaRoot { mailbox } => {
6✔
564
                ctx.write_all(b"GETQUOTAROOT ")?;
6✔
565
                mailbox.encode_ctx(ctx)
6✔
566
            }
567
            CommandBody::SetQuota { root, quotas } => {
12✔
568
                ctx.write_all(b"SETQUOTA ")?;
12✔
569
                root.encode_ctx(ctx)?;
12✔
570
                ctx.write_all(b" (")?;
12✔
571
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
572
                ctx.write_all(b")")
12✔
573
            }
574
            CommandBody::Move {
575
                sequence_set,
6✔
576
                mailbox,
6✔
577
                uid,
6✔
578
            } => {
6✔
579
                if *uid {
6✔
580
                    ctx.write_all(b"UID MOVE ")?;
2✔
581
                } else {
582
                    ctx.write_all(b"MOVE ")?;
4✔
583
                }
584
                sequence_set.encode_ctx(ctx)?;
6✔
585
                ctx.write_all(b" ")?;
6✔
586
                mailbox.encode_ctx(ctx)
6✔
587
            }
588
            #[cfg(feature = "ext_id")]
589
            CommandBody::Id { parameters } => {
8✔
590
                ctx.write_all(b"ID ")?;
8✔
591

592
                match parameters {
8✔
593
                    Some(parameters) => {
4✔
594
                        if let Some((first, tail)) = parameters.split_first() {
4✔
595
                            ctx.write_all(b"(")?;
4✔
596

597
                            first.0.encode_ctx(ctx)?;
4✔
598
                            ctx.write_all(b" ")?;
4✔
599
                            first.1.encode_ctx(ctx)?;
4✔
600

601
                            for parameter in tail {
4✔
602
                                ctx.write_all(b" ")?;
×
603
                                parameter.0.encode_ctx(ctx)?;
×
604
                                ctx.write_all(b" ")?;
×
605
                                parameter.1.encode_ctx(ctx)?;
×
606
                            }
607

608
                            ctx.write_all(b")")
4✔
609
                        } else {
610
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
611
                            {
612
                                ctx.write_all(b"()")
613
                            }
614
                            #[cfg(feature = "quirk_id_empty_to_nil")]
615
                            {
616
                                ctx.write_all(b"NIL")
×
617
                            }
618
                        }
619
                    }
620
                    None => ctx.write_all(b"NIL"),
4✔
621
                }
622
            }
623
            #[cfg(feature = "ext_metadata")]
624
            CommandBody::SetMetadata {
625
                mailbox,
6✔
626
                entry_values,
6✔
627
            } => {
6✔
628
                ctx.write_all(b"SETMETADATA ")?;
6✔
629
                mailbox.encode_ctx(ctx)?;
6✔
630
                ctx.write_all(b" (")?;
6✔
631
                join_serializable(entry_values.as_ref(), b" ", ctx)?;
6✔
632
                ctx.write_all(b")")
6✔
633
            }
634
            #[cfg(feature = "ext_metadata")]
635
            CommandBody::GetMetadata {
636
                options,
14✔
637
                mailbox,
14✔
638
                entries,
14✔
639
            } => {
14✔
640
                ctx.write_all(b"GETMETADATA")?;
14✔
641

642
                if !options.is_empty() {
14✔
643
                    ctx.write_all(b" (")?;
10✔
644
                    join_serializable(options, b" ", ctx)?;
10✔
645
                    ctx.write_all(b")")?;
10✔
646
                }
4✔
647

648
                ctx.write_all(b" ")?;
14✔
649
                mailbox.encode_ctx(ctx)?;
14✔
650

651
                ctx.write_all(b" ")?;
14✔
652

653
                if entries.as_ref().len() == 1 {
14✔
654
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
655
                } else {
656
                    ctx.write_all(b"(")?;
×
657
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
658
                    ctx.write_all(b")")
×
659
                }
660
            }
661
        }
662
    }
986✔
663
}
664

665
impl<'a> EncodeIntoContext for AuthMechanism<'a> {
666
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
22✔
667
        write!(ctx, "{}", self)
22✔
668
    }
22✔
669
}
670

671
impl EncodeIntoContext for AuthenticateData<'_> {
672
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
673
        match self {
8✔
674
            Self::Continue(data) => {
6✔
675
                let encoded = base64.encode(data.declassify());
6✔
676
                ctx.write_all(encoded.as_bytes())?;
6✔
677
                ctx.write_all(b"\r\n")
6✔
678
            }
679
            Self::Cancel => ctx.write_all(b"*\r\n"),
2✔
680
        }
681
    }
8✔
682
}
683

684
impl<'a> EncodeIntoContext for AString<'a> {
685
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,116✔
686
        match self {
1,116✔
687
            AString::Atom(atom) => atom.encode_ctx(ctx),
832✔
688
            AString::String(imap_str) => imap_str.encode_ctx(ctx),
284✔
689
        }
690
    }
1,116✔
691
}
692

693
impl<'a> EncodeIntoContext for Atom<'a> {
694
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
78✔
695
        ctx.write_all(self.inner().as_bytes())
78✔
696
    }
78✔
697
}
698

699
impl<'a> EncodeIntoContext for AtomExt<'a> {
700
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
832✔
701
        ctx.write_all(self.inner().as_bytes())
832✔
702
    }
832✔
703
}
704

705
impl<'a> EncodeIntoContext for IString<'a> {
706
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
704✔
707
        match self {
704✔
708
            Self::Literal(val) => val.encode_ctx(ctx),
48✔
709
            Self::Quoted(val) => val.encode_ctx(ctx),
656✔
710
        }
711
    }
704✔
712
}
713

714
impl<'a> EncodeIntoContext for Literal<'a> {
715
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
716
        match self.mode() {
48✔
717
            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
34✔
718
            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
14✔
719
        }
720

721
        ctx.push_line();
48✔
722
        ctx.write_all(self.as_ref())?;
48✔
723
        ctx.push_literal(self.mode());
48✔
724

48✔
725
        Ok(())
48✔
726
    }
48✔
727
}
728

729
impl<'a> EncodeIntoContext for Quoted<'a> {
730
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
664✔
731
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
664✔
732
    }
664✔
733
}
734

735
impl<'a> EncodeIntoContext for Mailbox<'a> {
736
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
896✔
737
        match self {
896✔
738
            Mailbox::Inbox => ctx.write_all(b"INBOX"),
100✔
739
            Mailbox::Other(other) => other.encode_ctx(ctx),
796✔
740
        }
741
    }
896✔
742
}
743

744
impl<'a> EncodeIntoContext for MailboxOther<'a> {
745
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
796✔
746
        self.inner().encode_ctx(ctx)
796✔
747
    }
796✔
748
}
749

750
impl<'a> EncodeIntoContext for ListMailbox<'a> {
751
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
180✔
752
        match self {
180✔
753
            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
120✔
754
            ListMailbox::String(istr) => istr.encode_ctx(ctx),
60✔
755
        }
756
    }
180✔
757
}
758

759
impl<'a> EncodeIntoContext for ListCharString<'a> {
760
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
761
        ctx.write_all(self.as_ref())
120✔
762
    }
120✔
763
}
764

765
impl EncodeIntoContext for StatusDataItemName {
766
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
767
        match self {
44✔
768
            Self::Messages => ctx.write_all(b"MESSAGES"),
16✔
769
            Self::Recent => ctx.write_all(b"RECENT"),
2✔
770
            Self::UidNext => ctx.write_all(b"UIDNEXT"),
14✔
771
            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
2✔
772
            Self::Unseen => ctx.write_all(b"UNSEEN"),
2✔
773
            Self::Deleted => ctx.write_all(b"DELETED"),
4✔
774
            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
4✔
775
            #[cfg(feature = "ext_condstore_qresync")]
776
            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
×
777
        }
778
    }
44✔
779
}
780

781
impl<'a> EncodeIntoContext for Flag<'a> {
782
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
468✔
783
        write!(ctx, "{}", self)
468✔
784
    }
468✔
785
}
786

787
impl<'a> EncodeIntoContext for FlagFetch<'a> {
788
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
180✔
789
        match self {
180✔
790
            Self::Flag(flag) => flag.encode_ctx(ctx),
180✔
791
            Self::Recent => ctx.write_all(b"\\Recent"),
×
792
        }
793
    }
180✔
794
}
795

796
impl<'a> EncodeIntoContext for FlagPerm<'a> {
797
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
36✔
798
        match self {
36✔
799
            Self::Flag(flag) => flag.encode_ctx(ctx),
24✔
800
            Self::Asterisk => ctx.write_all(b"\\*"),
12✔
801
        }
802
    }
36✔
803
}
804

805
impl EncodeIntoContext for DateTime {
806
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
18✔
807
        self.as_ref().encode_ctx(ctx)
18✔
808
    }
18✔
809
}
810

811
impl<'a> EncodeIntoContext for Charset<'a> {
812
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
82✔
813
        match self {
82✔
814
            Charset::Atom(atom) => atom.encode_ctx(ctx),
74✔
815
            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
8✔
816
        }
817
    }
82✔
818
}
819

820
impl<'a> EncodeIntoContext for SearchKey<'a> {
821
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
220✔
822
        match self {
220✔
823
            SearchKey::All => ctx.write_all(b"ALL"),
14✔
824
            SearchKey::Answered => ctx.write_all(b"ANSWERED"),
6✔
825
            SearchKey::Bcc(astring) => {
2✔
826
                ctx.write_all(b"BCC ")?;
2✔
827
                astring.encode_ctx(ctx)
2✔
828
            }
829
            SearchKey::Before(date) => {
2✔
830
                ctx.write_all(b"BEFORE ")?;
2✔
831
                date.encode_ctx(ctx)
2✔
832
            }
833
            SearchKey::Body(astring) => {
2✔
834
                ctx.write_all(b"BODY ")?;
2✔
835
                astring.encode_ctx(ctx)
2✔
836
            }
837
            SearchKey::Cc(astring) => {
2✔
838
                ctx.write_all(b"CC ")?;
2✔
839
                astring.encode_ctx(ctx)
2✔
840
            }
841
            SearchKey::Deleted => ctx.write_all(b"DELETED"),
2✔
842
            SearchKey::Flagged => ctx.write_all(b"FLAGGED"),
14✔
843
            SearchKey::From(astring) => {
14✔
844
                ctx.write_all(b"FROM ")?;
14✔
845
                astring.encode_ctx(ctx)
14✔
846
            }
847
            SearchKey::Keyword(flag_keyword) => {
2✔
848
                ctx.write_all(b"KEYWORD ")?;
2✔
849
                flag_keyword.encode_ctx(ctx)
2✔
850
            }
851
            SearchKey::New => ctx.write_all(b"NEW"),
6✔
852
            SearchKey::Old => ctx.write_all(b"OLD"),
2✔
853
            SearchKey::On(date) => {
2✔
854
                ctx.write_all(b"ON ")?;
2✔
855
                date.encode_ctx(ctx)
2✔
856
            }
857
            SearchKey::Recent => ctx.write_all(b"RECENT"),
4✔
858
            SearchKey::Seen => ctx.write_all(b"SEEN"),
4✔
859
            SearchKey::Since(date) => {
50✔
860
                ctx.write_all(b"SINCE ")?;
50✔
861
                date.encode_ctx(ctx)
50✔
862
            }
863
            SearchKey::Subject(astring) => {
2✔
864
                ctx.write_all(b"SUBJECT ")?;
2✔
865
                astring.encode_ctx(ctx)
2✔
866
            }
867
            SearchKey::Text(astring) => {
38✔
868
                ctx.write_all(b"TEXT ")?;
38✔
869
                astring.encode_ctx(ctx)
38✔
870
            }
871
            SearchKey::To(astring) => {
2✔
872
                ctx.write_all(b"TO ")?;
2✔
873
                astring.encode_ctx(ctx)
2✔
874
            }
875
            SearchKey::Unanswered => ctx.write_all(b"UNANSWERED"),
2✔
876
            SearchKey::Undeleted => ctx.write_all(b"UNDELETED"),
2✔
877
            SearchKey::Unflagged => ctx.write_all(b"UNFLAGGED"),
2✔
878
            SearchKey::Unkeyword(flag_keyword) => {
2✔
879
                ctx.write_all(b"UNKEYWORD ")?;
2✔
880
                flag_keyword.encode_ctx(ctx)
2✔
881
            }
882
            SearchKey::Unseen => ctx.write_all(b"UNSEEN"),
2✔
883
            SearchKey::Draft => ctx.write_all(b"DRAFT"),
2✔
884
            SearchKey::Header(header_fld_name, astring) => {
2✔
885
                ctx.write_all(b"HEADER ")?;
2✔
886
                header_fld_name.encode_ctx(ctx)?;
2✔
887
                ctx.write_all(b" ")?;
2✔
888
                astring.encode_ctx(ctx)
2✔
889
            }
890
            SearchKey::Larger(number) => write!(ctx, "LARGER {number}"),
2✔
891
            SearchKey::Not(search_key) => {
14✔
892
                ctx.write_all(b"NOT ")?;
14✔
893
                search_key.encode_ctx(ctx)
14✔
894
            }
895
            SearchKey::Or(search_key_a, search_key_b) => {
2✔
896
                ctx.write_all(b"OR ")?;
2✔
897
                search_key_a.encode_ctx(ctx)?;
2✔
898
                ctx.write_all(b" ")?;
2✔
899
                search_key_b.encode_ctx(ctx)
2✔
900
            }
901
            SearchKey::SentBefore(date) => {
2✔
902
                ctx.write_all(b"SENTBEFORE ")?;
2✔
903
                date.encode_ctx(ctx)
2✔
904
            }
905
            SearchKey::SentOn(date) => {
2✔
906
                ctx.write_all(b"SENTON ")?;
2✔
907
                date.encode_ctx(ctx)
2✔
908
            }
909
            SearchKey::SentSince(date) => {
2✔
910
                ctx.write_all(b"SENTSINCE ")?;
2✔
911
                date.encode_ctx(ctx)
2✔
912
            }
913
            SearchKey::Smaller(number) => write!(ctx, "SMALLER {number}"),
2✔
914
            SearchKey::Uid(sequence_set) => {
2✔
915
                ctx.write_all(b"UID ")?;
2✔
916
                sequence_set.encode_ctx(ctx)
2✔
917
            }
918
            SearchKey::Undraft => ctx.write_all(b"UNDRAFT"),
2✔
919
            SearchKey::SequenceSet(sequence_set) => sequence_set.encode_ctx(ctx),
2✔
920
            SearchKey::And(search_keys) => {
4✔
921
                ctx.write_all(b"(")?;
4✔
922
                join_serializable(search_keys.as_ref(), b" ", ctx)?;
4✔
923
                ctx.write_all(b")")
4✔
924
            }
925
        }
926
    }
220✔
927
}
928

929
impl EncodeIntoContext for SequenceSet {
930
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
124✔
931
        join_serializable(self.0.as_ref(), b",", ctx)
124✔
932
    }
124✔
933
}
934

935
impl EncodeIntoContext for Sequence {
936
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
130✔
937
        match self {
130✔
938
            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
50✔
939
            Sequence::Range(from, to) => {
80✔
940
                from.encode_ctx(ctx)?;
80✔
941
                ctx.write_all(b":")?;
80✔
942
                to.encode_ctx(ctx)
80✔
943
            }
944
        }
945
    }
130✔
946
}
947

948
impl EncodeIntoContext for SeqOrUid {
949
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
210✔
950
        match self {
210✔
951
            SeqOrUid::Value(number) => write!(ctx, "{number}"),
200✔
952
            SeqOrUid::Asterisk => ctx.write_all(b"*"),
10✔
953
        }
954
    }
210✔
955
}
956

957
impl EncodeIntoContext for NaiveDate {
958
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
60✔
959
        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
60✔
960
    }
60✔
961
}
962

963
impl<'a> EncodeIntoContext for MacroOrMessageDataItemNames<'a> {
964
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
965
        match self {
48✔
966
            Self::Macro(m) => m.encode_ctx(ctx),
12✔
967
            Self::MessageDataItemNames(item_names) => {
36✔
968
                if item_names.len() == 1 {
36✔
969
                    item_names[0].encode_ctx(ctx)
24✔
970
                } else {
971
                    ctx.write_all(b"(")?;
12✔
972
                    join_serializable(item_names.as_slice(), b" ", ctx)?;
12✔
973
                    ctx.write_all(b")")
12✔
974
                }
975
            }
976
        }
977
    }
48✔
978
}
979

980
impl EncodeIntoContext for Macro {
981
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
982
        write!(ctx, "{}", self)
12✔
983
    }
12✔
984
}
985

986
impl<'a> EncodeIntoContext for MessageDataItemName<'a> {
987
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
70✔
988
        match self {
70✔
989
            Self::Body => ctx.write_all(b"BODY"),
2✔
990
            Self::BodyExt {
26✔
991
                section,
26✔
992
                partial,
26✔
993
                peek,
26✔
994
            } => {
26✔
995
                if *peek {
26✔
996
                    ctx.write_all(b"BODY.PEEK[")?;
×
997
                } else {
998
                    ctx.write_all(b"BODY[")?;
26✔
999
                }
1000
                if let Some(section) = section {
26✔
1001
                    section.encode_ctx(ctx)?;
24✔
1002
                }
2✔
1003
                ctx.write_all(b"]")?;
26✔
1004
                if let Some((a, b)) = partial {
26✔
1005
                    write!(ctx, "<{a}.{b}>")?;
×
1006
                }
26✔
1007

1008
                Ok(())
26✔
1009
            }
1010
            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
2✔
1011
            Self::Envelope => ctx.write_all(b"ENVELOPE"),
2✔
1012
            Self::Flags => ctx.write_all(b"FLAGS"),
26✔
1013
            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
2✔
1014
            Self::Rfc822 => ctx.write_all(b"RFC822"),
2✔
1015
            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
2✔
1016
            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
2✔
1017
            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
2✔
1018
            Self::Uid => ctx.write_all(b"UID"),
2✔
1019
            #[cfg(feature = "ext_binary")]
1020
            MessageDataItemName::Binary {
1021
                section,
×
1022
                partial,
×
1023
                peek,
×
1024
            } => {
×
1025
                ctx.write_all(b"BINARY")?;
×
1026
                if *peek {
×
1027
                    ctx.write_all(b".PEEK")?;
×
1028
                }
×
1029

1030
                ctx.write_all(b"[")?;
×
1031
                join_serializable(section, b".", ctx)?;
×
1032
                ctx.write_all(b"]")?;
×
1033

1034
                if let Some((a, b)) = partial {
×
1035
                    ctx.write_all(b"<")?;
×
1036
                    a.encode_ctx(ctx)?;
×
1037
                    ctx.write_all(b".")?;
×
1038
                    b.encode_ctx(ctx)?;
×
1039
                    ctx.write_all(b">")?;
×
1040
                }
×
1041

1042
                Ok(())
×
1043
            }
1044
            #[cfg(feature = "ext_binary")]
1045
            MessageDataItemName::BinarySize { section } => {
×
1046
                ctx.write_all(b"BINARY.SIZE")?;
×
1047

1048
                ctx.write_all(b"[")?;
×
1049
                join_serializable(section, b".", ctx)?;
×
1050
                ctx.write_all(b"]")
×
1051
            }
1052
        }
1053
    }
70✔
1054
}
1055

1056
impl<'a> EncodeIntoContext for Section<'a> {
1057
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
56✔
1058
        match self {
56✔
1059
            Section::Part(part) => part.encode_ctx(ctx),
2✔
1060
            Section::Header(maybe_part) => match maybe_part {
28✔
1061
                Some(part) => {
2✔
1062
                    part.encode_ctx(ctx)?;
2✔
1063
                    ctx.write_all(b".HEADER")
2✔
1064
                }
1065
                None => ctx.write_all(b"HEADER"),
26✔
1066
            },
1067
            Section::HeaderFields(maybe_part, header_list) => {
16✔
1068
                match maybe_part {
16✔
1069
                    Some(part) => {
2✔
1070
                        part.encode_ctx(ctx)?;
2✔
1071
                        ctx.write_all(b".HEADER.FIELDS (")?;
2✔
1072
                    }
1073
                    None => ctx.write_all(b"HEADER.FIELDS (")?,
14✔
1074
                };
1075
                join_serializable(header_list.as_ref(), b" ", ctx)?;
16✔
1076
                ctx.write_all(b")")
16✔
1077
            }
1078
            Section::HeaderFieldsNot(maybe_part, header_list) => {
4✔
1079
                match maybe_part {
4✔
1080
                    Some(part) => {
2✔
1081
                        part.encode_ctx(ctx)?;
2✔
1082
                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
2✔
1083
                    }
1084
                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
2✔
1085
                };
1086
                join_serializable(header_list.as_ref(), b" ", ctx)?;
4✔
1087
                ctx.write_all(b")")
4✔
1088
            }
1089
            Section::Text(maybe_part) => match maybe_part {
4✔
1090
                Some(part) => {
2✔
1091
                    part.encode_ctx(ctx)?;
2✔
1092
                    ctx.write_all(b".TEXT")
2✔
1093
                }
1094
                None => ctx.write_all(b"TEXT"),
2✔
1095
            },
1096
            Section::Mime(part) => {
2✔
1097
                part.encode_ctx(ctx)?;
2✔
1098
                ctx.write_all(b".MIME")
2✔
1099
            }
1100
        }
1101
    }
56✔
1102
}
1103

1104
impl EncodeIntoContext for Part {
1105
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
1106
        join_serializable(self.0.as_ref(), b".", ctx)
12✔
1107
    }
12✔
1108
}
1109

1110
impl EncodeIntoContext for NonZeroU32 {
1111
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
346✔
1112
        write!(ctx, "{self}")
346✔
1113
    }
346✔
1114
}
1115

1116
impl<'a> EncodeIntoContext for Capability<'a> {
1117
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
336✔
1118
        write!(ctx, "{}", self)
336✔
1119
    }
336✔
1120
}
1121

1122
// ----- Responses ---------------------------------------------------------------------------------
1123

1124
impl<'a> EncodeIntoContext for Response<'a> {
1125
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2,268✔
1126
        match self {
2,268✔
1127
            Response::Status(status) => status.encode_ctx(ctx),
1,212✔
1128
            Response::Data(data) => data.encode_ctx(ctx),
1,044✔
1129
            Response::CommandContinuationRequest(continue_request) => {
12✔
1130
                continue_request.encode_ctx(ctx)
12✔
1131
            }
1132
        }
1133
    }
2,268✔
1134
}
1135

1136
impl<'a> EncodeIntoContext for Greeting<'a> {
1137
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
30✔
1138
        ctx.write_all(b"* ")?;
30✔
1139
        self.kind.encode_ctx(ctx)?;
30✔
1140
        ctx.write_all(b" ")?;
30✔
1141

1142
        if let Some(ref code) = self.code {
30✔
1143
            ctx.write_all(b"[")?;
12✔
1144
            code.encode_ctx(ctx)?;
12✔
1145
            ctx.write_all(b"] ")?;
12✔
1146
        }
18✔
1147

1148
        self.text.encode_ctx(ctx)?;
30✔
1149
        ctx.write_all(b"\r\n")
30✔
1150
    }
30✔
1151
}
1152

1153
impl EncodeIntoContext for GreetingKind {
1154
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
30✔
1155
        match self {
30✔
1156
            GreetingKind::Ok => ctx.write_all(b"OK"),
12✔
1157
            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
16✔
1158
            GreetingKind::Bye => ctx.write_all(b"BYE"),
2✔
1159
        }
1160
    }
30✔
1161
}
1162

1163
impl<'a> EncodeIntoContext for Status<'a> {
1164
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,212✔
1165
        fn format_status(
1,333✔
1166
            tag: Option<&Tag>,
1,333✔
1167
            status: &str,
1,333✔
1168
            code: &Option<Code>,
1,333✔
1169
            comment: &Text,
1,333✔
1170
            ctx: &mut EncodeContext,
1,333✔
1171
        ) -> std::io::Result<()> {
1,333✔
1172
            match tag {
1,333✔
1173
                Some(tag) => tag.encode_ctx(ctx)?,
1,293✔
1174
                None => ctx.write_all(b"*")?,
1,252✔
1175
            }
1,212✔
1176
            ctx.write_all(b" ")?;
1,333✔
1177
            ctx.write_all(status.as_bytes())?;
1,333✔
1178
            ctx.write_all(b" ")?;
1,333✔
1179
            if let Some(code) = code {
1,333✔
1180
                ctx.write_all(b"[")?;
1,244✔
1181
                code.encode_ctx(ctx)?;
1,244✔
1182
                ctx.write_all(b"] ")?;
1,244✔
1183
            }
1,301✔
1184
            comment.encode_ctx(ctx)?;
1,333✔
1185
            ctx.write_all(b"\r\n")
1,333✔
1186
        }
1,333✔
1187

1,212✔
1188
        match self {
1,212✔
1189
            Self::Untagged(StatusBody { kind, code, text }) => match kind {
272✔
1190
                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
190✔
1191
                StatusKind::No => format_status(None, "NO", code, text, ctx),
42✔
1192
                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
40✔
1193
            },
1194
            Self::Tagged(Tagged {
902✔
1195
                tag,
902✔
1196
                body: StatusBody { kind, code, text },
902✔
1197
            }) => match kind {
902✔
1198
                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
856✔
1199
                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
30✔
1200
                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
16✔
1201
            },
1202
            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
38✔
1203
        }
1204
    }
1,212✔
1205
}
1206

1207
impl<'a> EncodeIntoContext for Code<'a> {
1208
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
216✔
1209
        match self {
216✔
1210
            Code::Alert => ctx.write_all(b"ALERT"),
28✔
1211
            Code::BadCharset { allowed } => {
2✔
1212
                if allowed.is_empty() {
2✔
1213
                    ctx.write_all(b"BADCHARSET")
2✔
1214
                } else {
1215
                    ctx.write_all(b"BADCHARSET (")?;
×
1216
                    join_serializable(allowed, b" ", ctx)?;
×
1217
                    ctx.write_all(b")")
×
1218
                }
1219
            }
1220
            Code::Capability(caps) => {
4✔
1221
                ctx.write_all(b"CAPABILITY ")?;
4✔
1222
                join_serializable(caps.as_ref(), b" ", ctx)
4✔
1223
            }
1224
            Code::Parse => ctx.write_all(b"PARSE"),
×
1225
            Code::PermanentFlags(flags) => {
24✔
1226
                ctx.write_all(b"PERMANENTFLAGS (")?;
24✔
1227
                join_serializable(flags, b" ", ctx)?;
24✔
1228
                ctx.write_all(b")")
24✔
1229
            }
1230
            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
12✔
1231
            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
24✔
1232
            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
×
1233
            Code::UidNext(next) => {
24✔
1234
                ctx.write_all(b"UIDNEXT ")?;
24✔
1235
                next.encode_ctx(ctx)
24✔
1236
            }
1237
            Code::UidValidity(validity) => {
36✔
1238
                ctx.write_all(b"UIDVALIDITY ")?;
36✔
1239
                validity.encode_ctx(ctx)
36✔
1240
            }
1241
            Code::Unseen(seq) => {
40✔
1242
                ctx.write_all(b"UNSEEN ")?;
40✔
1243
                seq.encode_ctx(ctx)
40✔
1244
            }
1245
            // RFC 2221
1246
            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1247
            Code::Referral(url) => {
×
1248
                ctx.write_all(b"REFERRAL ")?;
×
1249
                ctx.write_all(url.as_bytes())
×
1250
            }
1251
            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
×
1252
            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
4✔
1253
            Code::TooBig => ctx.write_all(b"TOOBIG"),
×
1254
            #[cfg(feature = "ext_metadata")]
1255
            Code::Metadata(code) => {
12✔
1256
                ctx.write_all(b"METADATA ")?;
12✔
1257
                code.encode_ctx(ctx)
12✔
1258
            }
1259
            #[cfg(feature = "ext_binary")]
1260
            Code::UnknownCte => ctx.write_all(b"UNKNOWN-CTE"),
×
1261
            #[cfg(feature = "ext_uidplus")]
1262
            Code::AppendUid { uid_validity, uid } => {
2✔
1263
                ctx.write_all(b"APPENDUID ")?;
2✔
1264
                uid_validity.encode_ctx(ctx)?;
2✔
1265
                ctx.write_all(b" ")?;
2✔
1266
                uid.encode_ctx(ctx)
2✔
1267
            }
1268
            #[cfg(feature = "ext_uidplus")]
1269
            Code::CopyUid {
1270
                uid_validity,
2✔
1271
                source,
2✔
1272
                destination,
2✔
1273
            } => {
2✔
1274
                ctx.write_all(b"COPYUID ")?;
2✔
1275
                uid_validity.encode_ctx(ctx)?;
2✔
1276
                ctx.write_all(b" ")?;
2✔
1277
                source.encode_ctx(ctx)?;
2✔
1278
                ctx.write_all(b" ")?;
2✔
1279
                destination.encode_ctx(ctx)
2✔
1280
            }
1281
            #[cfg(feature = "ext_uidplus")]
1282
            Code::UidNotSticky => ctx.write_all(b"UIDNOTSTICKY"),
2✔
1283
            Code::Other(unknown) => unknown.encode_ctx(ctx),
×
1284
        }
1285
    }
216✔
1286
}
1287

1288
impl<'a> EncodeIntoContext for CodeOther<'a> {
1289
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1290
        ctx.write_all(self.inner())
×
1291
    }
×
1292
}
1293

1294
impl<'a> EncodeIntoContext for Text<'a> {
1295
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,254✔
1296
        ctx.write_all(self.inner().as_bytes())
1,254✔
1297
    }
1,254✔
1298
}
1299

1300
impl<'a> EncodeIntoContext for Data<'a> {
1301
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,044✔
1302
        match self {
1,044✔
1303
            Data::Capability(caps) => {
92✔
1304
                ctx.write_all(b"* CAPABILITY ")?;
92✔
1305
                join_serializable(caps.as_ref(), b" ", ctx)?;
92✔
1306
            }
1307
            Data::List {
1308
                items,
314✔
1309
                delimiter,
314✔
1310
                mailbox,
314✔
1311
            } => {
314✔
1312
                ctx.write_all(b"* LIST (")?;
314✔
1313
                join_serializable(items, b" ", ctx)?;
314✔
1314
                ctx.write_all(b") ")?;
314✔
1315

1316
                if let Some(delimiter) = delimiter {
314✔
1317
                    ctx.write_all(b"\"")?;
314✔
1318
                    delimiter.encode_ctx(ctx)?;
314✔
1319
                    ctx.write_all(b"\"")?;
314✔
1320
                } else {
1321
                    ctx.write_all(b"NIL")?;
×
1322
                }
1323
                ctx.write_all(b" ")?;
314✔
1324
                mailbox.encode_ctx(ctx)?;
314✔
1325
            }
1326
            Data::Lsub {
1327
                items,
48✔
1328
                delimiter,
48✔
1329
                mailbox,
48✔
1330
            } => {
48✔
1331
                ctx.write_all(b"* LSUB (")?;
48✔
1332
                join_serializable(items, b" ", ctx)?;
48✔
1333
                ctx.write_all(b") ")?;
48✔
1334

1335
                if let Some(delimiter) = delimiter {
48✔
1336
                    ctx.write_all(b"\"")?;
48✔
1337
                    delimiter.encode_ctx(ctx)?;
48✔
1338
                    ctx.write_all(b"\"")?;
48✔
1339
                } else {
1340
                    ctx.write_all(b"NIL")?;
×
1341
                }
1342
                ctx.write_all(b" ")?;
48✔
1343
                mailbox.encode_ctx(ctx)?;
48✔
1344
            }
1345
            Data::Status { mailbox, items } => {
26✔
1346
                ctx.write_all(b"* STATUS ")?;
26✔
1347
                mailbox.encode_ctx(ctx)?;
26✔
1348
                ctx.write_all(b" (")?;
26✔
1349
                join_serializable(items, b" ", ctx)?;
26✔
1350
                ctx.write_all(b")")?;
26✔
1351
            }
1352
            Data::Search(seqs) => {
54✔
1353
                if seqs.is_empty() {
54✔
1354
                    ctx.write_all(b"* SEARCH")?;
12✔
1355
                } else {
1356
                    ctx.write_all(b"* SEARCH ")?;
42✔
1357
                    join_serializable(seqs, b" ", ctx)?;
42✔
1358
                }
1359
            }
1360
            #[cfg(feature = "ext_sort_thread")]
1361
            Data::Sort(seqs) => {
36✔
1362
                if seqs.is_empty() {
36✔
1363
                    ctx.write_all(b"* SORT")?;
12✔
1364
                } else {
1365
                    ctx.write_all(b"* SORT ")?;
24✔
1366
                    join_serializable(seqs, b" ", ctx)?;
24✔
1367
                }
1368
            }
1369
            #[cfg(feature = "ext_sort_thread")]
1370
            Data::Thread(threads) => {
36✔
1371
                if threads.is_empty() {
36✔
1372
                    ctx.write_all(b"* THREAD")?;
12✔
1373
                } else {
1374
                    ctx.write_all(b"* THREAD ")?;
24✔
1375
                    for thread in threads {
600✔
1376
                        thread.encode_ctx(ctx)?;
576✔
1377
                    }
1378
                }
1379
            }
1380
            Data::Flags(flags) => {
48✔
1381
                ctx.write_all(b"* FLAGS (")?;
48✔
1382
                join_serializable(flags, b" ", ctx)?;
48✔
1383
                ctx.write_all(b")")?;
48✔
1384
            }
1385
            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
62✔
1386
            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
62✔
1387
            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
74✔
1388
            Data::Fetch { seq, items } => {
140✔
1389
                write!(ctx, "* {seq} FETCH (")?;
140✔
1390
                join_serializable(items.as_ref(), b" ", ctx)?;
140✔
1391
                ctx.write_all(b")")?;
140✔
1392
            }
1393
            Data::Enabled { capabilities } => {
24✔
1394
                write!(ctx, "* ENABLED")?;
24✔
1395

1396
                for cap in capabilities {
48✔
1397
                    ctx.write_all(b" ")?;
24✔
1398
                    cap.encode_ctx(ctx)?;
24✔
1399
                }
1400
            }
1401
            Data::Quota { root, quotas } => {
12✔
1402
                ctx.write_all(b"* QUOTA ")?;
12✔
1403
                root.encode_ctx(ctx)?;
12✔
1404
                ctx.write_all(b" (")?;
12✔
1405
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
1406
                ctx.write_all(b")")?;
12✔
1407
            }
1408
            Data::QuotaRoot { mailbox, roots } => {
10✔
1409
                ctx.write_all(b"* QUOTAROOT ")?;
10✔
1410
                mailbox.encode_ctx(ctx)?;
10✔
1411
                for root in roots {
20✔
1412
                    ctx.write_all(b" ")?;
10✔
1413
                    root.encode_ctx(ctx)?;
10✔
1414
                }
1415
            }
1416
            #[cfg(feature = "ext_id")]
1417
            Data::Id { parameters } => {
2✔
1418
                ctx.write_all(b"* ID ")?;
2✔
1419

1420
                match parameters {
2✔
1421
                    Some(parameters) => {
×
1422
                        if let Some((first, tail)) = parameters.split_first() {
×
1423
                            ctx.write_all(b"(")?;
×
1424

1425
                            first.0.encode_ctx(ctx)?;
×
1426
                            ctx.write_all(b" ")?;
×
1427
                            first.1.encode_ctx(ctx)?;
×
1428

1429
                            for parameter in tail {
×
1430
                                ctx.write_all(b" ")?;
×
1431
                                parameter.0.encode_ctx(ctx)?;
×
1432
                                ctx.write_all(b" ")?;
×
1433
                                parameter.1.encode_ctx(ctx)?;
×
1434
                            }
1435

1436
                            ctx.write_all(b")")?;
×
1437
                        } else {
1438
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
1439
                            {
1440
                                ctx.write_all(b"()")?;
1441
                            }
1442
                            #[cfg(feature = "quirk_id_empty_to_nil")]
1443
                            {
1444
                                ctx.write_all(b"NIL")?;
×
1445
                            }
1446
                        }
1447
                    }
1448
                    None => {
1449
                        ctx.write_all(b"NIL")?;
2✔
1450
                    }
1451
                }
1452
            }
1453
            #[cfg(feature = "ext_metadata")]
1454
            Data::Metadata { mailbox, items } => {
4✔
1455
                ctx.write_all(b"* METADATA ")?;
4✔
1456
                mailbox.encode_ctx(ctx)?;
4✔
1457
                ctx.write_all(b" ")?;
4✔
1458
                items.encode_ctx(ctx)?;
4✔
1459
            }
1460
        }
1461

1462
        ctx.write_all(b"\r\n")
1,044✔
1463
    }
1,044✔
1464
}
1465

1466
impl<'a> EncodeIntoContext for FlagNameAttribute<'a> {
1467
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
134✔
1468
        write!(ctx, "{}", self)
134✔
1469
    }
134✔
1470
}
1471

1472
impl EncodeIntoContext for QuotedChar {
1473
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
362✔
1474
        match self.inner() {
362✔
1475
            '\\' => ctx.write_all(b"\\\\"),
×
1476
            '"' => ctx.write_all(b"\\\""),
×
1477
            other => ctx.write_all(&[other as u8]),
362✔
1478
        }
1479
    }
362✔
1480
}
1481

1482
impl EncodeIntoContext for StatusDataItem {
1483
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
68✔
1484
        match self {
68✔
1485
            Self::Messages(count) => {
28✔
1486
                ctx.write_all(b"MESSAGES ")?;
28✔
1487
                count.encode_ctx(ctx)
28✔
1488
            }
1489
            Self::Recent(count) => {
2✔
1490
                ctx.write_all(b"RECENT ")?;
2✔
1491
                count.encode_ctx(ctx)
2✔
1492
            }
1493
            Self::UidNext(next) => {
26✔
1494
                ctx.write_all(b"UIDNEXT ")?;
26✔
1495
                next.encode_ctx(ctx)
26✔
1496
            }
1497
            Self::UidValidity(identifier) => {
2✔
1498
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1499
                identifier.encode_ctx(ctx)
2✔
1500
            }
1501
            Self::Unseen(count) => {
2✔
1502
                ctx.write_all(b"UNSEEN ")?;
2✔
1503
                count.encode_ctx(ctx)
2✔
1504
            }
1505
            Self::Deleted(count) => {
4✔
1506
                ctx.write_all(b"DELETED ")?;
4✔
1507
                count.encode_ctx(ctx)
4✔
1508
            }
1509
            Self::DeletedStorage(count) => {
4✔
1510
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1511
                count.encode_ctx(ctx)
4✔
1512
            }
1513
        }
1514
    }
68✔
1515
}
1516

1517
impl<'a> EncodeIntoContext for MessageDataItem<'a> {
1518
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
260✔
1519
        match self {
260✔
1520
            Self::BodyExt {
20✔
1521
                section,
20✔
1522
                origin,
20✔
1523
                data,
20✔
1524
            } => {
20✔
1525
                ctx.write_all(b"BODY[")?;
20✔
1526
                if let Some(section) = section {
20✔
1527
                    section.encode_ctx(ctx)?;
12✔
1528
                }
8✔
1529
                ctx.write_all(b"]")?;
20✔
1530
                if let Some(origin) = origin {
20✔
1531
                    write!(ctx, "<{origin}>")?;
2✔
1532
                }
18✔
1533
                ctx.write_all(b" ")?;
20✔
1534
                data.encode_ctx(ctx)
20✔
1535
            }
1536
            // FIXME: do not return body-ext-1part and body-ext-mpart here
1537
            Self::Body(body) => {
14✔
1538
                ctx.write_all(b"BODY ")?;
14✔
1539
                body.encode_ctx(ctx)
14✔
1540
            }
1541
            Self::BodyStructure(body) => {
4✔
1542
                ctx.write_all(b"BODYSTRUCTURE ")?;
4✔
1543
                body.encode_ctx(ctx)
4✔
1544
            }
1545
            Self::Envelope(envelope) => {
14✔
1546
                ctx.write_all(b"ENVELOPE ")?;
14✔
1547
                envelope.encode_ctx(ctx)
14✔
1548
            }
1549
            Self::Flags(flags) => {
122✔
1550
                ctx.write_all(b"FLAGS (")?;
122✔
1551
                join_serializable(flags, b" ", ctx)?;
122✔
1552
                ctx.write_all(b")")
122✔
1553
            }
1554
            Self::InternalDate(datetime) => {
14✔
1555
                ctx.write_all(b"INTERNALDATE ")?;
14✔
1556
                datetime.encode_ctx(ctx)
14✔
1557
            }
1558
            Self::Rfc822(nstring) => {
4✔
1559
                ctx.write_all(b"RFC822 ")?;
4✔
1560
                nstring.encode_ctx(ctx)
4✔
1561
            }
1562
            Self::Rfc822Header(nstring) => {
2✔
1563
                ctx.write_all(b"RFC822.HEADER ")?;
2✔
1564
                nstring.encode_ctx(ctx)
2✔
1565
            }
1566
            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
26✔
1567
            Self::Rfc822Text(nstring) => {
2✔
1568
                ctx.write_all(b"RFC822.TEXT ")?;
2✔
1569
                nstring.encode_ctx(ctx)
2✔
1570
            }
1571
            Self::Uid(uid) => write!(ctx, "UID {uid}"),
38✔
1572
            #[cfg(feature = "ext_binary")]
1573
            Self::Binary { section, value } => {
×
1574
                ctx.write_all(b"BINARY[")?;
×
1575
                join_serializable(section, b".", ctx)?;
×
1576
                ctx.write_all(b"] ")?;
×
1577
                value.encode_ctx(ctx)
×
1578
            }
1579
            #[cfg(feature = "ext_binary")]
1580
            Self::BinarySize { section, size } => {
×
1581
                ctx.write_all(b"BINARY.SIZE[")?;
×
1582
                join_serializable(section, b".", ctx)?;
×
1583
                ctx.write_all(b"] ")?;
×
1584
                size.encode_ctx(ctx)
×
1585
            }
1586
        }
1587
    }
260✔
1588
}
1589

1590
impl<'a> EncodeIntoContext for NString<'a> {
1591
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
442✔
1592
        match &self.0 {
442✔
1593
            Some(imap_str) => imap_str.encode_ctx(ctx),
268✔
1594
            None => ctx.write_all(b"NIL"),
174✔
1595
        }
1596
    }
442✔
1597
}
1598

1599
#[cfg(any(feature = "ext_binary", feature = "ext_metadata"))]
1600
impl<'a> EncodeIntoContext for NString8<'a> {
1601
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1602
        match self {
8✔
1603
            NString8::NString(nstring) => nstring.encode_ctx(ctx),
6✔
1604
            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
2✔
1605
        }
1606
    }
8✔
1607
}
1608

1609
impl<'a> EncodeIntoContext for BodyStructure<'a> {
1610
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
36✔
1611
        ctx.write_all(b"(")?;
36✔
1612
        match self {
36✔
1613
            BodyStructure::Single {
1614
                body,
24✔
1615
                extension_data: extension,
24✔
1616
            } => {
24✔
1617
                body.encode_ctx(ctx)?;
24✔
1618
                if let Some(extension) = extension {
24✔
1619
                    ctx.write_all(b" ")?;
4✔
1620
                    extension.encode_ctx(ctx)?;
4✔
1621
                }
20✔
1622
            }
1623
            BodyStructure::Multi {
1624
                bodies,
12✔
1625
                subtype,
12✔
1626
                extension_data,
12✔
1627
            } => {
1628
                for body in bodies.as_ref() {
12✔
1629
                    body.encode_ctx(ctx)?;
12✔
1630
                }
1631
                ctx.write_all(b" ")?;
12✔
1632
                subtype.encode_ctx(ctx)?;
12✔
1633

1634
                if let Some(extension) = extension_data {
12✔
1635
                    ctx.write_all(b" ")?;
×
1636
                    extension.encode_ctx(ctx)?;
×
1637
                }
12✔
1638
            }
1639
        }
1640
        ctx.write_all(b")")
36✔
1641
    }
36✔
1642
}
1643

1644
impl<'a> EncodeIntoContext for Body<'a> {
1645
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
24✔
1646
        match self.specific {
24✔
1647
            SpecificFields::Basic {
1648
                r#type: ref type_,
4✔
1649
                ref subtype,
4✔
1650
            } => {
4✔
1651
                type_.encode_ctx(ctx)?;
4✔
1652
                ctx.write_all(b" ")?;
4✔
1653
                subtype.encode_ctx(ctx)?;
4✔
1654
                ctx.write_all(b" ")?;
4✔
1655
                self.basic.encode_ctx(ctx)
4✔
1656
            }
1657
            SpecificFields::Message {
1658
                ref envelope,
×
1659
                ref body_structure,
×
1660
                number_of_lines,
×
1661
            } => {
×
1662
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
1663
                self.basic.encode_ctx(ctx)?;
×
1664
                ctx.write_all(b" ")?;
×
1665
                envelope.encode_ctx(ctx)?;
×
1666
                ctx.write_all(b" ")?;
×
1667
                body_structure.encode_ctx(ctx)?;
×
1668
                ctx.write_all(b" ")?;
×
1669
                write!(ctx, "{number_of_lines}")
×
1670
            }
1671
            SpecificFields::Text {
1672
                ref subtype,
20✔
1673
                number_of_lines,
20✔
1674
            } => {
20✔
1675
                ctx.write_all(b"\"TEXT\" ")?;
20✔
1676
                subtype.encode_ctx(ctx)?;
20✔
1677
                ctx.write_all(b" ")?;
20✔
1678
                self.basic.encode_ctx(ctx)?;
20✔
1679
                ctx.write_all(b" ")?;
20✔
1680
                write!(ctx, "{number_of_lines}")
20✔
1681
            }
1682
        }
1683
    }
24✔
1684
}
1685

1686
impl<'a> EncodeIntoContext for BasicFields<'a> {
1687
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
24✔
1688
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
24✔
1689
        ctx.write_all(b" ")?;
24✔
1690
        self.id.encode_ctx(ctx)?;
24✔
1691
        ctx.write_all(b" ")?;
24✔
1692
        self.description.encode_ctx(ctx)?;
24✔
1693
        ctx.write_all(b" ")?;
24✔
1694
        self.content_transfer_encoding.encode_ctx(ctx)?;
24✔
1695
        ctx.write_all(b" ")?;
24✔
1696
        write!(ctx, "{}", self.size)
24✔
1697
    }
24✔
1698
}
1699

1700
impl<'a> EncodeIntoContext for Envelope<'a> {
1701
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
1702
        ctx.write_all(b"(")?;
14✔
1703
        self.date.encode_ctx(ctx)?;
14✔
1704
        ctx.write_all(b" ")?;
14✔
1705
        self.subject.encode_ctx(ctx)?;
14✔
1706
        ctx.write_all(b" ")?;
14✔
1707
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
14✔
1708
        ctx.write_all(b" ")?;
14✔
1709
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
14✔
1710
        ctx.write_all(b" ")?;
14✔
1711
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
14✔
1712
        ctx.write_all(b" ")?;
14✔
1713
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
14✔
1714
        ctx.write_all(b" ")?;
14✔
1715
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
14✔
1716
        ctx.write_all(b" ")?;
14✔
1717
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
14✔
1718
        ctx.write_all(b" ")?;
14✔
1719
        self.in_reply_to.encode_ctx(ctx)?;
14✔
1720
        ctx.write_all(b" ")?;
14✔
1721
        self.message_id.encode_ctx(ctx)?;
14✔
1722
        ctx.write_all(b")")
14✔
1723
    }
14✔
1724
}
1725

1726
impl<'a> EncodeIntoContext for Address<'a> {
1727
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
72✔
1728
        ctx.write_all(b"(")?;
72✔
1729
        self.name.encode_ctx(ctx)?;
72✔
1730
        ctx.write_all(b" ")?;
72✔
1731
        self.adl.encode_ctx(ctx)?;
72✔
1732
        ctx.write_all(b" ")?;
72✔
1733
        self.mailbox.encode_ctx(ctx)?;
72✔
1734
        ctx.write_all(b" ")?;
72✔
1735
        self.host.encode_ctx(ctx)?;
72✔
1736
        ctx.write_all(b")")?;
72✔
1737

1738
        Ok(())
72✔
1739
    }
72✔
1740
}
1741

1742
impl<'a> EncodeIntoContext for SinglePartExtensionData<'a> {
1743
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1744
        self.md5.encode_ctx(ctx)?;
6✔
1745

1746
        if let Some(disposition) = &self.tail {
6✔
1747
            ctx.write_all(b" ")?;
6✔
1748
            disposition.encode_ctx(ctx)?;
6✔
1749
        }
×
1750

1751
        Ok(())
6✔
1752
    }
6✔
1753
}
1754

1755
impl<'a> EncodeIntoContext for MultiPartExtensionData<'a> {
1756
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1757
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1758

1759
        if let Some(disposition) = &self.tail {
×
1760
            ctx.write_all(b" ")?;
×
1761
            disposition.encode_ctx(ctx)?;
×
1762
        }
×
1763

1764
        Ok(())
×
1765
    }
×
1766
}
1767

1768
impl<'a> EncodeIntoContext for Disposition<'a> {
1769
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1770
        match &self.disposition {
6✔
1771
            Some((s, param)) => {
×
1772
                ctx.write_all(b"(")?;
×
1773
                s.encode_ctx(ctx)?;
×
1774
                ctx.write_all(b" ")?;
×
1775
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1776
                ctx.write_all(b")")?;
×
1777
            }
1778
            None => ctx.write_all(b"NIL")?,
6✔
1779
        }
1780

1781
        if let Some(language) = &self.tail {
6✔
1782
            ctx.write_all(b" ")?;
6✔
1783
            language.encode_ctx(ctx)?;
6✔
1784
        }
×
1785

1786
        Ok(())
6✔
1787
    }
6✔
1788
}
1789

1790
impl<'a> EncodeIntoContext for Language<'a> {
1791
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1792
        List1OrNil(&self.language, b" ").encode_ctx(ctx)?;
6✔
1793

1794
        if let Some(location) = &self.tail {
6✔
1795
            ctx.write_all(b" ")?;
6✔
1796
            location.encode_ctx(ctx)?;
6✔
1797
        }
×
1798

1799
        Ok(())
6✔
1800
    }
6✔
1801
}
1802

1803
impl<'a> EncodeIntoContext for Location<'a> {
1804
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1805
        self.location.encode_ctx(ctx)?;
6✔
1806

1807
        for body_extension in &self.extensions {
10✔
1808
            ctx.write_all(b" ")?;
4✔
1809
            body_extension.encode_ctx(ctx)?;
4✔
1810
        }
1811

1812
        Ok(())
6✔
1813
    }
6✔
1814
}
1815

1816
impl<'a> EncodeIntoContext for BodyExtension<'a> {
1817
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1818
        match self {
6✔
1819
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
1820
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
1821
            BodyExtension::List(list) => {
2✔
1822
                ctx.write_all(b"(")?;
2✔
1823
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
1824
                ctx.write_all(b")")
2✔
1825
            }
1826
        }
1827
    }
6✔
1828
}
1829

1830
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
1831
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
18✔
1832
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
18✔
1833
    }
18✔
1834
}
1835

1836
impl<'a> EncodeIntoContext for CommandContinuationRequest<'a> {
1837
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
1838
        match self {
12✔
1839
            Self::Basic(continue_basic) => match continue_basic.code() {
12✔
1840
                Some(code) => {
×
1841
                    ctx.write_all(b"+ [")?;
×
1842
                    code.encode_ctx(ctx)?;
×
1843
                    ctx.write_all(b"] ")?;
×
1844
                    continue_basic.text().encode_ctx(ctx)?;
×
1845
                    ctx.write_all(b"\r\n")
×
1846
                }
1847
                None => {
1848
                    ctx.write_all(b"+ ")?;
12✔
1849
                    continue_basic.text().encode_ctx(ctx)?;
12✔
1850
                    ctx.write_all(b"\r\n")
12✔
1851
                }
1852
            },
1853
            Self::Base64(data) => {
×
1854
                ctx.write_all(b"+ ")?;
×
1855
                ctx.write_all(base64.encode(data).as_bytes())?;
×
1856
                ctx.write_all(b"\r\n")
×
1857
            }
1858
        }
1859
    }
12✔
1860
}
1861

1862
pub(crate) mod utils {
1863
    use std::io::Write;
1864

1865
    use super::{EncodeContext, EncodeIntoContext};
1866

1867
    pub struct List1OrNil<'a, T>(pub &'a Vec<T>, pub &'a [u8]);
1868

1869
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
1870

1871
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
1,306✔
1872
        elements: &[I],
1,306✔
1873
        sep: &[u8],
1,306✔
1874
        ctx: &mut EncodeContext,
1,306✔
1875
    ) -> std::io::Result<()> {
1,306✔
1876
        if let Some((last, head)) = elements.split_last() {
1,306✔
1877
            for item in head {
1,930✔
1878
                item.encode_ctx(ctx)?;
868✔
1879
                ctx.write_all(sep)?;
868✔
1880
            }
1881

1882
            last.encode_ctx(ctx)
1,062✔
1883
        } else {
1884
            Ok(())
244✔
1885
        }
1886
    }
1,306✔
1887

1888
    impl<'a, T> EncodeIntoContext for List1OrNil<'a, T>
1889
    where
1890
        T: EncodeIntoContext,
1891
    {
1892
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
1893
            if let Some((last, head)) = self.0.split_last() {
90✔
1894
                ctx.write_all(b"(")?;
60✔
1895

1896
                for item in head {
72✔
1897
                    item.encode_ctx(ctx)?;
12✔
1898
                    ctx.write_all(self.1)?;
12✔
1899
                }
1900

1901
                last.encode_ctx(ctx)?;
60✔
1902

1903
                ctx.write_all(b")")
60✔
1904
            } else {
1905
                ctx.write_all(b"NIL")
30✔
1906
            }
1907
        }
90✔
1908
    }
1909

1910
    impl<'a, T> EncodeIntoContext for List1AttributeValueOrNil<'a, T>
1911
    where
1912
        T: EncodeIntoContext,
1913
    {
1914
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
24✔
1915
            if let Some((last, head)) = self.0.split_last() {
24✔
1916
                ctx.write_all(b"(")?;
12✔
1917

1918
                for (attribute, value) in head {
12✔
1919
                    attribute.encode_ctx(ctx)?;
×
1920
                    ctx.write_all(b" ")?;
×
1921
                    value.encode_ctx(ctx)?;
×
1922
                    ctx.write_all(b" ")?;
×
1923
                }
1924

1925
                let (attribute, value) = last;
12✔
1926
                attribute.encode_ctx(ctx)?;
12✔
1927
                ctx.write_all(b" ")?;
12✔
1928
                value.encode_ctx(ctx)?;
12✔
1929

1930
                ctx.write_all(b")")
12✔
1931
            } else {
1932
                ctx.write_all(b"NIL")
12✔
1933
            }
1934
        }
24✔
1935
    }
1936
}
1937

1938
#[cfg(test)]
1939
mod tests {
1940
    use std::num::NonZeroU32;
1941

1942
    use imap_types::{
1943
        auth::AuthMechanism,
1944
        command::{Command, CommandBody},
1945
        core::{AString, Literal, NString, Vec1},
1946
        fetch::MessageDataItem,
1947
        response::{Data, Response},
1948
        utils::escape_byte_string,
1949
    };
1950

1951
    use super::*;
1952

1953
    #[test]
1954
    fn test_api_encoder_usage() {
2✔
1955
        let cmd = Command::new(
2✔
1956
            "A",
2✔
1957
            CommandBody::login(
2✔
1958
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
1959
                "password",
2✔
1960
            )
2✔
1961
            .unwrap(),
2✔
1962
        )
2✔
1963
        .unwrap();
2✔
1964

2✔
1965
        // Dump.
2✔
1966
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
1967

2✔
1968
        // Encoded.
2✔
1969
        let encoded = CommandCodec::default().encode(&cmd);
2✔
1970

2✔
1971
        let mut out = Vec::new();
2✔
1972

1973
        for x in encoded {
8✔
1974
            match x {
6✔
1975
                Fragment::Line { data } => {
4✔
1976
                    println!("C: {}", escape_byte_string(&data));
4✔
1977
                    out.extend_from_slice(&data);
4✔
1978
                }
4✔
1979
                Fragment::Literal { data, mode } => {
2✔
1980
                    match mode {
2✔
1981
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
1982
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
1983
                    }
1984

1985
                    println!("C: {}", escape_byte_string(&data));
2✔
1986
                    out.extend_from_slice(&data);
2✔
1987
                }
1988
            }
1989
        }
1990

1991
        assert_eq!(got_encoded, out);
2✔
1992
    }
2✔
1993

1994
    #[test]
1995
    fn test_encode_command() {
2✔
1996
        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2✔
1997
            (
2✔
1998
                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2✔
1999
                [Fragment::Line {
2✔
2000
                    data: b"A LOGIN alice pass\r\n".to_vec(),
2✔
2001
                }]
2✔
2002
                .as_ref(),
2✔
2003
            ),
2✔
2004
            (
2✔
2005
                Command::new(
2✔
2006
                    "A",
2✔
2007
                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2✔
2008
                )
2✔
2009
                .unwrap(),
2✔
2010
                [
2✔
2011
                    Fragment::Line {
2✔
2012
                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
2013
                    },
2✔
2014
                    Fragment::Literal {
2✔
2015
                        data: b"\xCA\xFE".to_vec(),
2✔
2016
                        mode: LiteralMode::Sync,
2✔
2017
                    },
2✔
2018
                    Fragment::Line {
2✔
2019
                        data: b"\r\n".to_vec(),
2✔
2020
                    },
2✔
2021
                ]
2✔
2022
                .as_ref(),
2✔
2023
            ),
2✔
2024
            (
2✔
2025
                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2✔
2026
                [Fragment::Line {
2✔
2027
                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2✔
2028
                }]
2✔
2029
                .as_ref(),
2✔
2030
            ),
2✔
2031
            (
2✔
2032
                Command::new(
2✔
2033
                    "A",
2✔
2034
                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2✔
2035
                )
2✔
2036
                .unwrap(),
2✔
2037
                [Fragment::Line {
2✔
2038
                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2✔
2039
                }]
2✔
2040
                .as_ref(),
2✔
2041
            ),
2✔
2042
            (
2✔
2043
                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2✔
2044
                [Fragment::Line {
2✔
2045
                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2✔
2046
                }]
2✔
2047
                .as_ref(),
2✔
2048
            ),
2✔
2049
            (
2✔
2050
                Command::new(
2✔
2051
                    "A",
2✔
2052
                    CommandBody::authenticate_with_ir(
2✔
2053
                        AuthMechanism::Plain,
2✔
2054
                        b"\x00alice\x00pass".as_ref(),
2✔
2055
                    ),
2✔
2056
                )
2✔
2057
                .unwrap(),
2✔
2058
                [Fragment::Line {
2✔
2059
                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2✔
2060
                }]
2✔
2061
                .as_ref(),
2✔
2062
            ),
2✔
2063
        ]);
2✔
2064
    }
2✔
2065

2066
    #[test]
2067
    fn test_encode_response() {
2✔
2068
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
2069
            (
2✔
2070
                Response::Data(Data::Fetch {
2✔
2071
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2072
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2073
                        section: None,
2✔
2074
                        origin: None,
2✔
2075
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
2076
                    }),
2✔
2077
                }),
2✔
2078
                [
2✔
2079
                    Fragment::Line {
2✔
2080
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
2081
                    },
2✔
2082
                    Fragment::Literal {
2✔
2083
                        data: b"ABCDE".to_vec(),
2✔
2084
                        mode: LiteralMode::Sync,
2✔
2085
                    },
2✔
2086
                    Fragment::Line {
2✔
2087
                        data: b")\r\n".to_vec(),
2✔
2088
                    },
2✔
2089
                ]
2✔
2090
                .as_ref(),
2✔
2091
            ),
2✔
2092
            (
2✔
2093
                Response::Data(Data::Fetch {
2✔
2094
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2095
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2096
                        section: None,
2✔
2097
                        origin: None,
2✔
2098
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
2099
                    }),
2✔
2100
                }),
2✔
2101
                [
2✔
2102
                    Fragment::Line {
2✔
2103
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
2104
                    },
2✔
2105
                    Fragment::Literal {
2✔
2106
                        data: b"ABCDE".to_vec(),
2✔
2107
                        mode: LiteralMode::NonSync,
2✔
2108
                    },
2✔
2109
                    Fragment::Line {
2✔
2110
                        data: b")\r\n".to_vec(),
2✔
2111
                    },
2✔
2112
                ]
2✔
2113
                .as_ref(),
2✔
2114
            ),
2✔
2115
        ])
2✔
2116
    }
2✔
2117

2118
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
2119
    where
4✔
2120
        E: Encoder<Message<'a> = M> + Default,
4✔
2121
        F: AsRef<[Fragment]>,
4✔
2122
    {
4✔
2123
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
2124
            println!("# Testing {i}");
16✔
2125

16✔
2126
            let encoder = E::default().encode(obj);
16✔
2127
            let actions = actions.as_ref();
16✔
2128

16✔
2129
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2130
        }
2131
    }
4✔
2132

2133
    #[cfg(feature = "serde")]
2134
    #[test]
2135
    fn test_serialize_fragment() {
2✔
2136
        let fragments = [
2✔
2137
            Fragment::Line {
2✔
2138
                data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
2139
            },
2✔
2140
            Fragment::Literal {
2✔
2141
                data: b"\xCA\xFE".to_vec(),
2✔
2142
                mode: LiteralMode::Sync,
2✔
2143
            },
2✔
2144
            Fragment::Line {
2✔
2145
                data: b"\r\n".to_vec(),
2✔
2146
            },
2✔
2147
        ];
2✔
2148

2✔
2149
        let json = serde_json::to_string(&fragments).unwrap();
2✔
2150
        assert_eq!(
2✔
2151
            json,
2✔
2152
            r#"[{"Line":{"data":[65,32,76,79,71,73,78,32,97,108,105,99,101,32,123,50,125,13,10]}},{"Literal":{"data":[202,254],"mode":"Sync"}},{"Line":{"data":[13,10]}}]"#
2✔
2153
        );
2✔
2154
        assert_eq!(
2✔
2155
            serde_json::from_str::<[Fragment; 3]>(&json).unwrap(),
2✔
2156
            fragments
2✔
2157
        );
2✔
2158
    }
2✔
2159
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc