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

duesee / imap-codec / 12757105626

13 Jan 2025 10:16PM UTC coverage: 92.896% (-0.1%) from 93.011%
12757105626

push

github

duesee
docs: fix warning

11350 of 12218 relevant lines covered (92.9%)

904.56 hits per line

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

86.93
/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
#[cfg(feature = "ext_condstore_qresync")]
49
use std::num::NonZeroU64;
50
use std::{borrow::Borrow, collections::VecDeque, io::Write, num::NonZeroU32};
51

52
use base64::{engine::general_purpose::STANDARD as base64, Engine};
53
use chrono::{DateTime as ChronoDateTime, FixedOffset};
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, NString8, 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
use utils::{join_serializable, List1AttributeValueOrNil, List1OrNil};
83

84
use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
85

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

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

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

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

133
        for fragment in self.items {
4,076✔
134
            match fragment {
2,048✔
135
                Fragment::Line { mut data } => out.append(&mut data),
2,038✔
136
                Fragment::Literal { mut data, .. } => out.append(&mut data),
10✔
137
            }
138
        }
139

140
        out
2,028✔
141
    }
2,028✔
142
}
143

144
impl Iterator for Encoded {
145
    type Item = Fragment;
146

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

152
/// The intended action of a client or server.
153
#[derive(Clone, Debug, Eq, PartialEq)]
154
pub enum Fragment {
155
    /// A line that is ready to be send.
156
    Line { data: Vec<u8> },
157

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

162
//--------------------------------------------------------------------------------------------------
163

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

170
impl EncodeContext {
171
    pub fn new() -> Self {
2,494✔
172
        Self::default()
2,494✔
173
    }
2,494✔
174

175
    pub fn push_line(&mut self) {
46✔
176
        self.items.push_back(Fragment::Line {
46✔
177
            data: std::mem::take(&mut self.accumulator),
46✔
178
        })
46✔
179
    }
46✔
180

181
    pub fn push_literal(&mut self, mode: LiteralMode) {
46✔
182
        self.items.push_back(Fragment::Literal {
46✔
183
            data: std::mem::take(&mut self.accumulator),
46✔
184
            mode,
46✔
185
        })
46✔
186
    }
46✔
187

188
    pub fn into_items(self) -> VecDeque<Fragment> {
2,494✔
189
        let Self {
2,494✔
190
            accumulator,
2,494✔
191
            mut items,
2,494✔
192
        } = self;
2,494✔
193

2,494✔
194
        if !accumulator.is_empty() {
2,494✔
195
            items.push_back(Fragment::Line { data: accumulator });
2,494✔
196
        }
2,494✔
197

198
        items
2,494✔
199
    }
2,494✔
200

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

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

213
        out
448✔
214
    }
448✔
215
}
216

217
impl Write for EncodeContext {
218
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
19,248✔
219
        self.accumulator.extend_from_slice(buf);
19,248✔
220
        Ok(buf.len())
19,248✔
221
    }
19,248✔
222

223
    fn flush(&mut self) -> std::io::Result<()> {
×
224
        Ok(())
×
225
    }
×
226
}
227

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

233
            fn encode(&self, message: &Self::Message<'_>) -> Encoded {
1,540✔
234
                let mut encode_context = EncodeContext::new();
1,540✔
235
                EncodeIntoContext::encode_ctx(message.borrow(), &mut encode_context).unwrap();
1,540✔
236

1,540✔
237
                Encoded {
1,540✔
238
                    items: encode_context.into_items(),
1,540✔
239
                }
1,540✔
240
            }
1,540✔
241
        }
242
    };
243
}
244

245
impl_encoder_for_codec!(GreetingCodec, Greeting<'a>);
246
impl_encoder_for_codec!(CommandCodec, Command<'a>);
247
impl_encoder_for_codec!(AuthenticateDataCodec, AuthenticateData<'a>);
248
impl_encoder_for_codec!(ResponseCodec, Response<'a>);
249
impl_encoder_for_codec!(IdleDoneCodec, IdleDone);
250

251
// -------------------------------------------------------------------------------------------------
252

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

257
// ----- Primitive ---------------------------------------------------------------------------------
258

259
impl EncodeIntoContext for u32 {
260
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
261
        ctx.write_all(self.to_string().as_bytes())
44✔
262
    }
44✔
263
}
264

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

271
// ----- Command -----------------------------------------------------------------------------------
272

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

282
impl EncodeIntoContext for Tag<'_> {
283
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,304✔
284
        ctx.write_all(self.inner().as_bytes())
1,304✔
285
    }
1,304✔
286
}
287

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

304
                if let Some(ir) = initial_response {
10✔
305
                    ctx.write_all(b" ")?;
6✔
306

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

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

409
                if !flags.is_empty() {
×
410
                    ctx.write_all(b" ")?;
×
411
                    ctx.write_all(b"(")?;
×
412
                    join_serializable(flags, b" ", ctx)?;
×
413
                    ctx.write_all(b")")?;
×
414
                }
×
415

416
                if let Some(date) = date {
×
417
                    ctx.write_all(b" ")?;
×
418
                    date.encode_ctx(ctx)?;
×
419
                }
×
420

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

495
                sequence_set.encode_ctx(ctx)?;
32✔
496
                ctx.write_all(b" ")?;
32✔
497
                macro_or_item_names.encode_ctx(ctx)?;
32✔
498

499
                #[cfg(feature = "ext_condstore_qresync")]
500
                if let Some(changed_since) = changed_since {
32✔
501
                    ctx.write_all(b" (CHANGEDSINCE ")?;
×
502
                    changed_since.encode_ctx(ctx)?;
×
503
                    ctx.write_all(b" ")?;
×
504
                }
32✔
505

506
                Ok(())
32✔
507
            }
508
            CommandBody::Store {
509
                sequence_set,
16✔
510
                kind,
16✔
511
                response,
16✔
512
                flags,
16✔
513
                uid,
16✔
514
                #[cfg(feature = "ext_condstore_qresync")]
16✔
515
                unchanged_since,
16✔
516
            } => {
16✔
517
                if *uid {
16✔
518
                    ctx.write_all(b"UID STORE ")?;
×
519
                } else {
520
                    ctx.write_all(b"STORE ")?;
16✔
521
                }
522

523
                sequence_set.encode_ctx(ctx)?;
16✔
524
                ctx.write_all(b" ")?;
16✔
525

526
                #[cfg(feature = "ext_condstore_qresync")]
527
                if let Some(unchanged_since) = unchanged_since {
16✔
528
                    ctx.write_all(b"(UNCHANGEDSINCE ")?;
×
529
                    unchanged_since.encode_ctx(ctx)?;
×
530
                    ctx.write_all(b") ")?;
×
531
                }
16✔
532

533
                match kind {
16✔
534
                    StoreType::Add => ctx.write_all(b"+")?,
16✔
535
                    StoreType::Remove => ctx.write_all(b"-")?,
×
536
                    StoreType::Replace => {}
×
537
                }
538

539
                ctx.write_all(b"FLAGS")?;
16✔
540

541
                match response {
16✔
542
                    StoreResponse::Answer => {}
16✔
543
                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
×
544
                }
545

546
                ctx.write_all(b" (")?;
16✔
547
                join_serializable(flags, b" ", ctx)?;
16✔
548
                ctx.write_all(b")")
16✔
549
            }
550
            CommandBody::Copy {
551
                sequence_set,
24✔
552
                mailbox,
24✔
553
                uid,
24✔
554
            } => {
24✔
555
                if *uid {
24✔
556
                    ctx.write_all(b"UID COPY ")?;
×
557
                } else {
558
                    ctx.write_all(b"COPY ")?;
24✔
559
                }
560
                sequence_set.encode_ctx(ctx)?;
24✔
561
                ctx.write_all(b" ")?;
24✔
562
                mailbox.encode_ctx(ctx)
24✔
563
            }
564
            CommandBody::Idle => ctx.write_all(b"IDLE"),
4✔
565
            CommandBody::Enable { capabilities } => {
22✔
566
                ctx.write_all(b"ENABLE ")?;
22✔
567
                join_serializable(capabilities.as_ref(), b" ", ctx)
22✔
568
            }
569
            CommandBody::Compress { algorithm } => {
4✔
570
                ctx.write_all(b"COMPRESS ")?;
4✔
571
                algorithm.encode_ctx(ctx)
4✔
572
            }
573
            CommandBody::GetQuota { root } => {
10✔
574
                ctx.write_all(b"GETQUOTA ")?;
10✔
575
                root.encode_ctx(ctx)
10✔
576
            }
577
            CommandBody::GetQuotaRoot { mailbox } => {
6✔
578
                ctx.write_all(b"GETQUOTAROOT ")?;
6✔
579
                mailbox.encode_ctx(ctx)
6✔
580
            }
581
            CommandBody::SetQuota { root, quotas } => {
12✔
582
                ctx.write_all(b"SETQUOTA ")?;
12✔
583
                root.encode_ctx(ctx)?;
12✔
584
                ctx.write_all(b" (")?;
12✔
585
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
586
                ctx.write_all(b")")
12✔
587
            }
588
            CommandBody::Move {
589
                sequence_set,
6✔
590
                mailbox,
6✔
591
                uid,
6✔
592
            } => {
6✔
593
                if *uid {
6✔
594
                    ctx.write_all(b"UID MOVE ")?;
2✔
595
                } else {
596
                    ctx.write_all(b"MOVE ")?;
4✔
597
                }
598
                sequence_set.encode_ctx(ctx)?;
6✔
599
                ctx.write_all(b" ")?;
6✔
600
                mailbox.encode_ctx(ctx)
6✔
601
            }
602
            #[cfg(feature = "ext_id")]
603
            CommandBody::Id { parameters } => {
8✔
604
                ctx.write_all(b"ID ")?;
8✔
605

606
                match parameters {
8✔
607
                    Some(parameters) => {
4✔
608
                        if let Some((first, tail)) = parameters.split_first() {
4✔
609
                            ctx.write_all(b"(")?;
4✔
610

611
                            first.0.encode_ctx(ctx)?;
4✔
612
                            ctx.write_all(b" ")?;
4✔
613
                            first.1.encode_ctx(ctx)?;
4✔
614

615
                            for parameter in tail {
4✔
616
                                ctx.write_all(b" ")?;
×
617
                                parameter.0.encode_ctx(ctx)?;
×
618
                                ctx.write_all(b" ")?;
×
619
                                parameter.1.encode_ctx(ctx)?;
×
620
                            }
621

622
                            ctx.write_all(b")")
4✔
623
                        } else {
624
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
625
                            {
626
                                ctx.write_all(b"()")
627
                            }
628
                            #[cfg(feature = "quirk_id_empty_to_nil")]
629
                            {
630
                                ctx.write_all(b"NIL")
×
631
                            }
632
                        }
633
                    }
634
                    None => ctx.write_all(b"NIL"),
4✔
635
                }
636
            }
637
            #[cfg(feature = "ext_metadata")]
638
            CommandBody::SetMetadata {
639
                mailbox,
6✔
640
                entry_values,
6✔
641
            } => {
6✔
642
                ctx.write_all(b"SETMETADATA ")?;
6✔
643
                mailbox.encode_ctx(ctx)?;
6✔
644
                ctx.write_all(b" (")?;
6✔
645
                join_serializable(entry_values.as_ref(), b" ", ctx)?;
6✔
646
                ctx.write_all(b")")
6✔
647
            }
648
            #[cfg(feature = "ext_metadata")]
649
            CommandBody::GetMetadata {
650
                options,
14✔
651
                mailbox,
14✔
652
                entries,
14✔
653
            } => {
14✔
654
                ctx.write_all(b"GETMETADATA")?;
14✔
655

656
                if !options.is_empty() {
14✔
657
                    ctx.write_all(b" (")?;
10✔
658
                    join_serializable(options, b" ", ctx)?;
10✔
659
                    ctx.write_all(b")")?;
10✔
660
                }
4✔
661

662
                ctx.write_all(b" ")?;
14✔
663
                mailbox.encode_ctx(ctx)?;
14✔
664

665
                ctx.write_all(b" ")?;
14✔
666

667
                if entries.as_ref().len() == 1 {
14✔
668
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
669
                } else {
670
                    ctx.write_all(b"(")?;
×
671
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
672
                    ctx.write_all(b")")
×
673
                }
674
            }
675
        }
