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

duesee / imap-codec / 18635078135

19 Oct 2025 07:30PM UTC coverage: 90.848% (-0.9%) from 91.776%
18635078135

Pull #669

github

web-flow
Merge 9e771a7bb into 6b76cfe16
Pull Request #669: feat: Implement RFC 2342 (NAMESPACE) in imap-codec

15 of 129 new or added lines in 6 files covered. (11.63%)

8 existing lines in 1 file now uncovered.

10324 of 11364 relevant lines covered (90.85%)

930.83 hits per line

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

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

86
#[cfg(feature = "ext_namespace")]
87
use crate::extensions::namespace::encode_namespaces;
88
use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
89

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

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

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

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

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

144
        out
2,028✔
145
    }
2,028✔
146
}
147

148
impl Iterator for Encoded {
149
    type Item = Fragment;
150

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

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

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

166
//--------------------------------------------------------------------------------------------------
167

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

174
impl EncodeContext {
175
    pub fn new() -> Self {
2,496✔
176
        Self::default()
2,496✔
177
    }
2,496✔
178

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

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

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

198
        if !accumulator.is_empty() {
2,496✔
199
            items.push_back(Fragment::Line { data: accumulator });
2,496✔
200
        }
2,496✔
201

202
        items
2,496✔
203
    }
2,496✔
204

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

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

217
        out
450✔
218
    }
450✔
219
}
220

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

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

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

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

241
                Encoded {
1,540✔
242
                    items: encode_context.into_items(),
1,540✔
243
                }
1,540✔
244
            }
1,540✔
245
        }
246
    };
247
}
248

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

255
// -------------------------------------------------------------------------------------------------
256

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

261
// ----- Primitive ---------------------------------------------------------------------------------
262

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

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

275
// ----- Command -----------------------------------------------------------------------------------
276

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

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

292
impl EncodeIntoContext for CommandBody<'_> {
293
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
698✔
294
        match self {
698✔
295
            CommandBody::Capability => ctx.write_all(b"CAPABILITY"),
48✔
296
            CommandBody::Noop => ctx.write_all(b"NOOP"),
22✔
297
            CommandBody::Logout => ctx.write_all(b"LOGOUT"),
16✔
298
            #[cfg(feature = "starttls")]
299
            CommandBody::StartTLS => ctx.write_all(b"STARTTLS"),
16✔
300
            CommandBody::Authenticate {
301
                mechanism,
10✔
302
                initial_response,
10✔
303
            } => {
304
                ctx.write_all(b"AUTHENTICATE ")?;
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 } => {
56✔
323
                ctx.write_all(b"LOGIN ")?;
56✔
324
                username.encode_ctx(ctx)?;
56✔
325
                ctx.write_all(b" ")?;
56✔
326
                password.declassify().encode_ctx(ctx)
56✔
327
            }
328
            CommandBody::Select {
329
                mailbox,
20✔
330
                #[cfg(feature = "ext_condstore_qresync")]
331
                parameters,
20✔
332
            } => {
333
                ctx.write_all(b"SELECT ")?;
20✔
334
                mailbox.encode_ctx(ctx)?;
20✔
335

336
                #[cfg(feature = "ext_condstore_qresync")]
337
                if !parameters.is_empty() {
20✔
338
                    ctx.write_all(b" (")?;
×
339
                    join_serializable(parameters, b" ", ctx)?;
×
340
                    ctx.write_all(b")")?;
×
341
                }
20✔
342

343
                Ok(())
20✔
344
            }
345
            CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
2✔
346
            CommandBody::Examine {
347
                mailbox,
8✔
348
                #[cfg(feature = "ext_condstore_qresync")]
349
                parameters,
8✔
350
            } => {
351
                ctx.write_all(b"EXAMINE ")?;
8✔
352
                mailbox.encode_ctx(ctx)?;
8✔
353

354
                #[cfg(feature = "ext_condstore_qresync")]
355
                if !parameters.is_empty() {
8✔
356
                    ctx.write_all(b" (")?;
×
357
                    join_serializable(parameters, b" ", ctx)?;
×
358
                    ctx.write_all(b")")?;
×
359
                }
8✔
360

361
                Ok(())
8✔
362
            }
363
            CommandBody::Create { mailbox } => {
16✔
364
                ctx.write_all(b"CREATE ")?;
16✔
365
                mailbox.encode_ctx(ctx)
16✔
366
            }
367
            CommandBody::Delete { mailbox } => {
48✔
368
                ctx.write_all(b"DELETE ")?;
48✔
369
                mailbox.encode_ctx(ctx)
48✔
370
            }
371
            CommandBody::Rename {
372
                from: mailbox,
24✔
373
                to: new_mailbox,
24✔
374
            } => {
375
                ctx.write_all(b"RENAME ")?;
24✔
376
                mailbox.encode_ctx(ctx)?;
24✔
377
                ctx.write_all(b" ")?;
24✔
378
                new_mailbox.encode_ctx(ctx)
24✔
379
            }
380
            CommandBody::Subscribe { mailbox } => {
8✔
381
                ctx.write_all(b"SUBSCRIBE ")?;
8✔
382
                mailbox.encode_ctx(ctx)
8✔
383
            }
384
            CommandBody::Unsubscribe { mailbox } => {
8✔
385
                ctx.write_all(b"UNSUBSCRIBE ")?;
8✔
386
                mailbox.encode_ctx(ctx)
8✔
387
            }
388
            CommandBody::List {
389
                reference,
104✔
390
                mailbox_wildcard,
104✔
391
            } => {
392
                ctx.write_all(b"LIST ")?;
104✔
393
                reference.encode_ctx(ctx)?;
104✔
394
                ctx.write_all(b" ")?;
104✔
395
                mailbox_wildcard.encode_ctx(ctx)
104✔
396
            }
397
            CommandBody::Lsub {
398
                reference,
16✔
399
                mailbox_wildcard,
16✔
400
            } => {
401
                ctx.write_all(b"LSUB ")?;
16✔
402
                reference.encode_ctx(ctx)?;
16✔
403
                ctx.write_all(b" ")?;
16✔
404
                mailbox_wildcard.encode_ctx(ctx)
16✔
405
            }
406
            CommandBody::Status {
407
                mailbox,
10✔
408
                item_names,
10✔
409
            } => {
410
                ctx.write_all(b"STATUS ")?;
10✔
411
                mailbox.encode_ctx(ctx)?;
10✔
412
                ctx.write_all(b" (")?;
10✔
413
                join_serializable(item_names, b" ", ctx)?;
10✔
414
                ctx.write_all(b")")
10✔
415
            }
416
            CommandBody::Append {
417
                mailbox,
×
418
                flags,
×
419
                date,
×
420
                message,
×
421
            } => {
422
                ctx.write_all(b"APPEND ")?;
×
423
                mailbox.encode_ctx(ctx)?;
×
424

425
                if !flags.is_empty() {
×
426
                    ctx.write_all(b" (")?;
×
427
                    join_serializable(flags, b" ", ctx)?;
×
428
                    ctx.write_all(b")")?;
×
429
                }
×
430

431
                if let Some(date) = date {
×
432
                    ctx.write_all(b" ")?;
×
433
                    date.encode_ctx(ctx)?;
×
434
                }
×
435

436
                ctx.write_all(b" ")?;
×
437
                message.encode_ctx(ctx)
×
438
            }
439
            CommandBody::Check => ctx.write_all(b"CHECK"),
8✔
440
            CommandBody::Close => ctx.write_all(b"CLOSE"),
8✔
441
            CommandBody::Expunge => ctx.write_all(b"EXPUNGE"),
16✔
442
            CommandBody::ExpungeUid { sequence_set } => {
6✔
443
                ctx.write_all(b"UID EXPUNGE ")?;
6✔
444
                sequence_set.encode_ctx(ctx)
6✔
445
            }
446
            CommandBody::Search {
447
                charset,
16✔
448
                criteria,
16✔
449
                uid,
16✔
450
            } => {
451
                if *uid {
16✔
452
                    ctx.write_all(b"UID SEARCH")?;
×
453
                } else {
454
                    ctx.write_all(b"SEARCH")?;
16✔
455
                }
456
                if let Some(charset) = charset {
16✔
457
                    ctx.write_all(b" CHARSET ")?;
×
458
                    charset.encode_ctx(ctx)?;
×
459
                }
16✔
460
                ctx.write_all(b" ")?;
16✔
461
                join_serializable(criteria.as_ref(), b" ", ctx)
16✔
462
            }
463
            CommandBody::Sort {
464
                sort_criteria,
24✔
465
                charset,
24✔
466
                search_criteria,
24✔
467
                uid,
24✔
468
            } => {
469
                if *uid {
24✔
470
                    ctx.write_all(b"UID SORT (")?;
×
471
                } else {
472
                    ctx.write_all(b"SORT (")?;
24✔
473
                }
474
                join_serializable(sort_criteria.as_ref(), b" ", ctx)?;
24✔
475
                ctx.write_all(b") ")?;
24✔
476
                charset.encode_ctx(ctx)?;
24✔
477
                ctx.write_all(b" ")?;
24✔
478
                join_serializable(search_criteria.as_ref(), b" ", ctx)
24✔
479
            }
480
            CommandBody::Thread {
481
                algorithm,
24✔
482
                charset,
24✔
483
                search_criteria,
24✔
484
                uid,
24✔
485
            } => {
486
                if *uid {
24✔
487
                    ctx.write_all(b"UID THREAD ")?;
×
488
                } else {
489
                    ctx.write_all(b"THREAD ")?;
24✔
490
                }
491
                algorithm.encode_ctx(ctx)?;
24✔
492
                ctx.write_all(b" ")?;
24✔
493
                charset.encode_ctx(ctx)?;
24✔
494
                ctx.write_all(b" ")?;
24✔
495
                join_serializable(search_criteria.as_ref(), b" ", ctx)
24✔
496
            }
497
            CommandBody::Fetch {
498
                sequence_set,
32✔
499
                macro_or_item_names,
32✔
500
                uid,
32✔
501
                #[cfg(feature = "ext_condstore_qresync")]
502
                modifiers,
32✔
503
            } => {
504
                if *uid {
32✔
505
                    ctx.write_all(b"UID FETCH ")?;
8✔
506
                } else {
507
                    ctx.write_all(b"FETCH ")?;
24✔
508
                }
509

510
                sequence_set.encode_ctx(ctx)?;
32✔
511
                ctx.write_all(b" ")?;
32✔
512
                macro_or_item_names.encode_ctx(ctx)?;
32✔
513

514
                #[cfg(feature = "ext_condstore_qresync")]
515
                if !modifiers.is_empty() {
32✔
516
                    ctx.write_all(b" (")?;
×
517
                    join_serializable(modifiers, b" ", ctx)?;
×
518
                    ctx.write_all(b")")?;
×
519
                }
32✔
520

521
                Ok(())
32✔
522
            }
523
            CommandBody::Store {
524
                sequence_set,
16✔
525
                kind,
16✔
526
                response,
16✔
527
                flags,
16✔
528
                uid,
16✔
529
                #[cfg(feature = "ext_condstore_qresync")]
530
                modifiers,
16✔
531
            } => {
532
                if *uid {
16✔
533
                    ctx.write_all(b"UID STORE ")?;
×
534
                } else {
535
                    ctx.write_all(b"STORE ")?;
16✔
536
                }
537

538
                sequence_set.encode_ctx(ctx)?;
16✔
539
                ctx.write_all(b" ")?;
16✔
540

541
                #[cfg(feature = "ext_condstore_qresync")]
542
                if !modifiers.is_empty() {
16✔
543
                    ctx.write_all(b"(")?;
×
544
                    join_serializable(modifiers, b" ", ctx)?;
×
545
                    ctx.write_all(b") ")?;
×
546
                }
16✔
547

548
                match kind {
16✔
549
                    StoreType::Add => ctx.write_all(b"+")?,
16✔
550
                    StoreType::Remove => ctx.write_all(b"-")?,
×
551
                    StoreType::Replace => {}
×
552
                }
553

554
                ctx.write_all(b"FLAGS")?;
16✔
555

556
                match response {
16✔
557
                    StoreResponse::Answer => {}
16✔
558
                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
×
559
                }
560

561
                ctx.write_all(b" (")?;
16✔
562
                join_serializable(flags, b" ", ctx)?;
16✔
563
                ctx.write_all(b")")
16✔
564
            }
565
            CommandBody::Copy {
566
                sequence_set,
24✔
567
                mailbox,
24✔
568
                uid,
24✔
569
            } => {
570
                if *uid {
24✔
571
                    ctx.write_all(b"UID COPY ")?;
×
572
                } else {
573
                    ctx.write_all(b"COPY ")?;
24✔
574
                }
575
                sequence_set.encode_ctx(ctx)?;
24✔
576
                ctx.write_all(b" ")?;
24✔
577
                mailbox.encode_ctx(ctx)
24✔
578
            }
579
            CommandBody::Idle => ctx.write_all(b"IDLE"),
4✔
580
            CommandBody::Enable { capabilities } => {
22✔
581
                ctx.write_all(b"ENABLE ")?;
22✔
582
                join_serializable(capabilities.as_ref(), b" ", ctx)
22✔
583
            }
584
            CommandBody::Compress { algorithm } => {
4✔
585
                ctx.write_all(b"COMPRESS ")?;
4✔
586
                algorithm.encode_ctx(ctx)
4✔
587
            }
588
            CommandBody::GetQuota { root } => {
10✔
589
                ctx.write_all(b"GETQUOTA ")?;
10✔
590
                root.encode_ctx(ctx)
10✔
591
            }
592
            CommandBody::GetQuotaRoot { mailbox } => {
6✔
593
                ctx.write_all(b"GETQUOTAROOT ")?;
6✔
594
                mailbox.encode_ctx(ctx)
6✔
595
            }
596
            CommandBody::SetQuota { root, quotas } => {
12✔
597
                ctx.write_all(b"SETQUOTA ")?;
12✔
598
                root.encode_ctx(ctx)?;
12✔
599
                ctx.write_all(b" (")?;
12✔
600
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
601
                ctx.write_all(b")")
12✔
602
            }
603
            CommandBody::Move {
604
                sequence_set,
6✔
605
                mailbox,
6✔
606
                uid,
6✔
607
            } => {
608
                if *uid {
6✔
609
                    ctx.write_all(b"UID MOVE ")?;
2✔
610
                } else {
611
                    ctx.write_all(b"MOVE ")?;
4✔
612
                }
613
                sequence_set.encode_ctx(ctx)?;
6✔
614
                ctx.write_all(b" ")?;
6✔
615
                mailbox.encode_ctx(ctx)
6✔
616
            }
617
            #[cfg(feature = "ext_id")]
618
            CommandBody::Id { parameters } => {
8✔
619
                ctx.write_all(b"ID ")?;
8✔
620

621
                match parameters {
8✔
622
                    Some(parameters) => {
4✔
623
                        if let Some((first, tail)) = parameters.split_first() {
4✔
624
                            ctx.write_all(b"(")?;
4✔
625

626
                            first.0.encode_ctx(ctx)?;
4✔
627
                            ctx.write_all(b" ")?;
4✔
628
                            first.1.encode_ctx(ctx)?;
4✔
629

630
                            for parameter in tail {
4✔
631
                                ctx.write_all(b" ")?;
×
632
                                parameter.0.encode_ctx(ctx)?;
×
633
                                ctx.write_all(b" ")?;
×
634
                                parameter.1.encode_ctx(ctx)?;
×
635
                            }
636

637
                            ctx.write_all(b")")
4✔
638
                        } else {
639
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
640
                            {
641
                                ctx.write_all(b"()")
642
                            }
643
                            #[cfg(feature = "quirk_id_empty_to_nil")]
644
                            {
645
                                ctx.write_all(b"NIL")
×
646
                            }
647
                        }
648
                    }
649
                    None => ctx.write_all(b"NIL"),
4✔
650
                }
651
            }
652
            #[cfg(feature = "ext_metadata")]
653
            CommandBody::SetMetadata {
654
                mailbox,
6✔
655
                entry_values,
6✔
656
            } => {
657
                ctx.write_all(b"SETMETADATA ")?;
6✔
658
                mailbox.encode_ctx(ctx)?;
6✔
659
                ctx.write_all(b" (")?;
6✔
660
                join_serializable(entry_values.as_ref(), b" ", ctx)?;
6✔
661
                ctx.write_all(b")")
6✔
662
            }
663
            #[cfg(feature = "ext_metadata")]
664
            CommandBody::GetMetadata {
665
                options,
14✔
666
                mailbox,
14✔
667
                entries,
14✔
668
            } => {
669
                ctx.write_all(b"GETMETADATA")?;
14✔
670

671
                if !options.is_empty() {
14✔
672
                    ctx.write_all(b" (")?;
10✔
673
                    join_serializable(options, b" ", ctx)?;
10✔
674
                    ctx.write_all(b")")?;
10✔
675
                }
4✔
676

677
                ctx.write_all(b" ")?;
14✔
678
                mailbox.encode_ctx(ctx)?;
14✔
679

680
                ctx.write_all(b" ")?;
14✔
681

682
                if entries.as_ref().len() == 1 {
14✔
683
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
684
                } else {
685
                    ctx.write_all(b"(")?;
×
686
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
687
                    ctx.write_all(b")")
×
688
                }
689
            }
690
            #[cfg(feature = "ext_namespace")]
NEW
691
            &CommandBody::Namespace => ctx.write_all(b"NAMESPACE"),
×
692
        }