676
    }
698✔
677
}
678

679
impl EncodeIntoContext for AuthMechanism<'_> {
680
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
22✔
681
        write!(ctx, "{}", self)
22✔
682
    }
22✔
683
}
684

685
impl EncodeIntoContext for AuthenticateData<'_> {
686
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
687
        match self {
8✔
688
            Self::Continue(data) => {
6✔
689
                let encoded = base64.encode(data.declassify());
6✔
690
                ctx.write_all(encoded.as_bytes())?;
6✔
691
                ctx.write_all(b"\r\n")
6✔
692
            }
693
            Self::Cancel => ctx.write_all(b"*\r\n"),
2✔
694
        }
695
    }
8✔
696
}
697

698
impl EncodeIntoContext for AString<'_> {
699
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
792✔
700
        match self {
792✔
701
            AString::Atom(atom) => atom.encode_ctx(ctx),
580✔
702
            AString::String(imap_str) => imap_str.encode_ctx(ctx),
212✔
703
        }
704
    }
792✔
705
}
706

707
impl EncodeIntoContext for Atom<'_> {
708
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
54✔
709
        ctx.write_all(self.inner().as_bytes())
54✔
710
    }
54✔
711
}
712

713
impl EncodeIntoContext for AtomExt<'_> {
714
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
580✔
715
        ctx.write_all(self.inner().as_bytes())
580✔
716
    }
580✔
717
}
718

719
impl EncodeIntoContext for IString<'_> {
720
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
516✔
721
        match self {
516✔
722
            Self::Literal(val) => val.encode_ctx(ctx),
44✔
723
            Self::Quoted(val) => val.encode_ctx(ctx),
472✔
724
        }
725
    }
516✔
726
}
727

728
impl EncodeIntoContext for Literal<'_> {
729
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
730
        match self.mode() {
44✔
731
            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
30✔
732
            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
14✔
733
        }
734

735
        ctx.push_line();
44✔
736
        ctx.write_all(self.as_ref())?;
44✔
737
        ctx.push_literal(self.mode());
44✔
738

44✔
739
        Ok(())
44✔
740
    }
44✔
741
}
742

743
impl EncodeIntoContext for Quoted<'_> {
744
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
480✔
745
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
480✔
746
    }
480✔
747
}
748

749
impl EncodeIntoContext for Mailbox<'_> {
750
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
616✔
751
        match self {
616✔
752
            Mailbox::Inbox => ctx.write_all(b"INBOX"),
80✔
753
            Mailbox::Other(other) => other.encode_ctx(ctx),
536✔
754
        }
755
    }
616✔
756
}
757

758
impl EncodeIntoContext for MailboxOther<'_> {
759
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
536✔
760
        self.inner().encode_ctx(ctx)
536✔
761
    }
536✔
762
}
763

764
impl EncodeIntoContext for ListMailbox<'_> {
765
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
766
        match self {
120✔
767
            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
80✔
768
            ListMailbox::String(istr) => istr.encode_ctx(ctx),
40✔
769
        }
770
    }
120✔
771
}
772

773
impl EncodeIntoContext for ListCharString<'_> {
774
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
80✔
775
        ctx.write_all(self.as_ref())
80✔
776
    }
80✔
777
}
778

779
impl EncodeIntoContext for StatusDataItemName {
780
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
36✔
781
        match self {
36✔
782
            Self::Messages => ctx.write_all(b"MESSAGES"),
12✔
783
            Self::Recent => ctx.write_all(b"RECENT"),
2✔
784
            Self::UidNext => ctx.write_all(b"UIDNEXT"),
10✔
785
            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
2✔
786
            Self::Unseen => ctx.write_all(b"UNSEEN"),
2✔
787
            Self::Deleted => ctx.write_all(b"DELETED"),
4✔
788
            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
4✔
789
            #[cfg(feature = "ext_condstore_qresync")]
790
            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
×
791
        }
792
    }