693
    }
698✔
694
}
695

696
#[cfg(feature = "ext_condstore_qresync")]
697
impl EncodeIntoContext for FetchModifier {
698
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
699
        match self {
×
700
            FetchModifier::ChangedSince(since) => write!(ctx, "CHANGEDSINCE {since}"),
×
701
            FetchModifier::Vanished => write!(ctx, "VANISHED"),
×
702
        }
703
    }
×
704
}
705

706
#[cfg(feature = "ext_condstore_qresync")]
707
impl EncodeIntoContext for StoreModifier {
708
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
709
        match self {
×
710
            StoreModifier::UnchangedSince(since) => write!(ctx, "UNCHANGEDSINCE {since}"),
×
711
        }
712
    }
×
713
}
714

715
#[cfg(feature = "ext_condstore_qresync")]
716
impl EncodeIntoContext for SelectParameter {
717
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
718
        match self {
×
719
            SelectParameter::CondStore => write!(ctx, "CONDSTORE"),
×
720
            SelectParameter::QResync {
721
                uid_validity,
×
722
                mod_sequence_value,
×
723
                known_uids,
×
724
                seq_match_data,
×
725
            } => {
726
                write!(ctx, "QRESYNC (")?;
×
727
                uid_validity.encode_ctx(ctx)?;
×
728
                write!(ctx, " ")?;
×
729
                mod_sequence_value.encode_ctx(ctx)?;
×
730

731
                if let Some(known_uids) = known_uids {
×
732
                    write!(ctx, " ")?;
×
733
                    known_uids.encode_ctx(ctx)?;
×
734
                }
×
735

736
                if let Some((known_sequence_set, known_uid_set)) = seq_match_data {
×
737
                    write!(ctx, " (")?;
×
738
                    known_sequence_set.encode_ctx(ctx)?;
×
739
                    write!(ctx, " ")?;
×
740
                    known_uid_set.encode_ctx(ctx)?;
×
741
                    write!(ctx, ")")?;
×
742
                }
×
743

744
                write!(ctx, ")")
×
745
            }
746
        }
747
    }
×
748
}
749

750
impl EncodeIntoContext for AuthMechanism<'_> {
751
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
22✔
752
        write!(ctx, "{self}")
22✔
753
    }
22✔
754
}
755

756
impl EncodeIntoContext for AuthenticateData<'_> {
757
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
758
        match self {
8✔
759
            Self::Continue(data) => {
6✔
760
                let encoded = base64.encode(data.declassify());
6✔
761
                ctx.write_all(encoded.as_bytes())?;
6✔
762
                ctx.write_all(b"\r\n")
6✔
763
            }
764
            Self::Cancel => ctx.write_all(b"*\r\n"),
2✔
765
        }
766
    }
8✔
767
}
768