36✔
793
}
794

795
impl EncodeIntoContext for Flag<'_> {
796
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
312✔
797
        write!(ctx, "{}", self)
312✔
798
    }
312✔
799
}
800

801
impl EncodeIntoContext for FlagFetch<'_> {
802
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
803
        match self {
120✔
804
            Self::Flag(flag) => flag.encode_ctx(ctx),
120✔
805
            Self::Recent => ctx.write_all(b"\\Recent"),
×
806
        }
807
    }
120✔
808
}
809

810
impl EncodeIntoContext for FlagPerm<'_> {
811
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
24✔
812
        match self {
24✔
813
            Self::Flag(flag) => flag.encode_ctx(ctx),
16✔
814
            Self::Asterisk => ctx.write_all(b"\\*"),
8✔
815
        }
816
    }
24✔
817
}
818

819
impl EncodeIntoContext for DateTime {
820
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
821
        self.as_ref().encode_ctx(ctx)
14✔
822
    }
14✔
823
}
824

825
impl EncodeIntoContext for Charset<'_> {
826
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
58✔
827
        match self {
58✔
828
            Charset::Atom(atom) => atom.encode_ctx(ctx),
50✔
829
            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
8✔
830
        }
831
    }
58✔
832
}
833

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

943
impl EncodeIntoContext for SequenceSet {
944
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
88✔
945
        join_serializable(self.0.as_ref(), b",", ctx)
88✔
946
    }
88✔
947
}
948

949
impl EncodeIntoContext for Sequence {
950
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
94✔
951
        match self {
94✔
952
            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
38✔
953
            Sequence::Range(from, to) => {
56✔
954
                from.encode_ctx(ctx)?;
56✔
955
                ctx.write_all(b":")?;
56✔
956
                to.encode_ctx(ctx)
56✔
957
            }
958
        }
959
    }
94✔
960
}
961

962
impl EncodeIntoContext for SeqOrUid {
963
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
150✔
964
        match self {
150✔
965
            SeqOrUid::Value(number) => write!(ctx, "{number}"),
140✔
966
            SeqOrUid::Asterisk => ctx.write_all(b"*"),
10✔
967
        }
968
    }
150✔
969
}
970

971
impl EncodeIntoContext for NaiveDate {
972
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
973
        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
44✔
974
    }
44✔
975
}
976

977
impl EncodeIntoContext for MacroOrMessageDataItemNames<'_> {
978
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
979
        match self {
32✔
980
            Self::Macro(m) => m.encode_ctx(ctx),
8✔
981
            Self::MessageDataItemNames(item_names) => {
24✔
982
                if item_names.len() == 1 {
24✔
983
                    item_names[0].encode_ctx(ctx)
16✔
984
                } else {
985
                    ctx.write_all(b"(")?;
8✔
986
                    join_serializable(item_names.as_slice(), b" ", ctx)?;
8✔
987
                    ctx.write_all(b")")
8✔
988
                }
989
            }
990
        }
991
    }
32✔
992
}
993

994
impl EncodeIntoContext for Macro {
995
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
996
        write!(ctx, "{}", self)
8✔
997
    }
8✔
998
}
999

1000
impl EncodeIntoContext for MessageDataItemName<'_> {
1001
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
54✔
1002
        match self {
54✔
1003
            Self::Body => ctx.write_all(b"BODY"),
2✔
1004
            Self::BodyExt {
18✔
1005
                section,
18✔
1006
                partial,
18✔
1007
                peek,
18✔
1008
            } => {
18✔
1009
                if *peek {
18✔
1010
                    ctx.write_all(b"BODY.PEEK[")?;
×
1011
                } else {
1012
                    ctx.write_all(b"BODY[")?;
18✔
1013
                }
1014
                if let Some(section) = section {
18✔
1015
                    section.encode_ctx(ctx)?;
16✔
1016
                }
2✔
1017
                ctx.write_all(b"]")?;
18✔
1018
                if let Some((a, b)) = partial {
18✔
1019
                    write!(ctx, "<{a}.{b}>")?;
×
1020
                }
18✔
1021

1022
                Ok(())
18✔
1023
            }
1024
            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
2✔
1025
            Self::Envelope => ctx.write_all(b"ENVELOPE"),
2✔
1026
            Self::Flags => ctx.write_all(b"FLAGS"),
18✔
1027
            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
2✔
1028
            Self::Rfc822 => ctx.write_all(b"RFC822"),
2✔
1029
            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
2✔
1030
            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
2✔
1031
            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
2✔
1032
            Self::Uid => ctx.write_all(b"UID"),
2✔
1033
            MessageDataItemName::Binary {
1034
                section,
×
1035
                partial,
×
1036
                peek,
×
1037
            } => {
×
1038
                ctx.write_all(b"BINARY")?;
×
1039
                if *peek {
×
1040
                    ctx.write_all(b".PEEK")?;
×
1041
                }
×
1042

1043
                ctx.write_all(b"[")?;
×
1044
                join_serializable(section, b".", ctx)?;
×
1045
                ctx.write_all(b"]")?;
×
1046

1047
                if let Some((a, b)) = partial {
×
1048
                    ctx.write_all(b"<")?;
×
1049
                    a.encode_ctx(ctx)?;
×
1050
                    ctx.write_all(b".")?;
×
1051
                    b.encode_ctx(ctx)?;
×
1052
                    ctx.write_all(b">")?;
×
1053
                }
×
1054

1055
                Ok(())
×
1056
            }
1057
            MessageDataItemName::BinarySize { section } => {
×
1058
                ctx.write_all(b"BINARY.SIZE")?;
×
1059

1060
                ctx.write_all(b"[")?;
×
1061
                join_serializable(section, b".", ctx)?;
×
1062
                ctx.write_all(b"]")
×
1063
            }
1064
            #[cfg(feature = "ext_condstore_qresync")]
1065
            MessageDataItemName::ModSeq => ctx.write_all(b"MODSEQ"),
×
1066
        }
1067
    }
54✔
1068
}
1069

1070
impl EncodeIntoContext for Section<'_> {
1071
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
1072
        match self {
44✔
1073
            Section::Part(part) => part.encode_ctx(ctx),
2✔
1074
            Section::Header(maybe_part) => match maybe_part {
20✔
1075
                Some(part) => {
2✔
1076
                    part.encode_ctx(ctx)?;
2✔
1077
                    ctx.write_all(b".HEADER")
2✔
1078
                }
1079
                None => ctx.write_all(b"HEADER"),
18✔
1080
            },
1081
            Section::HeaderFields(maybe_part, header_list) => {
12✔
1082
                match maybe_part {
12✔
1083
                    Some(part) => {
2✔
1084
                        part.encode_ctx(ctx)?;
2✔
1085
                        ctx.write_all(b".HEADER.FIELDS (")?;
2✔
1086
                    }
1087
                    None => ctx.write_all(b"HEADER.FIELDS (")?,
10✔
1088
                };
1089
                join_serializable(header_list.as_ref(), b" ", ctx)?;
12✔
1090
                ctx.write_all(b")")
12✔
1091
            }
1092
            Section::HeaderFieldsNot(maybe_part, header_list) => {
4✔
1093
                match maybe_part {
4✔
1094
                    Some(part) => {
2✔
1095
                        part.encode_ctx(ctx)?;
2✔
1096
                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
2✔
1097
                    }
1098
                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
2✔
1099
                };
1100
                join_serializable(header_list.as_ref(), b" ", ctx)?;
4✔
1101
                ctx.write_all(b")")
4✔
1102
            }
1103
            Section::Text(maybe_part) => match maybe_part {
4✔
1104
                Some(part) => {
2✔
1105
                    part.encode_ctx(ctx)?;
2✔
1106
                    ctx.write_all(b".TEXT")
2✔
1107
                }
1108
                None => ctx.write_all(b"TEXT"),
2✔
1109
            },
1110
            Section::Mime(part) => {
2✔
1111
                part.encode_ctx(ctx)?;
2✔
1112
                ctx.write_all(b".MIME")
2✔
1113
            }
1114
        }
1115
    }
44✔
1116
}
1117