769
impl EncodeIntoContext for AString<'_> {
770
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
794✔
771
        match self {
794✔
772
            AString::Atom(atom) => atom.encode_ctx(ctx),
580✔
773
            AString::String(imap_str) => imap_str.encode_ctx(ctx),
214✔
774
        }
775
    }
794✔
776
}
777

778
impl EncodeIntoContext for Atom<'_> {
779
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
54✔
780
        ctx.write_all(self.inner().as_bytes())
54✔
781
    }
54✔
782
}
783

784
impl EncodeIntoContext for AtomExt<'_> {
785
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
580✔
786
        ctx.write_all(self.inner().as_bytes())
580✔
787
    }
580✔
788
}
789

790
impl EncodeIntoContext for IString<'_> {
791
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
518✔
792
        match self {
518✔
793
            Self::Literal(val) => val.encode_ctx(ctx),
44✔
794
            Self::Quoted(val) => val.encode_ctx(ctx),
474✔
795
            #[cfg(feature = "ext_utf8")]
796
            Self::QuotedUtf8(val) => val.encode_ctx(ctx),
×
797
        }
798
    }
518✔
799
}
800

801
impl EncodeIntoContext for Literal<'_> {
802
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
803
        match self.mode() {
44✔
804
            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
30✔
805
            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
14✔
806
        }
807

808
        ctx.push_line();
44✔
809
        ctx.write_all(self.as_ref())?;
44✔
810
        ctx.push_literal(self.mode());
44✔
811

812
        Ok(())
44✔
813
    }
44✔
814
}
815

816
impl EncodeIntoContext for Quoted<'_> {
817
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
482✔
818
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
482✔
819
    }
482✔
820
}
821

822
impl EncodeIntoContext for Mailbox<'_> {
823
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
616✔
824
        match self {
616✔
825
            Mailbox::Inbox => ctx.write_all(b"INBOX"),
80✔
826
            Mailbox::Other(other) => other.encode_ctx(ctx),
536✔
827
        }
828
    }
616✔
829
}
830

831
impl EncodeIntoContext for MailboxOther<'_> {
832
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
536✔
833
        self.inner().encode_ctx(ctx)
536✔
834
    }
536✔
835
}
836

837
impl EncodeIntoContext for ListMailbox<'_> {
838
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
839
        match self {
120✔
840
            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
80✔
841
            ListMailbox::String(istr) => istr.encode_ctx(ctx),
40✔
842
        }
843
    }
120✔
844
}
845

846
impl EncodeIntoContext for ListCharString<'_> {
847
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
80✔
848
        ctx.write_all(self.as_ref())
80✔
849
    }
80✔
850
}
851

852
impl EncodeIntoContext for StatusDataItemName {
853
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
36✔
854
        match self {
36✔
855
            Self::Messages => ctx.write_all(b"MESSAGES"),
12✔
856
            Self::Recent => ctx.write_all(b"RECENT"),
2✔
857
            Self::UidNext => ctx.write_all(b"UIDNEXT"),
10✔
858
            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
2✔
859
            Self::Unseen => ctx.write_all(b"UNSEEN"),
2✔
860
            Self::Deleted => ctx.write_all(b"DELETED"),
4✔
861
            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
4✔
862
            #[cfg(feature = "ext_condstore_qresync")]
863
            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
×
864
        }
865
    }
36✔
866
}
867

868
impl EncodeIntoContext for Flag<'_> {
869
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
312✔
870
        write!(ctx, "{self}")
312✔
871
    }
312✔
872
}
873

874
impl EncodeIntoContext for FlagFetch<'_> {
875
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
876
        match self {
120✔
877
            Self::Flag(flag) => flag.encode_ctx(ctx),
120✔
878
            Self::Recent => ctx.write_all(b"\\Recent"),
×
879
        }
880
    }
120✔
881
}
882

883
impl EncodeIntoContext for FlagPerm<'_> {
884
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
24✔
885
        match self {
24✔
886
            Self::Flag(flag) => flag.encode_ctx(ctx),
16✔
887
            Self::Asterisk => ctx.write_all(b"\\*"),
8✔
888
        }
889
    }
24✔
890
}
891

892
impl EncodeIntoContext for DateTime {
893
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
894
        self.as_ref().encode_ctx(ctx)
14✔
895
    }
14✔
896
}
897

898
impl EncodeIntoContext for Charset<'_> {
899
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
58✔
900
        match self {
58✔
901
            Charset::Atom(atom) => atom.encode_ctx(ctx),
50✔
902
            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
8✔
903
        }
904
    }
58✔
905
}
906

907
impl EncodeIntoContext for SearchKey<'_> {
908
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
176✔
909
        match self {
176✔
910
            SearchKey::All => ctx.write_all(b"ALL"),
10✔
911
            SearchKey::Answered => ctx.write_all(b"ANSWERED"),
6✔
912
            SearchKey::Bcc(astring) => {
2✔
913
                ctx.write_all(b"BCC ")?;
2✔
914
                astring.encode_ctx(ctx)
2✔
915
            }
916
            SearchKey::Before(date) => {
2✔
917
                ctx.write_all(b"BEFORE ")?;
2✔
918
                date.encode_ctx(ctx)
2✔
919
            }
920
            SearchKey::Body(astring) => {
2✔
921
                ctx.write_all(b"BODY ")?;
2✔
922
                astring.encode_ctx(ctx)
2✔
923
            }
924
            SearchKey::Cc(astring) => {
2✔
925
                ctx.write_all(b"CC ")?;
2✔
926
                astring.encode_ctx(ctx)
2✔
927
            }
928
            SearchKey::Deleted => ctx.write_all(b"DELETED"),
2✔
929
            SearchKey::Flagged => ctx.write_all(b"FLAGGED"),
10✔
930
            SearchKey::From(astring) => {
10✔
931
                ctx.write_all(b"FROM ")?;
10✔
932
                astring.encode_ctx(ctx)
10✔
933
            }
934
            SearchKey::Keyword(flag_keyword) => {
2✔
935
                ctx.write_all(b"KEYWORD ")?;
2✔
936
                flag_keyword.encode_ctx(ctx)
2✔
937
            }
938
            SearchKey::New => ctx.write_all(b"NEW"),
6✔
939
            SearchKey::Old => ctx.write_all(b"OLD"),
2✔
940
            SearchKey::On(date) => {
2✔
941
                ctx.write_all(b"ON ")?;
2✔
942
                date.encode_ctx(ctx)
2✔
943
            }
944
            SearchKey::Recent => ctx.write_all(b"RECENT"),
4✔
945
            SearchKey::Seen => ctx.write_all(b"SEEN"),
4✔
946
            SearchKey::Since(date) => {
34✔
947
                ctx.write_all(b"SINCE ")?;
34✔
948
                date.encode_ctx(ctx)
34✔
949
            }
950
            SearchKey::Subject(astring) => {
2✔
951
                ctx.write_all(b"SUBJECT ")?;
2✔
952
                astring.encode_ctx(ctx)
2✔
953
            }
954
            SearchKey::Text(astring) => {
26✔
955
                ctx.write_all(b"TEXT ")?;
26✔
956
                astring.encode_ctx(ctx)
26✔
957
            }
958
            SearchKey::To(astring) => {
2✔
959
                ctx.write_all(b"TO ")?;
2✔
960
                astring.encode_ctx(ctx)
2✔
961
            }
962
            SearchKey::Unanswered => ctx.write_all(b"UNANSWERED"),
2✔
963
            SearchKey::Undeleted => ctx.write_all(b"UNDELETED"),
2✔
964
            SearchKey::Unflagged => ctx.write_all(b"UNFLAGGED"),
2✔
965
            SearchKey::Unkeyword(flag_keyword) => {
2✔
966
                ctx.write_all(b"UNKEYWORD ")?;
2✔
967
                flag_keyword.encode_ctx(ctx)
2✔
968
            }
969
            SearchKey::Unseen => ctx.write_all(b"UNSEEN"),
2✔
970
            SearchKey::Draft => ctx.write_all(b"DRAFT"),
2✔
971
            SearchKey::Header(header_fld_name, astring) => {
2✔
972
                ctx.write_all(b"HEADER ")?;
2✔
973
                header_fld_name.encode_ctx(ctx)?;
2✔
974
                ctx.write_all(b" ")?;
2✔
975
                astring.encode_ctx(ctx)
2✔
976
            }
977
            SearchKey::Larger(number) => write!(ctx, "LARGER {number}"),
2✔
978
            SearchKey::Not(search_key) => {
10✔
979
                ctx.write_all(b"NOT ")?;
10✔
980
                search_key.encode_ctx(ctx)
10✔
981
            }
982
            SearchKey::Or(search_key_a, search_key_b) => {
2✔
983
                ctx.write_all(b"OR ")?;
2✔
984
                search_key_a.encode_ctx(ctx)?;
2✔
985
                ctx.write_all(b" ")?;
2✔
986
                search_key_b.encode_ctx(ctx)
2✔
987
            }
988
            SearchKey::SentBefore(date) => {
2✔
989
                ctx.write_all(b"SENTBEFORE ")?;
2✔
990
                date.encode_ctx(ctx)
2✔
991
            }
992
            SearchKey::SentOn(date) => {
2✔
993
                ctx.write_all(b"SENTON ")?;
2✔
994
                date.encode_ctx(ctx)
2✔
995
            }
996
            SearchKey::SentSince(date) => {
2✔
997
                ctx.write_all(b"SENTSINCE ")?;
2✔
998
                date.encode_ctx(ctx)
2✔
999
            }
1000
            SearchKey::Smaller(number) => write!(ctx, "SMALLER {number}"),
2✔
1001
            SearchKey::Uid(sequence_set) => {
2✔
1002
                ctx.write_all(b"UID ")?;
2✔
1003
                sequence_set.encode_ctx(ctx)
2✔
1004
            }
1005
            SearchKey::Undraft => ctx.write_all(b"UNDRAFT"),
2✔
1006
            #[cfg(feature = "ext_condstore_qresync")]
1007
            SearchKey::ModSequence { entry, modseq } => {
×
1008
                ctx.write_all(b"MODSEQ")?;
×
1009
                if let Some((attribute_flag, entry_type_req)) = entry {
×
1010
                    write!(ctx, " \"/flags/{attribute_flag}\"")?;
×
1011
                    write!(ctx, " {entry_type_req}")?;
×
1012
                }
×
1013
                ctx.write_all(b" ")?;
×
1014
                modseq.encode_ctx(ctx)
×
1015
            }
1016
            SearchKey::SequenceSet(sequence_set) => sequence_set.encode_ctx(ctx),
2✔
1017
            SearchKey::And(search_keys) => {
4✔
1018
                ctx.write_all(b"(")?;
4✔
1019
                join_serializable(search_keys.as_ref(), b" ", ctx)?;
4✔
1020
                ctx.write_all(b")")
4✔
1021
            }
1022
        }