1118
impl EncodeIntoContext for Part {
1119
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
1120
        join_serializable(self.0.as_ref(), b".", ctx)
12✔
1121
    }
12✔
1122
}
1123

1124
impl EncodeIntoContext for NonZeroU32 {
1125
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
246✔
1126
        write!(ctx, "{self}")
246✔
1127
    }
246✔
1128
}
1129

1130
#[cfg(feature = "ext_condstore_qresync")]
1131
impl EncodeIntoContext for NonZeroU64 {
1132
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1133
        write!(ctx, "{self}")
×
1134
    }
×
1135
}
1136

1137
impl EncodeIntoContext for Capability<'_> {
1138
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
232✔
1139
        write!(ctx, "{}", self)
232✔
1140
    }
232✔
1141
}
1142

1143
// ----- Responses ---------------------------------------------------------------------------------
1144

1145
impl EncodeIntoContext for Response<'_> {
1146
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,548✔
1147
        match self {
1,548✔
1148
            Response::Status(status) => status.encode_ctx(ctx),
824✔
1149
            Response::Data(data) => data.encode_ctx(ctx),
716✔
1150
            Response::CommandContinuationRequest(continue_request) => {
8✔
1151
                continue_request.encode_ctx(ctx)
8✔
1152
            }
1153
        }
1154
    }
1,548✔
1155
}
1156

1157
impl EncodeIntoContext for Greeting<'_> {
1158
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1159
        ctx.write_all(b"* ")?;
26✔
1160
        self.kind.encode_ctx(ctx)?;
26✔
1161
        ctx.write_all(b" ")?;
26✔
1162

1163
        if let Some(ref code) = self.code {
26✔
1164
            ctx.write_all(b"[")?;
12✔
1165
            code.encode_ctx(ctx)?;
12✔
1166
            ctx.write_all(b"] ")?;
12✔
1167
        }
14✔
1168

1169
        self.text.encode_ctx(ctx)?;
26✔
1170
        ctx.write_all(b"\r\n")
26✔
1171
    }
26✔
1172
}
1173

1174
impl EncodeIntoContext for GreetingKind {
1175
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1176
        match self {
26✔
1177
            GreetingKind::Ok => ctx.write_all(b"OK"),
12✔
1178
            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
12✔
1179
            GreetingKind::Bye => ctx.write_all(b"BYE"),
2✔
1180
        }
1181
    }
26✔
1182
}
1183

1184
impl EncodeIntoContext for Status<'_> {
1185
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
824✔
1186
        fn format_status(
824✔
1187
            tag: Option<&Tag>,
824✔
1188
            status: &str,
824✔
1189
            code: &Option<Code>,
824✔
1190
            comment: &Text,
824✔
1191
            ctx: &mut EncodeContext,
824✔
1192
        ) -> std::io::Result<()> {
824✔
1193
            match tag {
824✔
1194
                Some(tag) => tag.encode_ctx(ctx)?,
606✔
1195
                None => ctx.write_all(b"*")?,
218✔
1196
            }
1197
            ctx.write_all(b" ")?;
824✔
1198
            ctx.write_all(status.as_bytes())?;
824✔
1199
            ctx.write_all(b" ")?;
824✔
1200
            if let Some(code) = code {
824✔
1201
                ctx.write_all(b"[")?;
148✔
1202
                code.encode_ctx(ctx)?;
148✔
1203
                ctx.write_all(b"] ")?;
148✔
1204
            }
676✔
1205
            comment.encode_ctx(ctx)?;
824✔
1206
            ctx.write_all(b"\r\n")
824✔
1207
        }
824✔
1208

1209
        match self {
824✔
1210
            Self::Untagged(StatusBody { kind, code, text }) => match kind {
192✔
1211
                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
134✔
1212
                StatusKind::No => format_status(None, "NO", code, text, ctx),
30✔
1213
                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
28✔
1214
            },
1215
            Self::Tagged(Tagged {
606✔
1216
                tag,
606✔
1217
                body: StatusBody { kind, code, text },
606✔
1218
            }) => match kind {
606✔
1219
                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
572✔
1220
                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
22✔
1221
                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
12✔
1222
            },
1223
            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
26✔
1224
        }
1225
    }
824✔
1226
}
1227

1228
impl EncodeIntoContext for Code<'_> {
1229
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
160✔
1230
        match self {
160✔
1231
            Code::Alert => ctx.write_all(b"ALERT"),
24✔
1232
            Code::BadCharset { allowed } => {
2✔
1233
                if allowed.is_empty() {
2✔
1234
                    ctx.write_all(b"BADCHARSET")
2✔
1235
                } else {
1236
                    ctx.write_all(b"BADCHARSET (")?;
×
1237
                    join_serializable(allowed, b" ", ctx)?;
×
1238
                    ctx.write_all(b")")
×
1239
                }
1240
            }
1241
            Code::Capability(caps) => {
4✔
1242
                ctx.write_all(b"CAPABILITY ")?;
4✔
1243
                join_serializable(caps.as_ref(), b" ", ctx)
4✔
1244
            }
1245
            Code::Parse => ctx.write_all(b"PARSE"),
×
1246
            Code::PermanentFlags(flags) => {
16✔
1247
                ctx.write_all(b"PERMANENTFLAGS (")?;
16✔
1248
                join_serializable(flags, b" ", ctx)?;
16✔
1249
                ctx.write_all(b")")
16✔
1250
            }
1251
            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
8✔
1252
            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
16✔
1253
            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
×
1254
            Code::UidNext(next) => {
16✔
1255
                ctx.write_all(b"UIDNEXT ")?;
16✔
1256
                next.encode_ctx(ctx)
16✔
1257
            }
1258
            Code::UidValidity(validity) => {
24✔
1259
                ctx.write_all(b"UIDVALIDITY ")?;
24✔
1260
                validity.encode_ctx(ctx)
24✔
1261
            }
1262
            Code::Unseen(seq) => {
28✔
1263
                ctx.write_all(b"UNSEEN ")?;
28✔
1264
                seq.encode_ctx(ctx)
28✔
1265
            }
1266
            // RFC 2221
1267
            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1268
            Code::Referral(url) => {
×
1269
                ctx.write_all(b"REFERRAL ")?;
×
1270
                ctx.write_all(url.as_bytes())
×
1271
            }
1272
            // RFC 4551
1273
            #[cfg(feature = "ext_condstore_qresync")]
1274
            Code::HighestModSeq(modseq) => {
×
1275
                ctx.write_all(b"HIGHESTMODSEQ ")?;
×
1276
                modseq.encode_ctx(ctx)
×
1277
            }
1278
            #[cfg(feature = "ext_condstore_qresync")]
1279
            Code::NoModSeq => ctx.write_all(b"NOMODSEQ"),
×
1280
            #[cfg(feature = "ext_condstore_qresync")]
1281
            Code::Modified(sequence_set) => {
×
1282
                ctx.write_all(b"MODIFIED ")?;
×
1283
                sequence_set.encode_ctx(ctx)
×
1284
            }
1285
            #[cfg(feature = "ext_condstore_qresync")]
1286
            Code::Closed => ctx.write_all(b"CLOSED"),
×
1287
            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
×
1288
            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
4✔
1289
            Code::TooBig => ctx.write_all(b"TOOBIG"),
×
1290
            #[cfg(feature = "ext_metadata")]
1291
            Code::Metadata(code) => {
12✔
1292
                ctx.write_all(b"METADATA ")?;
12✔
1293
                code.encode_ctx(ctx)
12✔
1294
            }
1295
            Code::UnknownCte => ctx.write_all(b"UNKNOWN-CTE"),
×
1296
            Code::AppendUid { uid_validity, uid } => {
2✔
1297
                ctx.write_all(b"APPENDUID ")?;
2✔
1298
                uid_validity.encode_ctx(ctx)?;
2✔
1299
                ctx.write_all(b" ")?;
2✔
1300
                uid.encode_ctx(ctx)
2✔
1301
            }
1302
            Code::CopyUid {
1303
                uid_validity,
2✔
1304
                source,
2✔
1305
                destination,
2✔
1306
            } => {
2✔
1307
                ctx.write_all(b"COPYUID ")?;
2✔
1308
                uid_validity.encode_ctx(ctx)?;
2✔
1309
                ctx.write_all(b" ")?;
2✔
1310
                source.encode_ctx(ctx)?;
2✔
1311
                ctx.write_all(b" ")?;
2✔
1312
                destination.encode_ctx(ctx)
2✔
1313
            }
1314
            Code::UidNotSticky => ctx.write_all(b"UIDNOTSTICKY"),
2✔
1315
            Code::Other(unknown) => unknown.encode_ctx(ctx),
×
1316
        }