1023
    }
176✔
1024
}
1025

1026
impl EncodeIntoContext for SequenceSet {
1027
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
88✔
1028
        join_serializable(self.0.as_ref(), b",", ctx)
88✔
1029
    }
88✔
1030
}
1031

1032
impl EncodeIntoContext for Sequence {
1033
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
94✔
1034
        match self {
94✔
1035
            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
38✔
1036
            Sequence::Range(from, to) => {
56✔
1037
                from.encode_ctx(ctx)?;
56✔
1038
                ctx.write_all(b":")?;
56✔
1039
                to.encode_ctx(ctx)
56✔
1040
            }
1041
        }
1042
    }
94✔
1043
}
1044

1045
impl EncodeIntoContext for SeqOrUid {
1046
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
150✔
1047
        match self {
150✔
1048
            SeqOrUid::Value(number) => write!(ctx, "{number}"),
140✔
1049
            SeqOrUid::Asterisk => ctx.write_all(b"*"),
10✔
1050
        }
1051
    }
150✔
1052
}
1053

1054
impl EncodeIntoContext for NaiveDate {
1055
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
1056
        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
44✔
1057
    }
44✔
1058
}
1059

1060
impl EncodeIntoContext for MacroOrMessageDataItemNames<'_> {
1061
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1062
        match self {
32✔
1063
            Self::Macro(m) => m.encode_ctx(ctx),
8✔
1064
            Self::MessageDataItemNames(item_names) => {
24✔
1065
                if item_names.len() == 1 {
24✔
1066
                    item_names[0].encode_ctx(ctx)
16✔
1067
                } else {
1068
                    ctx.write_all(b"(")?;
8✔
1069
                    join_serializable(item_names.as_slice(), b" ", ctx)?;
8✔
1070
                    ctx.write_all(b")")
8✔
1071
                }
1072
            }
1073
        }
1074
    }
32✔
1075
}
1076

1077
impl EncodeIntoContext for Macro {
1078
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1079
        write!(ctx, "{self}")
8✔
1080
    }
8✔
1081
}
1082

1083
impl EncodeIntoContext for MessageDataItemName<'_> {
1084
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
54✔
1085
        match self {
54✔
1086
            Self::Body => ctx.write_all(b"BODY"),
2✔
1087
            Self::BodyExt {
18✔
1088
                section,
18✔
1089
                partial,
18✔
1090
                peek,
18✔
1091
            } => {
18✔
1092
                if *peek {
18✔
1093
                    ctx.write_all(b"BODY.PEEK[")?;
×
1094
                } else {
1095
                    ctx.write_all(b"BODY[")?;
18✔
1096
                }
1097
                if let Some(section) = section {
18✔
1098
                    section.encode_ctx(ctx)?;
16✔
1099
                }
2✔
1100
                ctx.write_all(b"]")?;
18✔
1101
                if let Some((a, b)) = partial {
18✔
1102
                    write!(ctx, "<{a}.{b}>")?;
×
1103
                }
18✔
1104

1105
                Ok(())
18✔
1106
            }
1107
            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
2✔
1108
            Self::Envelope => ctx.write_all(b"ENVELOPE"),
2✔
1109
            Self::Flags => ctx.write_all(b"FLAGS"),
18✔
1110
            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
2✔
1111
            Self::Rfc822 => ctx.write_all(b"RFC822"),
2✔
1112
            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
2✔
1113
            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
2✔
1114
            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
2✔
1115
            Self::Uid => ctx.write_all(b"UID"),
2✔
1116
            MessageDataItemName::Binary {
1117
                section,
×
1118
                partial,
×
1119
                peek,
×
1120
            } => {
1121
                ctx.write_all(b"BINARY")?;
×
1122
                if *peek {
×
1123
                    ctx.write_all(b".PEEK")?;
×
1124
                }
×
1125

1126
                ctx.write_all(b"[")?;
×
1127
                join_serializable(section, b".", ctx)?;
×
1128
                ctx.write_all(b"]")?;
×
1129

1130
                if let Some((a, b)) = partial {
×
1131
                    ctx.write_all(b"<")?;
×
1132
                    a.encode_ctx(ctx)?;
×
1133
                    ctx.write_all(b".")?;
×
1134
                    b.encode_ctx(ctx)?;
×
1135
                    ctx.write_all(b">")?;
×
1136
                }
×
1137

1138
                Ok(())
×
1139
            }
1140
            MessageDataItemName::BinarySize { section } => {
×
1141
                ctx.write_all(b"BINARY.SIZE")?;
×
1142

1143
                ctx.write_all(b"[")?;
×
1144
                join_serializable(section, b".", ctx)?;
×
1145
                ctx.write_all(b"]")
×
1146
            }
1147
            #[cfg(feature = "ext_condstore_qresync")]
1148
            MessageDataItemName::ModSeq => ctx.write_all(b"MODSEQ"),
×
1149
        }
1150
    }
54✔
1151
}
1152

1153
impl EncodeIntoContext for Section<'_> {
1154
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
1155
        match self {
44✔
1156
            Section::Part(part) => part.encode_ctx(ctx),
2✔
1157
            Section::Header(maybe_part) => match maybe_part {
20✔
1158
                Some(part) => {
2✔
1159
                    part.encode_ctx(ctx)?;
2✔
1160
                    ctx.write_all(b".HEADER")
2✔
1161
                }
1162
                None => ctx.write_all(b"HEADER"),
18✔
1163
            },
1164
            Section::HeaderFields(maybe_part, header_list) => {
12✔
1165
                match maybe_part {
12✔
1166
                    Some(part) => {
2✔
1167
                        part.encode_ctx(ctx)?;
2✔
1168
                        ctx.write_all(b".HEADER.FIELDS (")?;
2✔
1169
                    }
1170
                    None => ctx.write_all(b"HEADER.FIELDS (")?,
10✔
1171
                };
1172
                join_serializable(header_list.as_ref(), b" ", ctx)?;
12✔
1173
                ctx.write_all(b")")
12✔
1174
            }
1175
            Section::HeaderFieldsNot(maybe_part, header_list) => {
4✔
1176
                match maybe_part {
4✔
1177
                    Some(part) => {
2✔
1178
                        part.encode_ctx(ctx)?;
2✔
1179
                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
2✔
1180
                    }
1181
                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
2✔
1182
                };
1183
                join_serializable(header_list.as_ref(), b" ", ctx)?;
4✔
1184
                ctx.write_all(b")")
4✔
1185
            }
1186
            Section::Text(maybe_part) => match maybe_part {
4✔
1187
                Some(part) => {
2✔
1188
                    part.encode_ctx(ctx)?;
2✔
1189
                    ctx.write_all(b".TEXT")
2✔
1190
                }
1191
                None => ctx.write_all(b"TEXT"),
2✔
1192
            },
1193
            Section::Mime(part) => {
2✔
1194
                part.encode_ctx(ctx)?;
2✔
1195
                ctx.write_all(b".MIME")
2✔
1196
            }
1197
        }
1198
    }
44✔
1199
}
1200

1201
impl EncodeIntoContext for Part {
1202
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
1203
        join_serializable(self.0.as_ref(), b".", ctx)
12✔
1204
    }
12✔
1205
}
1206

1207
impl EncodeIntoContext for NonZeroU32 {
1208
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
246✔
1209
        write!(ctx, "{self}")
246✔
1210
    }
246✔
1211
}
1212

1213
#[cfg(feature = "ext_condstore_qresync")]
1214
impl EncodeIntoContext for NonZeroU64 {
1215
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1216
        write!(ctx, "{self}")
×
1217
    }
×
1218
}
1219

1220
impl EncodeIntoContext for Capability<'_> {
1221
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
232✔
1222
        write!(ctx, "{self}")
232✔
1223
    }
232✔
1224
}
1225

1226
// ----- Responses ---------------------------------------------------------------------------------
1227

1228
impl EncodeIntoContext for Response<'_> {
1229
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,550✔
1230
        match self {
1,550✔
1231
            Response::Status(status) => status.encode_ctx(ctx),
824✔
1232
            Response::Data(data) => data.encode_ctx(ctx),
718✔
1233
            Response::CommandContinuationRequest(continue_request) => {
8✔
1234
                continue_request.encode_ctx(ctx)
8✔
1235
            }
1236
        }
1237
    }
1,550✔
1238
}
1239

1240
impl EncodeIntoContext for Greeting<'_> {
1241
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1242
        ctx.write_all(b"* ")?;
26✔
1243
        self.kind.encode_ctx(ctx)?;
26✔
1244
        ctx.write_all(b" ")?;
26✔
1245

1246
        if let Some(ref code) = self.code {
26✔
1247
            ctx.write_all(b"[")?;
12✔
1248
            code.encode_ctx(ctx)?;
12✔
1249
            ctx.write_all(b"] ")?;
12✔
1250
        }
14✔
1251

1252
        self.text.encode_ctx(ctx)?;
26✔
1253
        ctx.write_all(b"\r\n")
26✔
1254
    }
26✔
1255
}
1256

1257
impl EncodeIntoContext for GreetingKind {
1258
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1259
        match self {
26✔
1260
            GreetingKind::Ok => ctx.write_all(b"OK"),
12✔
1261
            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
12✔
1262
            GreetingKind::Bye => ctx.write_all(b"BYE"),
2✔
1263
        }
1264
    }
26✔
1265
}
1266