1317
    }
160✔
1318
}
1319

1320
impl EncodeIntoContext for CodeOther<'_> {
1321
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1322
        ctx.write_all(self.inner())
×
1323
    }
×
1324
}
1325

1326
impl EncodeIntoContext for Text<'_> {
1327
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
858✔
1328
        ctx.write_all(self.inner().as_bytes())
858✔
1329
    }
858✔
1330
}
1331

1332
impl EncodeIntoContext for Data<'_> {
1333
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
716✔
1334
        match self {
716✔
1335
            Data::Capability(caps) => {
64✔
1336
                ctx.write_all(b"* CAPABILITY ")?;
64✔
1337
                join_serializable(caps.as_ref(), b" ", ctx)?;
64✔
1338
            }
1339
            Data::List {
1340
                items,
210✔
1341
                delimiter,
210✔
1342
                mailbox,
210✔
1343
            } => {
210✔
1344
                ctx.write_all(b"* LIST (")?;
210✔
1345
                join_serializable(items, b" ", ctx)?;
210✔
1346
                ctx.write_all(b") ")?;
210✔
1347

1348
                if let Some(delimiter) = delimiter {
210✔
1349
                    ctx.write_all(b"\"")?;
210✔
1350
                    delimiter.encode_ctx(ctx)?;
210✔
1351
                    ctx.write_all(b"\"")?;
210✔
1352
                } else {
1353
                    ctx.write_all(b"NIL")?;
×
1354
                }
1355
                ctx.write_all(b" ")?;
210✔
1356
                mailbox.encode_ctx(ctx)?;
210✔
1357
            }
1358
            Data::Lsub {
1359
                items,
32✔
1360
                delimiter,
32✔
1361
                mailbox,
32✔
1362
            } => {
32✔
1363
                ctx.write_all(b"* LSUB (")?;
32✔
1364
                join_serializable(items, b" ", ctx)?;
32✔
1365
                ctx.write_all(b") ")?;
32✔
1366

1367
                if let Some(delimiter) = delimiter {
32✔
1368
                    ctx.write_all(b"\"")?;
32✔
1369
                    delimiter.encode_ctx(ctx)?;
32✔
1370
                    ctx.write_all(b"\"")?;
32✔
1371
                } else {
1372
                    ctx.write_all(b"NIL")?;
×
1373
                }
1374
                ctx.write_all(b" ")?;
32✔
1375
                mailbox.encode_ctx(ctx)?;
32✔
1376
            }
1377
            Data::Status { mailbox, items } => {
18✔
1378
                ctx.write_all(b"* STATUS ")?;
18✔
1379
                mailbox.encode_ctx(ctx)?;
18✔
1380
                ctx.write_all(b" (")?;
18✔
1381
                join_serializable(items, b" ", ctx)?;
18✔
1382
                ctx.write_all(b")")?;
18✔
1383
            }
1384
            Data::Search(seqs) => {
38✔
1385
                if seqs.is_empty() {
38✔
1386
                    ctx.write_all(b"* SEARCH")?;
8✔
1387
                } else {
1388
                    ctx.write_all(b"* SEARCH ")?;
30✔
1389
                    join_serializable(seqs, b" ", ctx)?;
30✔
1390
                }
1391
            }
1392
            Data::Sort(seqs) => {
24✔
1393
                if seqs.is_empty() {
24✔
1394
                    ctx.write_all(b"* SORT")?;
8✔
1395
                } else {
1396
                    ctx.write_all(b"* SORT ")?;
16✔
1397
                    join_serializable(seqs, b" ", ctx)?;
16✔
1398
                }
1399
            }
1400
            Data::Thread(threads) => {
24✔
1401
                if threads.is_empty() {
24✔
1402
                    ctx.write_all(b"* THREAD")?;
8✔
1403
                } else {
1404
                    ctx.write_all(b"* THREAD ")?;
16✔
1405
                    for thread in threads {
400✔
1406
                        thread.encode_ctx(ctx)?;
384✔
1407
                    }
1408
                }
1409
            }
1410
            Data::Flags(flags) => {
32✔
1411
                ctx.write_all(b"* FLAGS (")?;
32✔
1412
                join_serializable(flags, b" ", ctx)?;
32✔
1413
                ctx.write_all(b")")?;
32✔
1414
            }
1415
            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
42✔
1416
            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
42✔
1417
            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
50✔
1418
            Data::Fetch { seq, items } => {
96✔
1419
                write!(ctx, "* {seq} FETCH (")?;
96✔
1420
                join_serializable(items.as_ref(), b" ", ctx)?;
96✔
1421
                ctx.write_all(b")")?;
96✔
1422
            }
1423
            Data::Enabled { capabilities } => {
16✔
1424
                write!(ctx, "* ENABLED")?;
16✔
1425

1426
                for cap in capabilities {
32✔
1427
                    ctx.write_all(b" ")?;
16✔
1428
                    cap.encode_ctx(ctx)?;
16✔
1429
                }
1430
            }
1431
            Data::Quota { root, quotas } => {
12✔
1432
                ctx.write_all(b"* QUOTA ")?;
12✔
1433
                root.encode_ctx(ctx)?;
12✔
1434
                ctx.write_all(b" (")?;
12✔
1435
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
1436
                ctx.write_all(b")")?;
12✔
1437
            }
1438
            Data::QuotaRoot { mailbox, roots } => {
10✔
1439
                ctx.write_all(b"* QUOTAROOT ")?;
10✔
1440
                mailbox.encode_ctx(ctx)?;
10✔
1441
                for root in roots {
20✔
1442
                    ctx.write_all(b" ")?;
10✔
1443
                    root.encode_ctx(ctx)?;
10✔
1444
                }
1445
            }
1446
            #[cfg(feature = "ext_id")]
1447
            Data::Id { parameters } => {
2✔
1448
                ctx.write_all(b"* ID ")?;
2✔
1449

1450
                match parameters {
2✔
1451
                    Some(parameters) => {
×
1452
                        if let Some((first, tail)) = parameters.split_first() {
×
1453
                            ctx.write_all(b"(")?;
×
1454

1455
                            first.0.encode_ctx(ctx)?;
×
1456
                            ctx.write_all(b" ")?;
×
1457
                            first.1.encode_ctx(ctx)?;
×
1458

1459
                            for parameter in tail {
×
1460
                                ctx.write_all(b" ")?;
×
1461
                                parameter.0.encode_ctx(ctx)?;
×
1462
                                ctx.write_all(b" ")?;
×
1463
                                parameter.1.encode_ctx(ctx)?;
×
1464
                            }
1465

1466
                            ctx.write_all(b")")?;
×
1467
                        } else {
1468
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
1469
                            {
1470
                                ctx.write_all(b"()")?;
1471
                            }
1472
                            #[cfg(feature = "quirk_id_empty_to_nil")]
1473
                            {
1474
                                ctx.write_all(b"NIL")?;
×
1475
                            }
1476
                        }
1477
                    }
1478
                    None => {
1479
                        ctx.write_all(b"NIL")?;
2✔
1480
                    }
1481
                }
1482
            }
1483
            #[cfg(feature = "ext_metadata")]
1484
            Data::Metadata { mailbox, items } => {
4✔
1485
                ctx.write_all(b"* METADATA ")?;
4✔
1486
                mailbox.encode_ctx(ctx)?;
4✔
1487
                ctx.write_all(b" ")?;
4✔
1488
                items.encode_ctx(ctx)?;
4✔
1489
            }
1490
        }
1491

1492
        ctx.write_all(b"\r\n")
716✔
1493
    }
716✔
1494
}
1495

1496
impl EncodeIntoContext for FlagNameAttribute<'_> {
1497
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
1498
        write!(ctx, "{}", self)