1267
impl EncodeIntoContext for Status<'_> {
1268
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
824✔
1269
        fn format_status(
824✔
1270
            tag: Option<&Tag>,
824✔
1271
            status: &str,
824✔
1272
            code: &Option<Code>,
824✔
1273
            comment: &Text,
824✔
1274
            ctx: &mut EncodeContext,
824✔
1275
        ) -> std::io::Result<()> {
824✔
1276
            match tag {
824✔
1277
                Some(tag) => tag.encode_ctx(ctx)?,
606✔
1278
                None => ctx.write_all(b"*")?,
218✔
1279
            }
1280
            ctx.write_all(b" ")?;
824✔
1281
            ctx.write_all(status.as_bytes())?;
824✔
1282
            ctx.write_all(b" ")?;
824✔
1283
            if let Some(code) = code {
824✔
1284
                ctx.write_all(b"[")?;
148✔
1285
                code.encode_ctx(ctx)?;
148✔
1286
                ctx.write_all(b"] ")?;
148✔
1287
            }
676✔
1288
            comment.encode_ctx(ctx)?;
824✔
1289
            ctx.write_all(b"\r\n")
824✔
1290
        }
824✔
1291

1292
        match self {
824✔
1293
            Self::Untagged(StatusBody { kind, code, text }) => match kind {
192✔
1294
                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
134✔
1295
                StatusKind::No => format_status(None, "NO", code, text, ctx),
30✔
1296
                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
28✔
1297
            },
1298
            Self::Tagged(Tagged {
606✔
1299
                tag,
606✔
1300
                body: StatusBody { kind, code, text },
606✔
1301
            }) => match kind {
606✔
1302
                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
572✔
1303
                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
22✔
1304
                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
12✔
1305
            },
1306
            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
26✔
1307
        }
1308
    }
824✔
1309
}
1310

1311
impl EncodeIntoContext for Code<'_> {
1312
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
160✔
1313
        match self {
160✔
1314
            Code::Alert => ctx.write_all(b"ALERT"),
24✔
1315
            Code::BadCharset { allowed } => {
2✔
1316
                if allowed.is_empty() {
2✔
1317
                    ctx.write_all(b"BADCHARSET")
2✔
1318
                } else {
1319
                    ctx.write_all(b"BADCHARSET (")?;
×
1320
                    join_serializable(allowed, b" ", ctx)?;
×
1321
                    ctx.write_all(b")")
×
1322
                }
1323
            }
1324
            Code::Capability(caps) => {
4✔
1325
                ctx.write_all(b"CAPABILITY ")?;
4✔
1326
                join_serializable(caps.as_ref(), b" ", ctx)
4✔
1327
            }
1328
            Code::Parse => ctx.write_all(b"PARSE"),
×
1329
            Code::PermanentFlags(flags) => {
16✔
1330
                ctx.write_all(b"PERMANENTFLAGS (")?;
16✔
1331
                join_serializable(flags, b" ", ctx)?;
16✔
1332
                ctx.write_all(b")")
16✔
1333
            }
1334
            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
8✔
1335
            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
16✔
1336
            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
×
1337
            Code::UidNext(next) => {
16✔
1338
                ctx.write_all(b"UIDNEXT ")?;
16✔
1339
                next.encode_ctx(ctx)
16✔
1340
            }
1341
            Code::UidValidity(validity) => {
24✔
1342
                ctx.write_all(b"UIDVALIDITY ")?;
24✔
1343
                validity.encode_ctx(ctx)
24✔
1344
            }
1345
            Code::Unseen(seq) => {
28✔
1346
                ctx.write_all(b"UNSEEN ")?;
28✔
1347
                seq.encode_ctx(ctx)
28✔
1348
            }
1349
            // RFC 2221
1350
            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1351
            Code::Referral(url) => {
×
1352
                ctx.write_all(b"REFERRAL ")?;
×
1353
                ctx.write_all(url.as_bytes())
×
1354
            }
1355
            // RFC 4551
1356
            #[cfg(feature = "ext_condstore_qresync")]
1357
            Code::HighestModSeq(modseq) => {
×
1358
                ctx.write_all(b"HIGHESTMODSEQ ")?;
×
1359
                modseq.encode_ctx(ctx)
×
1360
            }
1361
            #[cfg(feature = "ext_condstore_qresync")]
1362
            Code::NoModSeq => ctx.write_all(b"NOMODSEQ"),
×
1363
            #[cfg(feature = "ext_condstore_qresync")]
1364
            Code::Modified(sequence_set) => {
×
1365
                ctx.write_all(b"MODIFIED ")?;
×
1366
                sequence_set.encode_ctx(ctx)
×
1367
            }
1368
            #[cfg(feature = "ext_condstore_qresync")]
1369
            Code::Closed => ctx.write_all(b"CLOSED"),
×
1370
            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
×
1371
            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
4✔
1372
            Code::TooBig => ctx.write_all(b"TOOBIG"),
×
1373
            #[cfg(feature = "ext_metadata")]
1374
            Code::Metadata(code) => {
12✔
1375
                ctx.write_all(b"METADATA ")?;
12✔
1376
                code.encode_ctx(ctx)
12✔
1377
            }
1378
            Code::UnknownCte => ctx.write_all(b"UNKNOWN-CTE"),
×
1379
            Code::AppendUid { uid_validity, uid } => {
2✔
1380
                ctx.write_all(b"APPENDUID ")?;
2✔
1381
                uid_validity.encode_ctx(ctx)?;
2✔
1382
                ctx.write_all(b" ")?;
2✔
1383
                uid.encode_ctx(ctx)
2✔
1384
            }
1385
            Code::CopyUid {
1386
                uid_validity,
2✔
1387
                source,
2✔
1388
                destination,
2✔
1389
            } => {
1390
                ctx.write_all(b"COPYUID ")?;
2✔
1391
                uid_validity.encode_ctx(ctx)?;
2✔
1392
                ctx.write_all(b" ")?;
2✔
1393
                source.encode_ctx(ctx)?;
2✔
1394
                ctx.write_all(b" ")?;
2✔
1395
                destination.encode_ctx(ctx)
2✔
1396
            }
1397
            Code::UidNotSticky => ctx.write_all(b"UIDNOTSTICKY"),
2✔
1398
            Code::Other(unknown) => unknown.encode_ctx(ctx),
×
1399
        }
1400
    }
160✔
1401
}
1402

1403
impl EncodeIntoContext for CodeOther<'_> {
1404
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1405
        ctx.write_all(self.inner())
×
1406
    }
×
1407
}
1408

1409
impl EncodeIntoContext for Text<'_> {
1410
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
858✔
1411
        ctx.write_all(self.inner().as_bytes())
858✔
1412
    }
858✔
1413
}
1414