90✔
1499
    }
90✔
1500
}
1501

1502
impl EncodeIntoContext for QuotedChar {
1503
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
242✔
1504
        match self.inner() {
242✔
1505
            '\\' => ctx.write_all(b"\\\\"),
×
1506
            '"' => ctx.write_all(b"\\\""),
×
1507
            other => ctx.write_all(&[other as u8]),
242✔
1508
        }
1509
    }
242✔
1510
}
1511

1512
impl EncodeIntoContext for StatusDataItem {
1513
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
52✔
1514
        match self {
52✔
1515
            Self::Messages(count) => {
20✔
1516
                ctx.write_all(b"MESSAGES ")?;
20✔
1517
                count.encode_ctx(ctx)
20✔
1518
            }
1519
            Self::Recent(count) => {
2✔
1520
                ctx.write_all(b"RECENT ")?;
2✔
1521
                count.encode_ctx(ctx)
2✔
1522
            }
1523
            Self::UidNext(next) => {
18✔
1524
                ctx.write_all(b"UIDNEXT ")?;
18✔
1525
                next.encode_ctx(ctx)
18✔
1526
            }
1527
            Self::UidValidity(identifier) => {
2✔
1528
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1529
                identifier.encode_ctx(ctx)
2✔
1530
            }
1531
            Self::Unseen(count) => {
2✔
1532
                ctx.write_all(b"UNSEEN ")?;
2✔
1533
                count.encode_ctx(ctx)
2✔
1534
            }
1535
            Self::Deleted(count) => {
4✔
1536
                ctx.write_all(b"DELETED ")?;
4✔
1537
                count.encode_ctx(ctx)
4✔
1538
            }
1539
            Self::DeletedStorage(count) => {
4✔
1540
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1541
                count.encode_ctx(ctx)
4✔
1542
            }
1543
            #[cfg(feature = "ext_condstore_qresync")]
1544
            Self::HighestModSeq(value) => {
×
1545
                ctx.write_all(b"HIGHESTMODSEQ ")?;
×
1546
                value.encode_ctx(ctx)
×
1547
            }
1548
        }
1549
    }
52✔
1550
}
1551

1552
impl EncodeIntoContext for MessageDataItem<'_> {
1553
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
184✔
1554
        match self {
184✔
1555
            Self::BodyExt {
16✔
1556
                section,
16✔
1557
                origin,
16✔
1558
                data,
16✔
1559
            } => {
16✔
1560
                ctx.write_all(b"BODY[")?;
16✔
1561
                if let Some(section) = section {
16✔
1562
                    section.encode_ctx(ctx)?;
8✔
1563
                }
8✔
1564
                ctx.write_all(b"]")?;
16✔
1565
                if let Some(origin) = origin {
16✔
1566
                    write!(ctx, "<{origin}>")?;
2✔
1567
                }
14✔
1568
                ctx.write_all(b" ")?;
16✔
1569
                data.encode_ctx(ctx)
16✔
1570
            }
1571
            // FIXME: do not return body-ext-1part and body-ext-mpart here
1572
            Self::Body(body) => {
10✔
1573
                ctx.write_all(b"BODY ")?;
10✔
1574
                body.encode_ctx(ctx)
10✔
1575
            }
1576
            Self::BodyStructure(body) => {
4✔
1577
                ctx.write_all(b"BODYSTRUCTURE ")?;
4✔
1578
                body.encode_ctx(ctx)
4✔
1579
            }
1580
            Self::Envelope(envelope) => {
10✔
1581
                ctx.write_all(b"ENVELOPE ")?;
10✔
1582
                envelope.encode_ctx(ctx)
10✔
1583
            }
1584
            Self::Flags(flags) => {
82✔
1585
                ctx.write_all(b"FLAGS (")?;
82✔
1586
                join_serializable(flags, b" ", ctx)?;
82✔
1587
                ctx.write_all(b")")
82✔
1588
            }
1589
            Self::InternalDate(datetime) => {
10✔
1590
                ctx.write_all(b"INTERNALDATE ")?;
10✔
1591
                datetime.encode_ctx(ctx)
10✔
1592
            }
1593
            Self::Rfc822(nstring) => {
4✔
1594
                ctx.write_all(b"RFC822 ")?;
4✔
1595
                nstring.encode_ctx(ctx)
4✔
1596
            }
1597
            Self::Rfc822Header(nstring) => {
2✔
1598
                ctx.write_all(b"RFC822.HEADER ")?;
2✔
1599
                nstring.encode_ctx(ctx)
2✔
1600
            }
1601
            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
18✔
1602
            Self::Rfc822Text(nstring) => {
2✔
1603
                ctx.write_all(b"RFC822.TEXT ")?;
2✔
1604
                nstring.encode_ctx(ctx)
2✔
1605
            }
1606
            Self::Uid(uid) => write!(ctx, "UID {uid}"),
26✔
1607
            Self::Binary { section, value } => {
×
1608
                ctx.write_all(b"BINARY[")?;
×
1609
                join_serializable(section, b".", ctx)?;
×
1610
                ctx.write_all(b"] ")?;
×
1611
                value.encode_ctx(ctx)
×
1612
            }
1613
            Self::BinarySize { section, size } => {
×
1614
                ctx.write_all(b"BINARY.SIZE[")?;
×
1615
                join_serializable(section, b".", ctx)?;
×
1616
                ctx.write_all(b"] ")?;
×
1617
                size.encode_ctx(ctx)
×
1618
            }
1619
            #[cfg(feature = "ext_condstore_qresync")]
1620
            Self::ModSeq(value) => write!(ctx, "MODSEQ {value}"),
×
1621
        }
1622
    }
184✔
1623
}
1624

1625
impl EncodeIntoContext for NString<'_> {
1626
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
318✔
1627
        match &self.0 {
318✔
1628
            Some(imap_str) => imap_str.encode_ctx(ctx),
188✔
1629
            None => ctx.write_all(b"NIL"),
130✔
1630
        }
1631
    }
318✔
1632
}
1633

1634
impl EncodeIntoContext for NString8<'_> {
1635
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1636
        match self {
8✔
1637
            NString8::NString(nstring) => nstring.encode_ctx(ctx),
6✔
1638
            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
2✔
1639
        }
1640
    }
8✔
1641
}
1642

1643
impl EncodeIntoContext for BodyStructure<'_> {
1644
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1645
        ctx.write_all(b"(")?;
32✔
1646
        match self {
32✔
1647
            BodyStructure::Single {
1648
                body,
20✔
1649
                extension_data: extension,
20✔
1650
            } => {
20✔
1651
                body.encode_ctx(ctx)?;
20✔
1652
                if let Some(extension) = extension {
20✔
1653
                    ctx.write_all(b" ")?;
4✔
1654
                    extension.encode_ctx(ctx)?;
4✔
1655
                }
16✔
1656
            }
1657
            BodyStructure::Multi {
1658
                bodies,
12✔
1659
                subtype,
12✔
1660
                extension_data,
12✔
1661
            } => {
1662
                for body in bodies.as_ref() {
12✔
1663
                    body.encode_ctx(ctx)?;
12✔
1664
                }
1665
                ctx.write_all(b" ")?;
12✔
1666
                subtype.encode_ctx(ctx)?;
12✔
1667

1668
                if let Some(extension) = extension_data {
12✔
1669
                    ctx.write_all(b" ")?;
×
1670
                    extension.encode_ctx(ctx)?;
×
1671
                }
12✔
1672
            }
1673
        }
1674
        ctx.write_all(b")")
32✔
1675
    }
32✔
1676
}
1677

1678
impl EncodeIntoContext for Body<'_> {
1679
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1680
        match self.specific {
20✔
1681
            SpecificFields::Basic {
1682
                r#type: ref type_,
4✔
1683
                ref subtype,
4✔
1684
            } => {
4✔
1685
                type_.encode_ctx(ctx)?;
4✔
1686
                ctx.write_all(b" ")?;
4✔
1687
                subtype.encode_ctx(ctx)?;
4✔
1688
                ctx.write_all(b" ")?;
4✔
1689
                self.basic.encode_ctx(ctx)
4✔
1690
            }
1691
            SpecificFields::Message {
1692
                ref envelope,
×
1693
                ref body_structure,
×
1694
                number_of_lines,
×
1695
            } => {
×
1696
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
1697
                self.basic.encode_ctx(ctx)?;
×
1698
                ctx.write_all(b" ")?;
×
1699
                envelope.encode_ctx(ctx)?;
×
1700
                ctx.write_all(b" ")?;
×
1701
                body_structure.encode_ctx(ctx)?;
×
1702
                ctx.write_all(b" ")?;
×
1703
                write!(ctx, "{number_of_lines}")
×
1704
            }
1705
            SpecificFields::Text {
1706
                ref subtype,
16✔
1707
                number_of_lines,
16✔
1708
            } => {
16✔
1709
                ctx.write_all(b"\"TEXT\" ")?;
16✔
1710
                subtype.encode_ctx(ctx)?;
16✔
1711
                ctx.write_all(b" ")?;
16✔
1712
                self.basic.encode_ctx(ctx)?;
16✔
1713
                ctx.write_all(b" ")?;
16✔
1714
                write!(ctx, "{number_of_lines}")
16✔
1715
            }
1716
        }
1717
    }
20✔
1718
}
1719

1720
impl EncodeIntoContext for BasicFields<'_> {
1721
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1722
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
20✔
1723
        ctx.write_all(b" ")?;
20✔
1724
        self.id.encode_ctx(ctx)?;
20✔
1725
        ctx.write_all(b" ")?;
20✔
1726
        self.description.encode_ctx(ctx)?;
20✔
1727
        ctx.write_all(b" ")?;
20✔
1728
        self.content_transfer_encoding.encode_ctx(ctx)?;
20✔
1729
        ctx.write_all(b" ")?;
20✔
1730
        write!(ctx, "{}", self.size)
20✔
1731
    }
20✔
1732
}
1733

1734
impl EncodeIntoContext for Envelope<'_> {
1735
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10✔
1736
        ctx.write_all(b"(")?;
10✔
1737
        self.date.encode_ctx(ctx)?;
10✔
1738
        ctx.write_all(b" ")?;
10✔
1739
        self.subject.encode_ctx(ctx)?;
10✔
1740
        ctx.write_all(b" ")?;
10✔
1741
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
10✔
1742
        ctx.write_all(b" ")?;
10✔
1743
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
10✔
1744
        ctx.write_all(b" ")?;
10✔
1745
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
10✔
1746
        ctx.write_all(b" ")?;
10✔
1747
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
10✔
1748
        ctx.write_all(b" ")?;
10✔
1749
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
10✔
1750
        ctx.write_all(b" ")?;
10✔
1751
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
10✔
1752
        ctx.write_all(b" ")?;
10✔
1753
        self.in_reply_to.encode_ctx(ctx)?;
10✔
1754
        ctx.write_all(b" ")?;
10✔
1755
        self.message_id.encode_ctx(ctx)?;
10✔
1756
        ctx.write_all(b")")
10✔
1757
    }
10✔
1758
}
1759

1760
impl EncodeIntoContext for Address<'_> {
1761
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
1762
        ctx.write_all(b"(")?;
48✔
1763
        self.name.encode_ctx(ctx)?;
48✔
1764
        ctx.write_all(b" ")?;
48✔
1765
        self.adl.encode_ctx(ctx)?;
48✔
1766
        ctx.write_all(b" ")?;
48✔
1767
        self.mailbox.encode_ctx(ctx)?;
48✔
1768
        ctx.write_all(b" ")?;
48✔
1769
        self.host.encode_ctx(ctx)?;
48✔
1770
        ctx.write_all(b")")?;
48✔
1771

1772
        Ok(())
48✔
1773
    }
48✔
1774
}
1775

1776
impl EncodeIntoContext for SinglePartExtensionData<'_> {
1777
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1778
        self.md5.encode_ctx(ctx)?;
6✔
1779

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

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

1789
impl EncodeIntoContext for MultiPartExtensionData<'_> {
1790
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1791
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1792

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

1798
        Ok(())
×
1799
    }
×
1800
}
1801

1802
impl EncodeIntoContext for Disposition<'_> {
1803
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1804
        match &self.disposition {
6✔
1805
            Some((s, param)) => {
×
1806
                ctx.write_all(b"(")?;
×
1807
                s.encode_ctx(ctx)?;
×
1808
                ctx.write_all(b" ")?;
×
1809
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1810
                ctx.write_all(b")")?;
×
1811
            }
1812
            None => ctx.write_all(b"NIL")?,
6✔
1813
        }
1814

1815
        if let Some(language) = &self.tail {
6✔
1816
            ctx.write_all(b" ")?;
6✔
1817
            language.encode_ctx(ctx)?;
6✔
1818
        }
×
1819

1820
        Ok(())
6✔
1821
    }
6✔
1822
}
1823

1824
impl EncodeIntoContext for Language<'_> {
1825
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1826
        List1OrNil(&self.language, b" ").encode_ctx(ctx)?;
6✔
1827

1828
        if let Some(location) = &self.tail {
6✔
1829
            ctx.write_all(b" ")?;
6✔
1830
            location.encode_ctx(ctx)?;
6✔
1831
        }
×
1832

1833
        Ok(())
6✔
1834
    }
6✔
1835
}
1836

1837
impl EncodeIntoContext for Location<'_> {
1838
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1839
        self.location.encode_ctx(ctx)?;
6✔
1840

1841
        for body_extension in &self.extensions {
10✔
1842
            ctx.write_all(b" ")?;
4✔
1843
            body_extension.encode_ctx(ctx)?;
4✔
1844
        }
1845

1846
        Ok(())
6✔
1847
    }
6✔
1848
}
1849

1850
impl EncodeIntoContext for BodyExtension<'_> {
1851
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1852
        match self {
6✔
1853
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
1854
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
1855
            BodyExtension::List(list) => {
2✔
1856
                ctx.write_all(b"(")?;
2✔
1857
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
1858
                ctx.write_all(b")")
2✔
1859
            }
1860
        }
1861
    }
6✔
1862
}
1863

1864
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
1865
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
1866
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
14✔
1867
    }
14✔
1868
}
1869

1870
impl EncodeIntoContext for CommandContinuationRequest<'_> {
1871
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1872
        match self {
8✔
1873
            Self::Basic(continue_basic) => match continue_basic.code() {
8✔
1874
                Some(code) => {
×
1875
                    ctx.write_all(b"+ [")?;
×
1876
                    code.encode_ctx(ctx)?;
×
1877
                    ctx.write_all(b"] ")?;
×
1878
                    continue_basic.text().encode_ctx(ctx)?;
×
1879
                    ctx.write_all(b"\r\n")
×
1880
                }
1881
                None => {
1882
                    ctx.write_all(b"+ ")?;
8✔
1883
                    continue_basic.text().encode_ctx(ctx)?;
8✔
1884
                    ctx.write_all(b"\r\n")
8✔
1885
                }
1886
            },
1887
            Self::Base64(data) => {
×
1888
                ctx.write_all(b"+ ")?;
×
1889
                ctx.write_all(base64.encode(data).as_bytes())?;
×
1890
                ctx.write_all(b"\r\n")
×
1891
            }
1892
        }
1893
    }
8✔
1894
}
1895

1896
pub(crate) mod utils {
1897
    use std::io::Write;
1898

1899
    use super::{EncodeContext, EncodeIntoContext};
1900

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

1903
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
1904

1905
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
914✔
1906
        elements: &[I],
914✔
1907
        sep: &[u8],
914✔
1908
        ctx: &mut EncodeContext,
914✔
1909
    ) -> std::io::Result<()> {
914✔
1910
        if let Some((last, head)) = elements.split_last() {
914✔
1911
            for item in head {
1,342✔
1912
                item.encode_ctx(ctx)?;
592✔
1913
                ctx.write_all(sep)?;
592✔
1914
            }
1915

1916
            last.encode_ctx(ctx)
750✔
1917
        } else {
1918
            Ok(())
164✔
1919
        }
1920
    }
914✔
1921

1922
    impl<T> EncodeIntoContext for List1OrNil<'_, T>