1415
impl EncodeIntoContext for Data<'_> {
1416
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
718✔
1417
        match self {
718✔
1418
            Data::Capability(caps) => {
64✔
1419
                ctx.write_all(b"* CAPABILITY ")?;
64✔
1420
                join_serializable(caps.as_ref(), b" ", ctx)?;
64✔
1421
            }
1422
            Data::List {
1423
                items,
210✔
1424
                delimiter,
210✔
1425
                mailbox,
210✔
1426
            } => {
1427
                ctx.write_all(b"* LIST (")?;
210✔
1428
                join_serializable(items, b" ", ctx)?;
210✔
1429
                ctx.write_all(b") ")?;
210✔
1430

1431
                if let Some(delimiter) = delimiter {
210✔
1432
                    ctx.write_all(b"\"")?;
210✔
1433
                    delimiter.encode_ctx(ctx)?;
210✔
1434
                    ctx.write_all(b"\"")?;
210✔
1435
                } else {
1436
                    ctx.write_all(b"NIL")?;
×
1437
                }
1438
                ctx.write_all(b" ")?;
210✔
1439
                mailbox.encode_ctx(ctx)?;
210✔
1440
            }
1441
            Data::Lsub {
1442
                items,
32✔
1443
                delimiter,
32✔
1444
                mailbox,
32✔
1445
            } => {
1446
                ctx.write_all(b"* LSUB (")?;
32✔
1447
                join_serializable(items, b" ", ctx)?;
32✔
1448
                ctx.write_all(b") ")?;
32✔
1449

1450
                if let Some(delimiter) = delimiter {
32✔
1451
                    ctx.write_all(b"\"")?;
32✔
1452
                    delimiter.encode_ctx(ctx)?;
32✔
1453
                    ctx.write_all(b"\"")?;
32✔
1454
                } else {
1455
                    ctx.write_all(b"NIL")?;
×
1456
                }
1457
                ctx.write_all(b" ")?;
32✔
1458
                mailbox.encode_ctx(ctx)?;
32✔
1459
            }
1460
            Data::Status { mailbox, items } => {
18✔
1461
                ctx.write_all(b"* STATUS ")?;
18✔
1462
                mailbox.encode_ctx(ctx)?;
18✔
1463
                ctx.write_all(b" (")?;
18✔
1464
                join_serializable(items, b" ", ctx)?;
18✔
1465
                ctx.write_all(b")")?;
18✔
1466
            }
1467
            // TODO: Exclude pattern via cfg?
1468
            #[cfg(not(feature = "ext_condstore_qresync"))]
1469
            Data::Search(seqs) => {
1470
                if seqs.is_empty() {
1471
                    ctx.write_all(b"* SEARCH")?;
1472
                } else {
1473
                    ctx.write_all(b"* SEARCH ")?;
1474
                    join_serializable(seqs, b" ", ctx)?;
1475
                }
1476
            }
1477
            // TODO: Exclude pattern via cfg?
1478
            #[cfg(feature = "ext_condstore_qresync")]
1479
            Data::Search(seqs, modseq) => {
38✔
1480
                if seqs.is_empty() {
38✔
1481
                    ctx.write_all(b"* SEARCH")?;
8✔
1482
                } else {
1483
                    ctx.write_all(b"* SEARCH ")?;
30✔
1484
                    join_serializable(seqs, b" ", ctx)?;
30✔
1485
                }
1486

1487
                if let Some(modseq) = modseq {
38✔
1488
                    ctx.write_all(b" (MODSEQ ")?;
×
1489
                    modseq.encode_ctx(ctx)?;
×
1490
                    ctx.write_all(b")")?;
×
1491
                }
38✔
1492
            }
1493
            // TODO: Exclude pattern via cfg?
1494
            #[cfg(not(feature = "ext_condstore_qresync"))]
1495
            Data::Sort(seqs) => {
1496
                if seqs.is_empty() {
1497
                    ctx.write_all(b"* SORT")?;
1498
                } else {
1499
                    ctx.write_all(b"* SORT ")?;
1500
                    join_serializable(seqs, b" ", ctx)?;
1501
                }
1502
            }
1503
            // TODO: Exclude pattern via cfg?
1504
            #[cfg(feature = "ext_condstore_qresync")]
1505
            Data::Sort(seqs, modseq) => {
24✔
1506
                if seqs.is_empty() {
24✔
1507
                    ctx.write_all(b"* SORT")?;
8✔
1508
                } else {
1509
                    ctx.write_all(b"* SORT ")?;
16✔
1510
                    join_serializable(seqs, b" ", ctx)?;
16✔
1511
                }
1512

1513
                if let Some(modseq) = modseq {
24✔
1514
                    ctx.write_all(b" (MODSEQ ")?;
×
1515
                    modseq.encode_ctx(ctx)?;
×
1516
                    ctx.write_all(b")")?;
×
1517
                }
24✔
1518
            }
1519
            Data::Thread(threads) => {
24✔
1520
                if threads.is_empty() {
24✔
1521
                    ctx.write_all(b"* THREAD")?;
8✔
1522
                } else {
1523
                    ctx.write_all(b"* THREAD ")?;
16✔
1524
                    for thread in threads {
400✔
1525
                        thread.encode_ctx(ctx)?;
384✔
1526
                    }
1527
                }
1528
            }
1529
            Data::Flags(flags) => {
32✔
1530
                ctx.write_all(b"* FLAGS (")?;
32✔
1531
                join_serializable(flags, b" ", ctx)?;
32✔
1532
                ctx.write_all(b")")?;
32✔
1533
            }
1534
            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
42✔
1535
            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
42✔
1536
            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
50✔
1537
            Data::Fetch { seq, items } => {
96✔
1538
                write!(ctx, "* {seq} FETCH (")?;
96✔
1539
                join_serializable(items.as_ref(), b" ", ctx)?;
96✔
1540
                ctx.write_all(b")")?;
96✔
1541
            }
1542
            Data::Enabled { capabilities } => {
16✔
1543
                write!(ctx, "* ENABLED")?;
16✔
1544

1545
                for cap in capabilities {
32✔
1546
                    ctx.write_all(b" ")?;
16✔
1547
                    cap.encode_ctx(ctx)?;
16✔
1548
                }
1549
            }
1550
            Data::Quota { root, quotas } => {
14✔
1551
                ctx.write_all(b"* QUOTA ")?;
14✔
1552
                root.encode_ctx(ctx)?;
14✔
1553
                ctx.write_all(b" (")?;
14✔
1554
                join_serializable(quotas.as_ref(), b" ", ctx)?;
14✔
1555
                ctx.write_all(b")")?;
14✔
1556
            }
1557
            Data::QuotaRoot { mailbox, roots } => {
10✔
1558
                ctx.write_all(b"* QUOTAROOT ")?;
10✔
1559
                mailbox.encode_ctx(ctx)?;
10✔
1560
                for root in roots {
20✔
1561
                    ctx.write_all(b" ")?;
10✔
1562
                    root.encode_ctx(ctx)?;
10✔
1563
                }
1564
            }
1565
            #[cfg(feature = "ext_id")]
1566
            Data::Id { parameters } => {
2✔
1567
                ctx.write_all(b"* ID ")?;
2✔
1568

1569
                match parameters {
2✔
1570
                    Some(parameters) => {
×
1571
                        if let Some((first, tail)) = parameters.split_first() {
×
1572
                            ctx.write_all(b"(")?;
×
1573

1574
                            first.0.encode_ctx(ctx)?;
×
1575
                            ctx.write_all(b" ")?;
×
1576
                            first.1.encode_ctx(ctx)?;
×
1577

1578
                            for parameter in tail {
×
1579
                                ctx.write_all(b" ")?;
×
1580
                                parameter.0.encode_ctx(ctx)?;
×
1581
                                ctx.write_all(b" ")?;
×
1582
                                parameter.1.encode_ctx(ctx)?;
×
1583
                            }
1584

1585
                            ctx.write_all(b")")?;
×
1586
                        } else {
1587
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
1588
                            {
1589
                                ctx.write_all(b"()")?;
1590
                            }
1591
                            #[cfg(feature = "quirk_id_empty_to_nil")]
1592
                            {
1593
                                ctx.write_all(b"NIL")?;
×
1594
                            }
1595
                        }
1596
                    }
1597
                    None => {
1598
                        ctx.write_all(b"NIL")?;
2✔
1599
                    }
1600
                }
1601
            }
1602
            #[cfg(feature = "ext_metadata")]
1603
            Data::Metadata { mailbox, items } => {
4✔
1604
                ctx.write_all(b"* METADATA ")?;
4✔
1605
                mailbox.encode_ctx(ctx)?;
4✔
1606
                ctx.write_all(b" ")?;
4✔
1607
                items.encode_ctx(ctx)?;
4✔
1608
            }
1609
            #[cfg(feature = "ext_condstore_qresync")]
1610
            Data::Vanished {
1611
                earlier,
×
1612
                known_uids,
×
1613
            } => {
1614
                ctx.write_all(b"* VANISHED")?;
×
1615
                if *earlier {
×
1616
                    ctx.write_all(b" (EARLIER)")?;
×
1617
                }
×
1618
                ctx.write_all(b" ")?;
×
1619
                known_uids.encode_ctx(ctx)?;
×
1620
            }
1621

1622
            #[cfg(feature = "ext_namespace")]
1623
            Data::Namespace {
NEW
1624
                personal,
×
NEW
1625
                other,
×
NEW
1626
                shared,
×
1627
            } => {
NEW
1628
                ctx.write_all(b"* NAMESPACE ")?;
×
NEW
1629
                encode_namespaces(ctx, personal)?;
×
NEW
1630
                ctx.write_all(b" ")?;
×
NEW
1631
                encode_namespaces(ctx, other)?;
×
NEW
1632
                ctx.write_all(b" ")?;
×
NEW
1633
                encode_namespaces(ctx, shared)?;
×
1634
            }
1635
        }
1636

1637
        ctx.write_all(b"\r\n")
718✔
1638
    }
718✔
1639
}
1640

1641
impl EncodeIntoContext for FlagNameAttribute<'_> {
1642
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
1643
        write!(ctx, "{self}")
90✔
1644
    }
90✔
1645
}
1646

1647
impl EncodeIntoContext for QuotedChar {
1648
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
242✔
1649
        match self.inner() {
242✔
1650
            '\\' => ctx.write_all(b"\\\\"),
×
1651
            '"' => ctx.write_all(b"\\\""),
×
1652
            other => ctx.write_all(&[other as u8]),
242✔
1653
        }
1654
    }
242✔
1655
}
1656

1657
impl EncodeIntoContext for StatusDataItem {
1658
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
52✔
1659
        match self {
52✔
1660
            Self::Messages(count) => {
20✔
1661
                ctx.write_all(b"MESSAGES ")?;
20✔
1662
                count.encode_ctx(ctx)
20✔
1663
            }
1664
            Self::Recent(count) => {
2✔
1665
                ctx.write_all(b"RECENT ")?;
2✔
1666
                count.encode_ctx(ctx)
2✔
1667
            }
1668
            Self::UidNext(next) => {
18✔
1669
                ctx.write_all(b"UIDNEXT ")?;
18✔
1670
                next.encode_ctx(ctx)
18✔
1671
            }
1672
            Self::UidValidity(identifier) => {
2✔
1673
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1674
                identifier.encode_ctx(ctx)
2✔
1675
            }
1676
            Self::Unseen(count) => {
2✔
1677
                ctx.write_all(b"UNSEEN ")?;
2✔
1678
                count.encode_ctx(ctx)
2✔
1679
            }
1680
            Self::Deleted(count) => {
4✔
1681
                ctx.write_all(b"DELETED ")?;
4✔
1682
                count.encode_ctx(ctx)
4✔
1683
            }
1684
            Self::DeletedStorage(count) => {
4✔
1685
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1686
                count.encode_ctx(ctx)
4✔
1687
            }
1688
            #[cfg(feature = "ext_condstore_qresync")]
1689
            Self::HighestModSeq(value) => {
×
1690
                ctx.write_all(b"HIGHESTMODSEQ ")?;
×
1691
                value.encode_ctx(ctx)
×
1692
            }
1693
        }
1694
    }
52✔
1695
}
1696

1697
impl EncodeIntoContext for MessageDataItem<'_> {
1698
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
184✔
1699
        match self {
184✔
1700
            Self::BodyExt {
16✔
1701
                section,
16✔
1702
                origin,
16✔
1703
                data,
16✔
1704
            } => {
16✔
1705
                ctx.write_all(b"BODY[")?;
16✔
1706
                if let Some(section) = section {
16✔
1707
                    section.encode_ctx(ctx)?;
8✔
1708
                }
8✔
1709
                ctx.write_all(b"]")?;
16✔
1710
                if let Some(origin) = origin {
16✔
1711
                    write!(ctx, "<{origin}>")?;
2✔
1712
                }
14✔
1713
                ctx.write_all(b" ")?;
16✔
1714
                data.encode_ctx(ctx)
16✔
1715
            }
1716
            // FIXME: do not return body-ext-1part and body-ext-mpart here
1717
            Self::Body(body) => {
10✔
1718
                ctx.write_all(b"BODY ")?;
10✔
1719
                body.encode_ctx(ctx)
10✔
1720
            }
1721
            Self::BodyStructure(body) => {
4✔
1722
                ctx.write_all(b"BODYSTRUCTURE ")?;
4✔
1723
                body.encode_ctx(ctx)
4✔
1724
            }
1725
            Self::Envelope(envelope) => {
10✔
1726
                ctx.write_all(b"ENVELOPE ")?;
10✔
1727
                envelope.encode_ctx(ctx)
10✔
1728
            }
1729
            Self::Flags(flags) => {
82✔
1730
                ctx.write_all(b"FLAGS (")?;
82✔
1731
                join_serializable(flags, b" ", ctx)?;
82✔
1732
                ctx.write_all(b")")
82✔
1733
            }
1734
            Self::InternalDate(datetime) => {
10✔
1735
                ctx.write_all(b"INTERNALDATE ")?;
10✔
1736
                datetime.encode_ctx(ctx)
10✔
1737
            }
1738
            Self::Rfc822(nstring) => {
4✔
1739
                ctx.write_all(b"RFC822 ")?;
4✔
1740
                nstring.encode_ctx(ctx)
4✔
1741
            }
1742
            Self::Rfc822Header(nstring) => {
2✔
1743
                ctx.write_all(b"RFC822.HEADER ")?;
2✔
1744
                nstring.encode_ctx(ctx)
2✔
1745
            }
1746
            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
18✔
1747
            Self::Rfc822Text(nstring) => {
2✔
1748
                ctx.write_all(b"RFC822.TEXT ")?;
2✔
1749
                nstring.encode_ctx(ctx)
2✔
1750
            }
1751
            Self::Uid(uid) => write!(ctx, "UID {uid}"),
26✔
1752
            Self::Binary { section, value } => {
×
1753
                ctx.write_all(b"BINARY[")?;
×
1754
                join_serializable(section, b".", ctx)?;
×
1755
                ctx.write_all(b"] ")?;
×
1756
                value.encode_ctx(ctx)
×
1757
            }
1758
            Self::BinarySize { section, size } => {
×
1759
                ctx.write_all(b"BINARY.SIZE[")?;
×
1760
                join_serializable(section, b".", ctx)?;
×
1761
                ctx.write_all(b"] ")?;
×
1762
                size.encode_ctx(ctx)
×
1763
            }
1764
            #[cfg(feature = "ext_condstore_qresync")]
1765
            Self::ModSeq(value) => write!(ctx, "MODSEQ {value}"),
×
1766
        }
1767
    }
184✔
1768
}
1769

1770
impl EncodeIntoContext for NString<'_> {
1771
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
318✔
1772
        match &self.0 {
318✔
1773
            Some(imap_str) => imap_str.encode_ctx(ctx),
188✔
1774
            None => ctx.write_all(b"NIL"),
130✔
1775
        }
1776
    }
318✔
1777
}
1778

1779
impl EncodeIntoContext for NString8<'_> {
1780
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1781
        match self {
8✔
1782
            NString8::NString(nstring) => nstring.encode_ctx(ctx),
6✔
1783
            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
2✔
1784
        }
1785
    }
8✔
1786
}
1787

1788
impl EncodeIntoContext for BodyStructure<'_> {
1789
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1790
        ctx.write_all(b"(")?;
32✔
1791
        match self {
32✔
1792
            BodyStructure::Single {
1793
                body,
20✔
1794
                extension_data: extension,
20✔
1795
            } => {
1796
                body.encode_ctx(ctx)?;
20✔
1797
                if let Some(extension) = extension {
20✔
1798
                    ctx.write_all(b" ")?;
4✔
1799
                    extension.encode_ctx(ctx)?;
4✔
1800
                }
16✔
1801
            }
1802
            BodyStructure::Multi {
1803
                bodies,
12✔
1804
                subtype,
12✔
1805
                extension_data,
12✔
1806
            } => {
1807
                for body in bodies.as_ref() {
12✔
1808
                    body.encode_ctx(ctx)?;
12✔
1809
                }
1810
                ctx.write_all(b" ")?;
12✔
1811
                subtype.encode_ctx(ctx)?;
12✔
1812

1813
                if let Some(extension) = extension_data {
12✔
1814
                    ctx.write_all(b" ")?;
×
1815
                    extension.encode_ctx(ctx)?;
×
1816
                }
12✔
1817
            }
1818
        }
1819
        ctx.write_all(b")")
32✔
1820
    }
32✔
1821
}
1822

1823
impl EncodeIntoContext for Body<'_> {
1824
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1825
        match self.specific {
20✔
1826
            SpecificFields::Basic {
1827
                r#type: ref type_,
4✔
1828
                ref subtype,
4✔
1829
            } => {
1830
                type_.encode_ctx(ctx)?;
4✔
1831
                ctx.write_all(b" ")?;
4✔
1832
                subtype.encode_ctx(ctx)?;
4✔
1833
                ctx.write_all(b" ")?;
4✔
1834
                self.basic.encode_ctx(ctx)
4✔
1835
            }
1836
            SpecificFields::Message {
1837
                ref envelope,
×
1838
                ref body_structure,
×
1839
                number_of_lines,
×
1840
            } => {
1841
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
1842
                self.basic.encode_ctx(ctx)?;
×
1843
                ctx.write_all(b" ")?;
×
1844
                envelope.encode_ctx(ctx)?;
×
1845
                ctx.write_all(b" ")?;
×
1846
                body_structure.encode_ctx(ctx)?;
×
1847
                ctx.write_all(b" ")?;
×
1848
                write!(ctx, "{number_of_lines}")
×
1849
            }
1850
            SpecificFields::Text {
1851
                ref subtype,
16✔
1852
                number_of_lines,
16✔
1853
            } => {
1854
                ctx.write_all(b"\"TEXT\" ")?;
16✔
1855
                subtype.encode_ctx(ctx)?;
16✔
1856
                ctx.write_all(b" ")?;
16✔
1857
                self.basic.encode_ctx(ctx)?;
16✔
1858
                ctx.write_all(b" ")?;
16✔
1859
                write!(ctx, "{number_of_lines}")
16✔
1860
            }
1861
        }
1862
    }
20✔
1863
}
1864

1865
impl EncodeIntoContext for BasicFields<'_> {
1866
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1867
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
20✔
1868
        ctx.write_all(b" ")?;
20✔
1869
        self.id.encode_ctx(ctx)?;
20✔
1870
        ctx.write_all(b" ")?;
20✔
1871
        self.description.encode_ctx(ctx)?;
20✔
1872
        ctx.write_all(b" ")?;
20✔
1873
        self.content_transfer_encoding.encode_ctx(ctx)?;
20✔
1874
        ctx.write_all(b" ")?;
20✔
1875
        write!(ctx, "{}", self.size)
20✔
1876
    }
20✔
1877
}
1878

1879
impl EncodeIntoContext for Envelope<'_> {
1880
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10✔
1881
        ctx.write_all(b"(")?;
10✔
1882
        self.date.encode_ctx(ctx)?;
10✔
1883
        ctx.write_all(b" ")?;
10✔
1884
        self.subject.encode_ctx(ctx)?;
10✔
1885
        ctx.write_all(b" ")?;
10✔
1886
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
10✔
1887
        ctx.write_all(b" ")?;
10✔
1888
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
10✔
1889
        ctx.write_all(b" ")?;
10✔
1890
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
10✔
1891
        ctx.write_all(b" ")?;
10✔
1892
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
10✔
1893
        ctx.write_all(b" ")?;
10✔
1894
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
10✔
1895
        ctx.write_all(b" ")?;
10✔
1896
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
10✔
1897
        ctx.write_all(b" ")?;
10✔
1898
        self.in_reply_to.encode_ctx(ctx)?;
10✔
1899
        ctx.write_all(b" ")?;
10✔
1900
        self.message_id.encode_ctx(ctx)?;
10✔
1901
        ctx.write_all(b")")
10✔
1902
    }
10✔
1903
}
1904

1905
impl EncodeIntoContext for Address<'_> {
1906
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
1907
        ctx.write_all(b"(")?;
48✔
1908
        self.name.encode_ctx(ctx)?;
48✔
1909
        ctx.write_all(b" ")?;
48✔
1910
        self.adl.encode_ctx(ctx)?;
48✔
1911
        ctx.write_all(b" ")?;
48✔
1912
        self.mailbox.encode_ctx(ctx)?;
48✔
1913
        ctx.write_all(b" ")?;
48✔
1914
        self.host.encode_ctx(ctx)?;
48✔
1915
        ctx.write_all(b")")?;
48✔
1916

1917
        Ok(())
48✔
1918
    }
48✔
1919
}
1920

1921
impl EncodeIntoContext for SinglePartExtensionData<'_> {
1922
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1923
        self.md5.encode_ctx(ctx)?;
6✔
1924

1925
        if let Some(disposition) = &self.tail {
6✔
1926
            ctx.write_all(b" ")?;
6✔
1927
            disposition.encode_ctx(ctx)?;
6✔
1928
        }
×
1929

1930
        Ok(())
6✔
1931
    }
6✔
1932
}
1933

1934
impl EncodeIntoContext for MultiPartExtensionData<'_> {
1935
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1936
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1937

1938
        if let Some(disposition) = &self.tail {
×
1939
            ctx.write_all(b" ")?;
×
1940
            disposition.encode_ctx(ctx)?;
×
1941
        }
×
1942

1943
        Ok(())
×
1944
    }
×
1945
}
1946

1947
impl EncodeIntoContext for Disposition<'_> {
1948
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1949
        match &self.disposition {
6✔
1950
            Some((s, param)) => {
×
1951
                ctx.write_all(b"(")?;
×
1952
                s.encode_ctx(ctx)?;
×
1953
                ctx.write_all(b" ")?;
×
1954
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1955
                ctx.write_all(b")")?;
×
1956
            }
1957
            None => ctx.write_all(b"NIL")?,
6✔
1958
        }
1959

1960
        if let Some(language) = &self.tail {
6✔
1961
            ctx.write_all(b" ")?;
6✔
1962
            language.encode_ctx(ctx)?;
6✔
1963
        }
×
1964

1965
        Ok(())
6✔
1966
    }
6✔
1967
}
1968

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

1973
        if let Some(location) = &self.tail {
6✔
1974
            ctx.write_all(b" ")?;
6✔
1975
            location.encode_ctx(ctx)?;
6✔
1976
        }
×
1977

1978
        Ok(())
6✔
1979
    }
6✔
1980
}
1981

1982
impl EncodeIntoContext for Location<'_> {
1983
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1984
        self.location.encode_ctx(ctx)?;
6✔
1985

1986
        for body_extension in &self.extensions {
10✔
1987
            ctx.write_all(b" ")?;
4✔
1988
            body_extension.encode_ctx(ctx)?;
4✔
1989
        }
1990

1991
        Ok(())
6✔
1992
    }
6✔
1993
}
1994

1995
impl EncodeIntoContext for BodyExtension<'_> {
1996
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1997
        match self {
6✔
1998
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
1999
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
2000
            BodyExtension::List(list) => {
2✔
2001
                ctx.write_all(b"(")?;
2✔
2002
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
2003
                ctx.write_all(b")")
2✔
2004
            }
2005
        }