1923
    where
1924
        T: EncodeIntoContext,
1925
    {
1926
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
66✔
1927
            if let Some((last, head)) = self.0.split_last() {
66✔
1928
                ctx.write_all(b"(")?;
40✔
1929

1930
                for item in head {
48✔
1931
                    item.encode_ctx(ctx)?;
8✔
1932
                    ctx.write_all(self.1)?;
8✔
1933
                }
1934

1935
                last.encode_ctx(ctx)?;
40✔
1936

1937
                ctx.write_all(b")")
40✔
1938
            } else {
1939
                ctx.write_all(b"NIL")
26✔
1940
            }
1941
        }
66✔
1942
    }
1943

1944
    impl<T> EncodeIntoContext for List1AttributeValueOrNil<'_, T>
1945
    where
1946
        T: EncodeIntoContext,
1947
    {
1948
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1949
            if let Some((last, head)) = self.0.split_last() {
20✔
1950
                ctx.write_all(b"(")?;
8✔
1951

1952
                for (attribute, value) in head {
8✔
1953
                    attribute.encode_ctx(ctx)?;
×
1954
                    ctx.write_all(b" ")?;
×
1955
                    value.encode_ctx(ctx)?;
×
1956
                    ctx.write_all(b" ")?;
×
1957
                }
1958

1959
                let (attribute, value) = last;
8✔
1960
                attribute.encode_ctx(ctx)?;
8✔
1961
                ctx.write_all(b" ")?;
8✔
1962
                value.encode_ctx(ctx)?;
8✔
1963

1964
                ctx.write_all(b")")
8✔
1965
            } else {
1966
                ctx.write_all(b"NIL")
12✔
1967
            }
1968
        }
20✔
1969
    }
1970
}
1971

1972
#[cfg(test)]
1973
mod tests {
1974
    use std::num::NonZeroU32;
1975

1976
    use imap_types::{
1977
        auth::AuthMechanism,
1978
        command::{Command, CommandBody},
1979
        core::{AString, Literal, NString, Vec1},
1980
        fetch::MessageDataItem,
1981
        response::{Data, Response},
1982
        utils::escape_byte_string,
1983
    };
1984

1985
    use super::*;
1986

1987
    #[test]
1988
    fn test_api_encoder_usage() {
2✔
1989
        let cmd = Command::new(
2✔
1990
            "A",
2✔
1991
            CommandBody::login(
2✔
1992
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
1993
                "password",
2✔
1994
            )
2✔
1995
            .unwrap(),
2✔
1996
        )
2✔
1997
        .unwrap();
2✔
1998

2✔
1999
        // Dump.
2✔
2000
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
2001

2✔
2002
        // Encoded.
2✔
2003
        let encoded = CommandCodec::default().encode(&cmd);
2✔
2004

2✔
2005
        let mut out = Vec::new();
2✔
2006

2007
        for x in encoded {
8✔
2008
            match x {
6✔
2009
                Fragment::Line { data } => {
4✔
2010
                    println!("C: {}", escape_byte_string(&data));
4✔
2011
                    out.extend_from_slice(&data);
4✔
2012
                }
4✔
2013
                Fragment::Literal { data, mode } => {
2✔
2014
                    match mode {
2✔
2015
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
2016
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
2017
                    }
2018

2019
                    println!("C: {}", escape_byte_string(&data));
2✔
2020
                    out.extend_from_slice(&data);
2✔
2021
                }
2022
            }
2023
        }
2024

2025
        assert_eq!(got_encoded, out);
2✔
2026
    }
2✔
2027

2028
    #[test]
2029
    fn test_encode_command() {
2✔
2030
        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2✔
2031
            (
2✔
2032
                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2✔
2033
                [Fragment::Line {
2✔
2034
                    data: b"A LOGIN alice pass\r\n".to_vec(),
2✔
2035
                }]
2✔
2036
                .as_ref(),
2✔
2037
            ),
2✔
2038
            (
2✔
2039
                Command::new(
2✔
2040
                    "A",
2✔
2041
                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2✔
2042
                )
2✔
2043
                .unwrap(),
2✔
2044
                [
2✔
2045
                    Fragment::Line {
2✔
2046
                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
2047
                    },
2✔
2048
                    Fragment::Literal {
2✔
2049
                        data: b"\xCA\xFE".to_vec(),
2✔
2050
                        mode: LiteralMode::Sync,
2✔
2051
                    },
2✔
2052
                    Fragment::Line {
2✔
2053
                        data: b"\r\n".to_vec(),
2✔
2054
                    },
2✔
2055
                ]
2✔
2056
                .as_ref(),
2✔
2057
            ),
2✔
2058
            (
2✔
2059
                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2✔
2060
                [Fragment::Line {
2✔
2061
                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2✔
2062
                }]
2✔
2063
                .as_ref(),
2✔
2064
            ),
2✔
2065
            (
2✔
2066
                Command::new(
2✔
2067
                    "A",
2✔
2068
                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2✔
2069
                )
2✔
2070
                .unwrap(),
2✔
2071
                [Fragment::Line {
2✔
2072
                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2✔
2073
                }]
2✔
2074
                .as_ref(),
2✔
2075
            ),
2✔
2076
            (
2✔
2077
                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2✔
2078
                [Fragment::Line {
2✔
2079
                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2✔
2080
                }]
2✔
2081
                .as_ref(),
2✔
2082
            ),
2✔
2083
            (
2✔
2084
                Command::new(
2✔
2085
                    "A",
2✔
2086
                    CommandBody::authenticate_with_ir(
2✔
2087
                        AuthMechanism::Plain,
2✔
2088
                        b"\x00alice\x00pass".as_ref(),
2✔
2089
                    ),
2✔
2090
                )
2✔
2091
                .unwrap(),
2✔
2092
                [Fragment::Line {
2✔
2093
                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2✔
2094
                }]
2✔
2095
                .as_ref(),
2✔
2096
            ),
2✔
2097
        ]);
2✔
2098
    }
2✔
2099

2100
    #[test]
2101
    fn test_encode_response() {
2✔
2102
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
2103
            (
2✔
2104
                Response::Data(Data::Fetch {
2✔
2105
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2106
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2107
                        section: None,
2✔
2108
                        origin: None,
2✔
2109
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
2110
                    }),
2✔
2111
                }),
2✔
2112
                [
2✔
2113
                    Fragment::Line {
2✔
2114
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
2115
                    },
2✔
2116
                    Fragment::Literal {
2✔
2117
                        data: b"ABCDE".to_vec(),
2✔
2118
                        mode: LiteralMode::Sync,
2✔
2119
                    },
2✔
2120
                    Fragment::Line {
2✔
2121
                        data: b")\r\n".to_vec(),
2✔
2122
                    },
2✔
2123
                ]
2✔
2124
                .as_ref(),
2✔
2125
            ),
2✔
2126
            (
2✔
2127
                Response::Data(Data::Fetch {
2✔
2128
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2129
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2130
                        section: None,
2✔
2131
                        origin: None,
2✔
2132
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
2133
                    }),
2✔
2134
                }),
2✔
2135
                [
2✔
2136
                    Fragment::Line {
2✔
2137
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
2138
                    },
2✔
2139
                    Fragment::Literal {
2✔
2140
                        data: b"ABCDE".to_vec(),
2✔
2141
                        mode: LiteralMode::NonSync,
2✔
2142
                    },
2✔
2143
                    Fragment::Line {
2✔
2144
                        data: b")\r\n".to_vec(),
2✔
2145
                    },
2✔
2146
                ]
2✔
2147
                .as_ref(),
2✔
2148
            ),
2✔
2149
        ])
2✔
2150
    }
2✔
2151

2152
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
2153
    where
4✔
2154
        E: Encoder<Message<'a> = M> + Default,
4✔
2155
        F: AsRef<[Fragment]>,
4✔
2156
    {
4✔
2157
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
2158
            println!("# Testing {i}");
16✔
2159

16✔
2160
            let encoder = E::default().encode(obj);
16✔
2161
            let actions = actions.as_ref();
16✔
2162

16✔
2163
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2164
        }
2165
    }
4✔
2166
}
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

© 2025 Coveralls, Inc