2006
    }
6✔
2007
}
2008

2009
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
2010
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
2011
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
14✔
2012
    }
14✔
2013
}
2014

2015
impl EncodeIntoContext for CommandContinuationRequest<'_> {
2016
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
2017
        match self {
8✔
2018
            Self::Basic(continue_basic) => match continue_basic.code() {
8✔
2019
                Some(code) => {
×
2020
                    ctx.write_all(b"+ [")?;
×
2021
                    code.encode_ctx(ctx)?;
×
2022
                    ctx.write_all(b"] ")?;
×
2023
                    continue_basic.text().encode_ctx(ctx)?;
×
2024
                    ctx.write_all(b"\r\n")
×
2025
                }
2026
                None => {
2027
                    ctx.write_all(b"+ ")?;
8✔
2028
                    continue_basic.text().encode_ctx(ctx)?;
8✔
2029
                    ctx.write_all(b"\r\n")
8✔
2030
                }
2031
            },
2032
            Self::Base64(data) => {
×
2033
                ctx.write_all(b"+ ")?;
×
2034
                ctx.write_all(base64.encode(data).as_bytes())?;
×
2035
                ctx.write_all(b"\r\n")
×
2036
            }
2037
        }
2038
    }
8✔
2039
}
2040

2041
pub(crate) mod utils {
2042
    use std::io::Write;
2043

2044
    use super::{EncodeContext, EncodeIntoContext};
2045

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

2048
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
2049

2050
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
916✔
2051
        elements: &[I],
916✔
2052
        sep: &[u8],
916✔
2053
        ctx: &mut EncodeContext,
916✔
2054
    ) -> std::io::Result<()> {
916✔
2055
        if let Some((last, head)) = elements.split_last() {
916✔
2056
            for item in head {
1,346✔
2057
                item.encode_ctx(ctx)?;
594✔
2058
                ctx.write_all(sep)?;
594✔
2059
            }
2060

2061
            last.encode_ctx(ctx)
752✔
2062
        } else {
2063
            Ok(())
164✔
2064
        }
2065
    }
916✔
2066

2067
    impl<T> EncodeIntoContext for List1OrNil<'_, T>
2068
    where
2069
        T: EncodeIntoContext,
2070
    {
2071
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
66✔
2072
            if let Some((last, head)) = self.0.split_last() {
66✔
2073
                ctx.write_all(b"(")?;
40✔
2074

2075
                for item in head {
48✔
2076
                    item.encode_ctx(ctx)?;
8✔
2077
                    ctx.write_all(self.1)?;
8✔
2078
                }
2079

2080
                last.encode_ctx(ctx)?;
40✔
2081

2082
                ctx.write_all(b")")
40✔
2083
            } else {
2084
                ctx.write_all(b"NIL")
26✔
2085
            }
2086
        }
66✔
2087
    }
2088

2089
    impl<T> EncodeIntoContext for List1AttributeValueOrNil<'_, T>
2090
    where
2091
        T: EncodeIntoContext,
2092
    {
2093
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
2094
            if let Some((last, head)) = self.0.split_last() {
20✔
2095
                ctx.write_all(b"(")?;
8✔
2096

2097
                for (attribute, value) in head {
8✔
2098
                    attribute.encode_ctx(ctx)?;
×
2099
                    ctx.write_all(b" ")?;
×
2100
                    value.encode_ctx(ctx)?;
×
2101
                    ctx.write_all(b" ")?;
×
2102
                }
2103

2104
                let (attribute, value) = last;
8✔
2105
                attribute.encode_ctx(ctx)?;
8✔
2106
                ctx.write_all(b" ")?;
8✔
2107
                value.encode_ctx(ctx)?;
8✔
2108

2109
                ctx.write_all(b")")
8✔
2110
            } else {
2111
                ctx.write_all(b"NIL")
12✔
2112
            }
2113
        }
20✔
2114
    }
2115
}
2116

2117
#[cfg(test)]
2118
mod tests {
2119
    use std::num::NonZeroU32;
2120

2121
    use imap_types::{
2122
        auth::AuthMechanism,
2123
        command::{Command, CommandBody},
2124
        core::{AString, Literal, NString, Vec1},
2125
        fetch::MessageDataItem,
2126
        response::{Data, Response},
2127
        utils::escape_byte_string,
2128
    };
2129

2130
    use super::*;
2131

2132
    #[test]
2133
    fn test_api_encoder_usage() {
2✔
2134
        let cmd = Command::new(
2✔
2135
            "A",
2136
            CommandBody::login(
2✔
2137
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
2138
                "password",
2139
            )
2140
            .unwrap(),
2✔
2141
        )
2142
        .unwrap();
2✔
2143

2144
        // Dump.
2145
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
2146

2147
        // Encoded.
2148
        let encoded = CommandCodec::default().encode(&cmd);
2✔
2149

2150
        let mut out = Vec::new();
2✔
2151

2152
        for x in encoded {
8✔
2153
            match x {
6✔
2154
                Fragment::Line { data } => {
4✔
2155
                    println!("C: {}", escape_byte_string(&data));
4✔
2156
                    out.extend_from_slice(&data);
4✔
2157
                }
4✔
2158
                Fragment::Literal { data, mode } => {
2✔
2159
                    match mode {
2✔
2160
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
2161
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
2162
                    }
2163

2164
                    println!("C: {}", escape_byte_string(&data));
2✔
2165
                    out.extend_from_slice(&data);
2✔
2166
                }
2167
            }
2168
        }
2169

2170
        assert_eq!(got_encoded, out);
2✔
2171
    }
2✔
2172

2173
    #[test]
2174
    fn test_encode_command() {
2✔
2175
        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2✔
2176
            (
2✔
2177
                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2✔
2178
                [Fragment::Line {
2✔
2179
                    data: b"A LOGIN alice pass\r\n".to_vec(),
2✔
2180
                }]
2✔
2181
                .as_ref(),
2✔
2182
            ),
2✔
2183
            (
2✔
2184
                Command::new(
2✔
2185
                    "A",
2✔
2186
                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2✔
2187
                )
2✔
2188
                .unwrap(),
2✔
2189
                [
2✔
2190
                    Fragment::Line {
2✔
2191
                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
2192
                    },
2✔
2193
                    Fragment::Literal {
2✔
2194
                        data: b"\xCA\xFE".to_vec(),
2✔
2195
                        mode: LiteralMode::Sync,
2✔
2196
                    },
2✔
2197
                    Fragment::Line {
2✔
2198
                        data: b"\r\n".to_vec(),
2✔
2199
                    },
2✔
2200
                ]
2✔
2201
                .as_ref(),
2✔
2202
            ),
2✔
2203
            (
2✔
2204
                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2✔
2205
                [Fragment::Line {
2✔
2206
                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2✔
2207
                }]
2✔
2208
                .as_ref(),
2✔
2209
            ),
2✔
2210
            (
2✔
2211
                Command::new(
2✔
2212
                    "A",
2✔
2213
                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2✔
2214
                )
2✔
2215
                .unwrap(),
2✔
2216
                [Fragment::Line {
2✔
2217
                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2✔
2218
                }]
2✔
2219
                .as_ref(),
2✔
2220
            ),
2✔
2221
            (
2✔
2222
                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2✔
2223
                [Fragment::Line {
2✔
2224
                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2✔
2225
                }]
2✔
2226
                .as_ref(),
2✔
2227
            ),
2✔
2228
            (
2✔
2229
                Command::new(
2✔
2230
                    "A",
2✔
2231
                    CommandBody::authenticate_with_ir(
2✔
2232
                        AuthMechanism::Plain,
2✔
2233
                        b"\x00alice\x00pass".as_ref(),
2✔
2234
                    ),
2✔
2235
                )
2✔
2236
                .unwrap(),
2✔
2237
                [Fragment::Line {
2✔
2238
                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2✔
2239
                }]
2✔
2240
                .as_ref(),
2✔
2241
            ),
2✔
2242
        ]);
2✔
2243
    }
2✔
2244

2245
    #[test]
2246
    fn test_encode_response() {
2✔
2247
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
2248
            (
2✔
2249
                Response::Data(Data::Fetch {
2✔
2250
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2251
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2252
                        section: None,
2✔
2253
                        origin: None,
2✔
2254
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
2255
                    }),
2✔
2256
                }),
2✔
2257
                [
2✔
2258
                    Fragment::Line {
2✔
2259
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
2260
                    },
2✔
2261
                    Fragment::Literal {
2✔
2262
                        data: b"ABCDE".to_vec(),
2✔
2263
                        mode: LiteralMode::Sync,
2✔
2264
                    },
2✔
2265
                    Fragment::Line {
2✔
2266
                        data: b")\r\n".to_vec(),
2✔
2267
                    },
2✔
2268
                ]
2✔
2269
                .as_ref(),
2✔
2270
            ),
2✔
2271
            (
2✔
2272
                Response::Data(Data::Fetch {
2✔
2273
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2274
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2275
                        section: None,
2✔
2276
                        origin: None,
2✔
2277
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
2278
                    }),
2✔
2279
                }),
2✔
2280
                [
2✔
2281
                    Fragment::Line {
2✔
2282
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
2283
                    },
2✔
2284
                    Fragment::Literal {
2✔
2285
                        data: b"ABCDE".to_vec(),
2✔
2286
                        mode: LiteralMode::NonSync,
2✔
2287
                    },
2✔
2288
                    Fragment::Line {
2✔
2289
                        data: b")\r\n".to_vec(),
2✔
2290
                    },
2✔
2291
                ]
2✔
2292
                .as_ref(),
2✔
2293
            ),
2✔
2294
        ])
2✔
2295
    }
2✔
2296

2297
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
2298
    where
4✔
2299
        E: Encoder<Message<'a> = M> + Default,
4✔
2300
        F: AsRef<[Fragment]>,
4✔
2301
    {
2302
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
2303
            println!("# Testing {i}");
16✔
2304

2305
            let encoder = E::default().encode(obj);
16✔
2306
            let actions = actions.as_ref();
16✔
2307

2308
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2309
        }
2310
    }
4✔
2311
}
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