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

duesee / imap-codec / 18421636743

11 Oct 2025 12:31AM UTC coverage: 91.075% (-0.7%) from 91.768%
18421636743

Pull #669

github

web-flow
Merge 47424d20e into 450fdf51c
Pull Request #669: feat: Implement RFC 2342 (NAMESPACE) in imap-codec

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

5 existing lines in 1 file now uncovered.

10306 of 11316 relevant lines covered (91.07%)

934.56 hits per line

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

82.52
/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> {
19,284✔
223
        self.accumulator.extend_from_slice(buf);
19,284✔
224
        Ok(buf.len())
19,284✔
225
    }
19,284✔
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
                ctx.write_all(b" ")?;
10✔
306
                mechanism.encode_ctx(ctx)?;
10✔
307

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

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

321
                Ok(())
10✔
322
            }
323
            CommandBody::Login { username, password } => {
56✔
324
                ctx.write_all(b"LOGIN")?;
56✔
325
                ctx.write_all(b" ")?;
56✔
326
                username.encode_ctx(ctx)?;
56✔
327
                ctx.write_all(b" ")?;
56✔
328
                password.declassify().encode_ctx(ctx)
56✔
329
            }
330
            CommandBody::Select {
331
                mailbox,
20✔
332
                #[cfg(feature = "ext_condstore_qresync")]
333
                parameters,
20✔
334
            } => {
335
                ctx.write_all(b"SELECT")?;
20✔
336
                ctx.write_all(b" ")?;
20✔
337
                mailbox.encode_ctx(ctx)?;
20✔
338

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

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

358
                #[cfg(feature = "ext_condstore_qresync")]
359
                if !parameters.is_empty() {
8✔
360
                    ctx.write_all(b" (")?;
×
361
                    join_serializable(parameters, b" ", ctx)?;
×
362
                    ctx.write_all(b")")?;
×
363
                }
8✔
364

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

439
                if !flags.is_empty() {
×
440
                    ctx.write_all(b" ")?;
×
441
                    ctx.write_all(b"(")?;
×
442
                    join_serializable(flags, b" ", ctx)?;
×
443
                    ctx.write_all(b")")?;
×
444
                }
×
445

446
                if let Some(date) = date {
×
447
                    ctx.write_all(b" ")?;
×
448
                    date.encode_ctx(ctx)?;
×
449
                }
×
450

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

525
                sequence_set.encode_ctx(ctx)?;
32✔
526
                ctx.write_all(b" ")?;
32✔
527
                macro_or_item_names.encode_ctx(ctx)?;
32✔
528

529
                #[cfg(feature = "ext_condstore_qresync")]
530
                if !modifiers.is_empty() {
32✔
531
                    ctx.write_all(b" (")?;
×
532
                    join_serializable(modifiers, b" ", ctx)?;
×
533
                    ctx.write_all(b")")?;
×
534
                }
32✔
535

536
                Ok(())
32✔
537
            }
538
            CommandBody::Store {
539
                sequence_set,
16✔
540
                kind,
16✔
541
                response,
16✔
542
                flags,
16✔
543
                uid,
16✔
544
                #[cfg(feature = "ext_condstore_qresync")]
545
                modifiers,
16✔
546
            } => {
547
                if *uid {
16✔
548
                    ctx.write_all(b"UID STORE ")?;
×
549
                } else {
550
                    ctx.write_all(b"STORE ")?;
16✔
551
                }
552

553
                sequence_set.encode_ctx(ctx)?;
16✔
554
                ctx.write_all(b" ")?;
16✔
555

556
                #[cfg(feature = "ext_condstore_qresync")]
557
                if !modifiers.is_empty() {
16✔
558
                    ctx.write_all(b"(")?;
×
559
                    join_serializable(modifiers, b" ", ctx)?;
×
560
                    ctx.write_all(b") ")?;
×
561
                }
16✔
562

563
                match kind {
16✔
564
                    StoreType::Add => ctx.write_all(b"+")?,
16✔
565
                    StoreType::Remove => ctx.write_all(b"-")?,
×
566
                    StoreType::Replace => {}
×
567
                }
568

569
                ctx.write_all(b"FLAGS")?;
16✔
570

571
                match response {
16✔
572
                    StoreResponse::Answer => {}
16✔
573
                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
×
574
                }
575

576
                ctx.write_all(b" (")?;
16✔
577
                join_serializable(flags, b" ", ctx)?;
16✔
578
                ctx.write_all(b")")
16✔
579
            }
580
            CommandBody::Copy {
581
                sequence_set,
24✔
582
                mailbox,
24✔
583
                uid,
24✔
584
            } => {
585
                if *uid {
24✔
586
                    ctx.write_all(b"UID COPY ")?;
×
587
                } else {
588
                    ctx.write_all(b"COPY ")?;
24✔
589
                }
590
                sequence_set.encode_ctx(ctx)?;
24✔
591
                ctx.write_all(b" ")?;
24✔
592
                mailbox.encode_ctx(ctx)
24✔
593
            }
594
            CommandBody::Idle => ctx.write_all(b"IDLE"),
4✔
595
            CommandBody::Enable { capabilities } => {
22✔
596
                ctx.write_all(b"ENABLE ")?;
22✔
597
                join_serializable(capabilities.as_ref(), b" ", ctx)
22✔
598
            }
599
            CommandBody::Compress { algorithm } => {
4✔
600
                ctx.write_all(b"COMPRESS ")?;
4✔
601
                algorithm.encode_ctx(ctx)
4✔
602
            }
603
            CommandBody::GetQuota { root } => {
10✔
604
                ctx.write_all(b"GETQUOTA ")?;
10✔
605
                root.encode_ctx(ctx)
10✔
606
            }
607
            CommandBody::GetQuotaRoot { mailbox } => {
6✔
608
                ctx.write_all(b"GETQUOTAROOT ")?;
6✔
609
                mailbox.encode_ctx(ctx)
6✔
610
            }
611
            CommandBody::SetQuota { root, quotas } => {
12✔
612
                ctx.write_all(b"SETQUOTA ")?;
12✔
613
                root.encode_ctx(ctx)?;
12✔
614
                ctx.write_all(b" (")?;
12✔
615
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
616
                ctx.write_all(b")")
12✔
617
            }
618
            CommandBody::Move {
619
                sequence_set,
6✔
620
                mailbox,
6✔
621
                uid,
6✔
622
            } => {
623
                if *uid {
6✔
624
                    ctx.write_all(b"UID MOVE ")?;
2✔
625
                } else {
626
                    ctx.write_all(b"MOVE ")?;
4✔
627
                }
628
                sequence_set.encode_ctx(ctx)?;
6✔
629
                ctx.write_all(b" ")?;
6✔
630
                mailbox.encode_ctx(ctx)
6✔
631
            }
632
            #[cfg(feature = "ext_id")]
633
            CommandBody::Id { parameters } => {
8✔
634
                ctx.write_all(b"ID ")?;
8✔
635

636
                match parameters {
8✔
637
                    Some(parameters) => {
4✔
638
                        if let Some((first, tail)) = parameters.split_first() {
4✔
639
                            ctx.write_all(b"(")?;
4✔
640

641
                            first.0.encode_ctx(ctx)?;
4✔
642
                            ctx.write_all(b" ")?;
4✔
643
                            first.1.encode_ctx(ctx)?;
4✔
644

645
                            for parameter in tail {
4✔
646
                                ctx.write_all(b" ")?;
×
647
                                parameter.0.encode_ctx(ctx)?;
×
648
                                ctx.write_all(b" ")?;
×
649
                                parameter.1.encode_ctx(ctx)?;
×
650
                            }
651

652
                            ctx.write_all(b")")
4✔
653
                        } else {
654
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
655
                            {
656
                                ctx.write_all(b"()")
657
                            }
658
                            #[cfg(feature = "quirk_id_empty_to_nil")]
659
                            {
660
                                ctx.write_all(b"NIL")
×
661
                            }
662
                        }
663
                    }
664
                    None => ctx.write_all(b"NIL"),
4✔
665
                }
666
            }
667
            #[cfg(feature = "ext_metadata")]
668
            CommandBody::SetMetadata {
669
                mailbox,
6✔
670
                entry_values,
6✔
671
            } => {
672
                ctx.write_all(b"SETMETADATA ")?;
6✔
673
                mailbox.encode_ctx(ctx)?;
6✔
674
                ctx.write_all(b" (")?;
6✔
675
                join_serializable(entry_values.as_ref(), b" ", ctx)?;
6✔
676
                ctx.write_all(b")")
6✔
677
            }
678
            #[cfg(feature = "ext_metadata")]
679
            CommandBody::GetMetadata {
680
                options,
14✔
681
                mailbox,
14✔
682
                entries,
14✔
683
            } => {
684
                ctx.write_all(b"GETMETADATA")?;
14✔
685

686
                if !options.is_empty() {
14✔
687
                    ctx.write_all(b" (")?;
10✔
688
                    join_serializable(options, b" ", ctx)?;
10✔
689
                    ctx.write_all(b")")?;
10✔
690
                }
4✔
691

692
                ctx.write_all(b" ")?;
14✔
693
                mailbox.encode_ctx(ctx)?;
14✔
694

695
                ctx.write_all(b" ")?;
14✔
696

697
                if entries.as_ref().len() == 1 {
14✔
698
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
699
                } else {
700
                    ctx.write_all(b"(")?;
×
701
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
702
                    ctx.write_all(b")")
×
703
                }
704
            }
705
            #[cfg(feature = "ext_namespace")]
NEW
706
            &CommandBody::Namespace => ctx.write_all(b"NAMESPACE"),
×
707
        }
708
    }
698✔
709
}
710

711
#[cfg(feature = "ext_condstore_qresync")]
712
impl EncodeIntoContext for FetchModifier {
713
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
714
        match self {
×
715
            FetchModifier::ChangedSince(since) => write!(ctx, "CHANGEDSINCE {since}"),
×
716
            FetchModifier::Vanished => write!(ctx, "VANISHED"),
×
717
        }
718
    }
×
719
}
720

721
#[cfg(feature = "ext_condstore_qresync")]
722
impl EncodeIntoContext for StoreModifier {
723
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
724
        match self {
×
725
            StoreModifier::UnchangedSince(since) => write!(ctx, "UNCHANGEDSINCE {since}"),
×
726
        }
727
    }
×
728
}
729

730
#[cfg(feature = "ext_condstore_qresync")]
731
impl EncodeIntoContext for SelectParameter {
732
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
733
        match self {
×
734
            SelectParameter::CondStore => write!(ctx, "CONDSTORE"),
×
735
            SelectParameter::QResync {
736
                uid_validity,
×
737
                mod_sequence_value,
×
738
                known_uids,
×
739
                seq_match_data,
×
740
            } => {
741
                write!(ctx, "QRESYNC (")?;
×
742
                uid_validity.encode_ctx(ctx)?;
×
743
                write!(ctx, " ")?;
×
744
                mod_sequence_value.encode_ctx(ctx)?;
×
745

746
                if let Some(known_uids) = known_uids {
×
747
                    write!(ctx, " ")?;
×
748
                    known_uids.encode_ctx(ctx)?;
×
749
                }
×
750

751
                if let Some((known_sequence_set, known_uid_set)) = seq_match_data {
×
752
                    write!(ctx, " (")?;
×
753
                    known_sequence_set.encode_ctx(ctx)?;
×
754
                    write!(ctx, " ")?;
×
755
                    known_uid_set.encode_ctx(ctx)?;
×
756
                    write!(ctx, ")")?;
×
757
                }
×
758

759
                write!(ctx, ")")
×
760
            }
761
        }
762
    }
×
763
}
764

765
impl EncodeIntoContext for AuthMechanism<'_> {
766
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
22✔
767
        write!(ctx, "{self}")
22✔
768
    }
22✔
769
}
770

771
impl EncodeIntoContext for AuthenticateData<'_> {
772
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
773
        match self {
8✔
774
            Self::Continue(data) => {
6✔
775
                let encoded = base64.encode(data.declassify());
6✔
776
                ctx.write_all(encoded.as_bytes())?;
6✔
777
                ctx.write_all(b"\r\n")
6✔
778
            }
779
            Self::Cancel => ctx.write_all(b"*\r\n"),
2✔
780
        }
781
    }
8✔
782
}
783

784
impl EncodeIntoContext for AString<'_> {
785
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
794✔
786
        match self {
794✔
787
            AString::Atom(atom) => atom.encode_ctx(ctx),
580✔
788
            AString::String(imap_str) => imap_str.encode_ctx(ctx),
214✔
789
        }
790
    }
794✔
791
}
792

793
impl EncodeIntoContext for Atom<'_> {
794
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
54✔
795
        ctx.write_all(self.inner().as_bytes())
54✔
796
    }
54✔
797
}
798

799
impl EncodeIntoContext for AtomExt<'_> {
800
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
580✔
801
        ctx.write_all(self.inner().as_bytes())
580✔
802
    }
580✔
803
}
804

805
impl EncodeIntoContext for IString<'_> {
806
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
518✔
807
        match self {
518✔
808
            Self::Literal(val) => val.encode_ctx(ctx),
44✔
809
            Self::Quoted(val) => val.encode_ctx(ctx),
474✔
810
        }
811
    }
518✔
812
}
813

814
impl EncodeIntoContext for Literal<'_> {
815
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
816
        match self.mode() {
44✔
817
            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
30✔
818
            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
14✔
819
        }
820

821
        ctx.push_line();
44✔
822
        ctx.write_all(self.as_ref())?;
44✔
823
        ctx.push_literal(self.mode());
44✔
824

825
        Ok(())
44✔
826
    }
44✔
827
}
828

829
impl EncodeIntoContext for Quoted<'_> {
830
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
482✔
831
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
482✔
832
    }
482✔
833
}
834

835
impl EncodeIntoContext for Mailbox<'_> {
836
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
616✔
837
        match self {
616✔
838
            Mailbox::Inbox => ctx.write_all(b"INBOX"),
80✔
839
            Mailbox::Other(other) => other.encode_ctx(ctx),
536✔
840
        }
841
    }
616✔
842
}
843

844
impl EncodeIntoContext for MailboxOther<'_> {
845
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
536✔
846
        self.inner().encode_ctx(ctx)
536✔
847
    }
536✔
848
}
849

850
impl EncodeIntoContext for ListMailbox<'_> {
851
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
852
        match self {
120✔
853
            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
80✔
854
            ListMailbox::String(istr) => istr.encode_ctx(ctx),
40✔
855
        }
856
    }
120✔
857
}
858

859
impl EncodeIntoContext for ListCharString<'_> {
860
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
80✔
861
        ctx.write_all(self.as_ref())
80✔
862
    }
80✔
863
}
864

865
impl EncodeIntoContext for StatusDataItemName {
866
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
36✔
867
        match self {
36✔
868
            Self::Messages => ctx.write_all(b"MESSAGES"),
12✔
869
            Self::Recent => ctx.write_all(b"RECENT"),
2✔
870
            Self::UidNext => ctx.write_all(b"UIDNEXT"),
10✔
871
            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
2✔
872
            Self::Unseen => ctx.write_all(b"UNSEEN"),
2✔
873
            Self::Deleted => ctx.write_all(b"DELETED"),
4✔
874
            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
4✔
875
            #[cfg(feature = "ext_condstore_qresync")]
876
            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
×
877
        }
878
    }
36✔
879
}
880

881
impl EncodeIntoContext for Flag<'_> {
882
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
312✔
883
        write!(ctx, "{self}")
312✔
884
    }
312✔
885
}
886

887
impl EncodeIntoContext for FlagFetch<'_> {
888
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
120✔
889
        match self {
120✔
890
            Self::Flag(flag) => flag.encode_ctx(ctx),
120✔
891
            Self::Recent => ctx.write_all(b"\\Recent"),
×
892
        }
893
    }
120✔
894
}
895

896
impl EncodeIntoContext for FlagPerm<'_> {
897
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
24✔
898
        match self {
24✔
899
            Self::Flag(flag) => flag.encode_ctx(ctx),
16✔
900
            Self::Asterisk => ctx.write_all(b"\\*"),
8✔
901
        }
902
    }
24✔
903
}
904

905
impl EncodeIntoContext for DateTime {
906
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
907
        self.as_ref().encode_ctx(ctx)
14✔
908
    }
14✔
909
}
910

911
impl EncodeIntoContext for Charset<'_> {
912
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
58✔
913
        match self {
58✔
914
            Charset::Atom(atom) => atom.encode_ctx(ctx),
50✔
915
            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
8✔
916
        }
917
    }
58✔
918
}
919

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

1039
impl EncodeIntoContext for SequenceSet {
1040
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
88✔
1041
        join_serializable(self.0.as_ref(), b",", ctx)
88✔
1042
    }
88✔
1043
}
1044

1045
impl EncodeIntoContext for Sequence {
1046
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
94✔
1047
        match self {
94✔
1048
            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
38✔
1049
            Sequence::Range(from, to) => {
56✔
1050
                from.encode_ctx(ctx)?;
56✔
1051
                ctx.write_all(b":")?;
56✔
1052
                to.encode_ctx(ctx)
56✔
1053
            }
1054
        }
1055
    }
94✔
1056
}
1057

1058
impl EncodeIntoContext for SeqOrUid {
1059
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
150✔
1060
        match self {
150✔
1061
            SeqOrUid::Value(number) => write!(ctx, "{number}"),
140✔
1062
            SeqOrUid::Asterisk => ctx.write_all(b"*"),
10✔
1063
        }
1064
    }
150✔
1065
}
1066

1067
impl EncodeIntoContext for NaiveDate {
1068
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
1069
        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
44✔
1070
    }
44✔
1071
}
1072

1073
impl EncodeIntoContext for MacroOrMessageDataItemNames<'_> {
1074
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1075
        match self {
32✔
1076
            Self::Macro(m) => m.encode_ctx(ctx),
8✔
1077
            Self::MessageDataItemNames(item_names) => {
24✔
1078
                if item_names.len() == 1 {
24✔
1079
                    item_names[0].encode_ctx(ctx)
16✔
1080
                } else {
1081
                    ctx.write_all(b"(")?;
8✔
1082
                    join_serializable(item_names.as_slice(), b" ", ctx)?;
8✔
1083
                    ctx.write_all(b")")
8✔
1084
                }
1085
            }
1086
        }
1087
    }
32✔
1088
}
1089

1090
impl EncodeIntoContext for Macro {
1091
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1092
        write!(ctx, "{self}")
8✔
1093
    }
8✔
1094
}
1095

1096
impl EncodeIntoContext for MessageDataItemName<'_> {
1097
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
54✔
1098
        match self {
54✔
1099
            Self::Body => ctx.write_all(b"BODY"),
2✔
1100
            Self::BodyExt {
18✔
1101
                section,
18✔
1102
                partial,
18✔
1103
                peek,
18✔
1104
            } => {
18✔
1105
                if *peek {
18✔
1106
                    ctx.write_all(b"BODY.PEEK[")?;
×
1107
                } else {
1108
                    ctx.write_all(b"BODY[")?;
18✔
1109
                }
1110
                if let Some(section) = section {
18✔
1111
                    section.encode_ctx(ctx)?;
16✔
1112
                }
2✔
1113
                ctx.write_all(b"]")?;
18✔
1114
                if let Some((a, b)) = partial {
18✔
1115
                    write!(ctx, "<{a}.{b}>")?;
×
1116
                }
18✔
1117

1118
                Ok(())
18✔
1119
            }
1120
            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
2✔
1121
            Self::Envelope => ctx.write_all(b"ENVELOPE"),
2✔
1122
            Self::Flags => ctx.write_all(b"FLAGS"),
18✔
1123
            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
2✔
1124
            Self::Rfc822 => ctx.write_all(b"RFC822"),
2✔
1125
            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
2✔
1126
            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
2✔
1127
            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
2✔
1128
            Self::Uid => ctx.write_all(b"UID"),
2✔
1129
            MessageDataItemName::Binary {
1130
                section,
×
1131
                partial,
×
1132
                peek,
×
1133
            } => {
1134
                ctx.write_all(b"BINARY")?;
×
1135
                if *peek {
×
1136
                    ctx.write_all(b".PEEK")?;
×
1137
                }
×
1138

1139
                ctx.write_all(b"[")?;
×
1140
                join_serializable(section, b".", ctx)?;
×
1141
                ctx.write_all(b"]")?;
×
1142

1143
                if let Some((a, b)) = partial {
×
1144
                    ctx.write_all(b"<")?;
×
1145
                    a.encode_ctx(ctx)?;
×
1146
                    ctx.write_all(b".")?;
×
1147
                    b.encode_ctx(ctx)?;
×
1148
                    ctx.write_all(b">")?;
×
1149
                }
×
1150

1151
                Ok(())
×
1152
            }
1153
            MessageDataItemName::BinarySize { section } => {
×
1154
                ctx.write_all(b"BINARY.SIZE")?;
×
1155

1156
                ctx.write_all(b"[")?;
×
1157
                join_serializable(section, b".", ctx)?;
×
1158
                ctx.write_all(b"]")
×
1159
            }
1160
            #[cfg(feature = "ext_condstore_qresync")]
1161
            MessageDataItemName::ModSeq => ctx.write_all(b"MODSEQ"),
×
1162
        }
1163
    }
54✔
1164
}
1165

1166
impl EncodeIntoContext for Section<'_> {
1167
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
44✔
1168
        match self {
44✔
1169
            Section::Part(part) => part.encode_ctx(ctx),
2✔
1170
            Section::Header(maybe_part) => match maybe_part {
20✔
1171
                Some(part) => {
2✔
1172
                    part.encode_ctx(ctx)?;
2✔
1173
                    ctx.write_all(b".HEADER")
2✔
1174
                }
1175
                None => ctx.write_all(b"HEADER"),
18✔
1176
            },
1177
            Section::HeaderFields(maybe_part, header_list) => {
12✔
1178
                match maybe_part {
12✔
1179
                    Some(part) => {
2✔
1180
                        part.encode_ctx(ctx)?;
2✔
1181
                        ctx.write_all(b".HEADER.FIELDS (")?;
2✔
1182
                    }
1183
                    None => ctx.write_all(b"HEADER.FIELDS (")?,
10✔
1184
                };
1185
                join_serializable(header_list.as_ref(), b" ", ctx)?;
12✔
1186
                ctx.write_all(b")")
12✔
1187
            }
1188
            Section::HeaderFieldsNot(maybe_part, header_list) => {
4✔
1189
                match maybe_part {
4✔
1190
                    Some(part) => {
2✔
1191
                        part.encode_ctx(ctx)?;
2✔
1192
                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
2✔
1193
                    }
1194
                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
2✔
1195
                };
1196
                join_serializable(header_list.as_ref(), b" ", ctx)?;
4✔
1197
                ctx.write_all(b")")
4✔
1198
            }
1199
            Section::Text(maybe_part) => match maybe_part {
4✔
1200
                Some(part) => {
2✔
1201
                    part.encode_ctx(ctx)?;
2✔
1202
                    ctx.write_all(b".TEXT")
2✔
1203
                }
1204
                None => ctx.write_all(b"TEXT"),
2✔
1205
            },
1206
            Section::Mime(part) => {
2✔
1207
                part.encode_ctx(ctx)?;
2✔
1208
                ctx.write_all(b".MIME")
2✔
1209
            }
1210
        }
1211
    }
44✔
1212
}
1213

1214
impl EncodeIntoContext for Part {
1215
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
1216
        join_serializable(self.0.as_ref(), b".", ctx)
12✔
1217
    }
12✔
1218
}
1219

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

1226
#[cfg(feature = "ext_condstore_qresync")]
1227
impl EncodeIntoContext for NonZeroU64 {
1228
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1229
        write!(ctx, "{self}")
×
1230
    }
×
1231
}
1232

1233
impl EncodeIntoContext for Capability<'_> {
1234
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
232✔
1235
        write!(ctx, "{self}")
232✔
1236
    }
232✔
1237
}
1238

1239
// ----- Responses ---------------------------------------------------------------------------------
1240

1241
impl EncodeIntoContext for Response<'_> {
1242
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,550✔
1243
        match self {
1,550✔
1244
            Response::Status(status) => status.encode_ctx(ctx),
824✔
1245
            Response::Data(data) => data.encode_ctx(ctx),
718✔
1246
            Response::CommandContinuationRequest(continue_request) => {
8✔
1247
                continue_request.encode_ctx(ctx)
8✔
1248
            }
1249
        }
1250
    }
1,550✔
1251
}
1252

1253
impl EncodeIntoContext for Greeting<'_> {
1254
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1255
        ctx.write_all(b"* ")?;
26✔
1256
        self.kind.encode_ctx(ctx)?;
26✔
1257
        ctx.write_all(b" ")?;
26✔
1258

1259
        if let Some(ref code) = self.code {
26✔
1260
            ctx.write_all(b"[")?;
12✔
1261
            code.encode_ctx(ctx)?;
12✔
1262
            ctx.write_all(b"] ")?;
12✔
1263
        }
14✔
1264

1265
        self.text.encode_ctx(ctx)?;
26✔
1266
        ctx.write_all(b"\r\n")
26✔
1267
    }
26✔
1268
}
1269

1270
impl EncodeIntoContext for GreetingKind {
1271
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1272
        match self {
26✔
1273
            GreetingKind::Ok => ctx.write_all(b"OK"),
12✔
1274
            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
12✔
1275
            GreetingKind::Bye => ctx.write_all(b"BYE"),
2✔
1276
        }
1277
    }
26✔
1278
}
1279

1280
impl EncodeIntoContext for Status<'_> {
1281
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
824✔
1282
        fn format_status(
824✔
1283
            tag: Option<&Tag>,
824✔
1284
            status: &str,
824✔
1285
            code: &Option<Code>,
824✔
1286
            comment: &Text,
824✔
1287
            ctx: &mut EncodeContext,
824✔
1288
        ) -> std::io::Result<()> {
824✔
1289
            match tag {
824✔
1290
                Some(tag) => tag.encode_ctx(ctx)?,
606✔
1291
                None => ctx.write_all(b"*")?,
218✔
1292
            }
1293
            ctx.write_all(b" ")?;
824✔
1294
            ctx.write_all(status.as_bytes())?;
824✔
1295
            ctx.write_all(b" ")?;
824✔
1296
            if let Some(code) = code {
824✔
1297
                ctx.write_all(b"[")?;
148✔
1298
                code.encode_ctx(ctx)?;
148✔
1299
                ctx.write_all(b"] ")?;
148✔
1300
            }
676✔
1301
            comment.encode_ctx(ctx)?;
824✔
1302
            ctx.write_all(b"\r\n")
824✔
1303
        }
824✔
1304

1305
        match self {
824✔
1306
            Self::Untagged(StatusBody { kind, code, text }) => match kind {
192✔
1307
                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
134✔
1308
                StatusKind::No => format_status(None, "NO", code, text, ctx),
30✔
1309
                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
28✔
1310
            },
1311
            Self::Tagged(Tagged {
606✔
1312
                tag,
606✔
1313
                body: StatusBody { kind, code, text },
606✔
1314
            }) => match kind {
606✔
1315
                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
572✔
1316
                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
22✔
1317
                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
12✔
1318
            },
1319
            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
26✔
1320
        }
1321
    }
824✔
1322
}
1323

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

1416
impl EncodeIntoContext for CodeOther<'_> {
1417
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1418
        ctx.write_all(self.inner())
×
1419
    }
×
1420
}
1421

1422
impl EncodeIntoContext for Text<'_> {
1423
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
858✔
1424
        ctx.write_all(self.inner().as_bytes())
858✔
1425
    }
858✔
1426
}
1427

1428
impl EncodeIntoContext for Data<'_> {
1429
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
718✔
1430
        match self {
718✔
1431
            Data::Capability(caps) => {
64✔
1432
                ctx.write_all(b"* CAPABILITY ")?;
64✔
1433
                join_serializable(caps.as_ref(), b" ", ctx)?;
64✔
1434
            }
1435
            Data::List {
1436
                items,
210✔
1437
                delimiter,
210✔
1438
                mailbox,
210✔
1439
            } => {
1440
                ctx.write_all(b"* LIST (")?;
210✔
1441
                join_serializable(items, b" ", ctx)?;
210✔
1442
                ctx.write_all(b") ")?;
210✔
1443

1444
                if let Some(delimiter) = delimiter {
210✔
1445
                    ctx.write_all(b"\"")?;
210✔
1446
                    delimiter.encode_ctx(ctx)?;
210✔
1447
                    ctx.write_all(b"\"")?;
210✔
1448
                } else {
1449
                    ctx.write_all(b"NIL")?;
×
1450
                }
1451
                ctx.write_all(b" ")?;
210✔
1452
                mailbox.encode_ctx(ctx)?;
210✔
1453
            }
1454
            Data::Lsub {
1455
                items,
32✔
1456
                delimiter,
32✔
1457
                mailbox,
32✔
1458
            } => {
1459
                ctx.write_all(b"* LSUB (")?;
32✔
1460
                join_serializable(items, b" ", ctx)?;
32✔
1461
                ctx.write_all(b") ")?;
32✔
1462

1463
                if let Some(delimiter) = delimiter {
32✔
1464
                    ctx.write_all(b"\"")?;
32✔
1465
                    delimiter.encode_ctx(ctx)?;
32✔
1466
                    ctx.write_all(b"\"")?;
32✔
1467
                } else {
1468
                    ctx.write_all(b"NIL")?;
×
1469
                }
1470
                ctx.write_all(b" ")?;
32✔
1471
                mailbox.encode_ctx(ctx)?;
32✔
1472
            }
1473
            Data::Status { mailbox, items } => {
18✔
1474
                ctx.write_all(b"* STATUS ")?;
18✔
1475
                mailbox.encode_ctx(ctx)?;
18✔
1476
                ctx.write_all(b" (")?;
18✔
1477
                join_serializable(items, b" ", ctx)?;
18✔
1478
                ctx.write_all(b")")?;
18✔
1479
            }
1480
            // TODO: Exclude pattern via cfg?
1481
            #[cfg(not(feature = "ext_condstore_qresync"))]
1482
            Data::Search(seqs) => {
1483
                if seqs.is_empty() {
1484
                    ctx.write_all(b"* SEARCH")?;
1485
                } else {
1486
                    ctx.write_all(b"* SEARCH ")?;
1487
                    join_serializable(seqs, b" ", ctx)?;
1488
                }
1489
            }
1490
            // TODO: Exclude pattern via cfg?
1491
            #[cfg(feature = "ext_condstore_qresync")]
1492
            Data::Search(seqs, modseq) => {
38✔
1493
                if seqs.is_empty() {
38✔
1494
                    ctx.write_all(b"* SEARCH")?;
8✔
1495
                } else {
1496
                    ctx.write_all(b"* SEARCH ")?;
30✔
1497
                    join_serializable(seqs, b" ", ctx)?;
30✔
1498
                }
1499

1500
                if let Some(modseq) = modseq {
38✔
1501
                    ctx.write_all(b" (MODSEQ ")?;
×
1502
                    modseq.encode_ctx(ctx)?;
×
1503
                    ctx.write_all(b")")?;
×
1504
                }
38✔
1505
            }
1506
            // TODO: Exclude pattern via cfg?
1507
            #[cfg(not(feature = "ext_condstore_qresync"))]
1508
            Data::Sort(seqs) => {
1509
                if seqs.is_empty() {
1510
                    ctx.write_all(b"* SORT")?;
1511
                } else {
1512
                    ctx.write_all(b"* SORT ")?;
1513
                    join_serializable(seqs, b" ", ctx)?;
1514
                }
1515
            }
1516
            // TODO: Exclude pattern via cfg?
1517
            #[cfg(feature = "ext_condstore_qresync")]
1518
            Data::Sort(seqs, modseq) => {
24✔
1519
                if seqs.is_empty() {
24✔
1520
                    ctx.write_all(b"* SORT")?;
8✔
1521
                } else {
1522
                    ctx.write_all(b"* SORT ")?;
16✔
1523
                    join_serializable(seqs, b" ", ctx)?;
16✔
1524
                }
1525

1526
                if let Some(modseq) = modseq {
24✔
1527
                    ctx.write_all(b" (MODSEQ ")?;
×
1528
                    modseq.encode_ctx(ctx)?;
×
1529
                    ctx.write_all(b")")?;
×
1530
                }
24✔
1531
            }
1532
            Data::Thread(threads) => {
24✔
1533
                if threads.is_empty() {
24✔
1534
                    ctx.write_all(b"* THREAD")?;
8✔
1535
                } else {
1536
                    ctx.write_all(b"* THREAD ")?;
16✔
1537
                    for thread in threads {
400✔
1538
                        thread.encode_ctx(ctx)?;
384✔
1539
                    }
1540
                }
1541
            }
1542
            Data::Flags(flags) => {
32✔
1543
                ctx.write_all(b"* FLAGS (")?;
32✔
1544
                join_serializable(flags, b" ", ctx)?;
32✔
1545
                ctx.write_all(b")")?;
32✔
1546
            }
1547
            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
42✔
1548
            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
42✔
1549
            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
50✔
1550
            Data::Fetch { seq, items } => {
96✔
1551
                write!(ctx, "* {seq} FETCH (")?;
96✔
1552
                join_serializable(items.as_ref(), b" ", ctx)?;
96✔
1553
                ctx.write_all(b")")?;
96✔
1554
            }
1555
            Data::Enabled { capabilities } => {
16✔
1556
                write!(ctx, "* ENABLED")?;
16✔
1557

1558
                for cap in capabilities {
32✔
1559
                    ctx.write_all(b" ")?;
16✔
1560
                    cap.encode_ctx(ctx)?;
16✔
1561
                }
1562
            }
1563
            Data::Quota { root, quotas } => {
14✔
1564
                ctx.write_all(b"* QUOTA ")?;
14✔
1565
                root.encode_ctx(ctx)?;
14✔
1566
                ctx.write_all(b" (")?;
14✔
1567
                join_serializable(quotas.as_ref(), b" ", ctx)?;
14✔
1568
                ctx.write_all(b")")?;
14✔
1569
            }
1570
            Data::QuotaRoot { mailbox, roots } => {
10✔
1571
                ctx.write_all(b"* QUOTAROOT ")?;
10✔
1572
                mailbox.encode_ctx(ctx)?;
10✔
1573
                for root in roots {
20✔
1574
                    ctx.write_all(b" ")?;
10✔
1575
                    root.encode_ctx(ctx)?;
10✔
1576
                }
1577
            }
1578
            #[cfg(feature = "ext_id")]
1579
            Data::Id { parameters } => {
2✔
1580
                ctx.write_all(b"* ID ")?;
2✔
1581

1582
                match parameters {
2✔
1583
                    Some(parameters) => {
×
1584
                        if let Some((first, tail)) = parameters.split_first() {
×
1585
                            ctx.write_all(b"(")?;
×
1586

1587
                            first.0.encode_ctx(ctx)?;
×
1588
                            ctx.write_all(b" ")?;
×
1589
                            first.1.encode_ctx(ctx)?;
×
1590

1591
                            for parameter in tail {
×
1592
                                ctx.write_all(b" ")?;
×
1593
                                parameter.0.encode_ctx(ctx)?;
×
1594
                                ctx.write_all(b" ")?;
×
1595
                                parameter.1.encode_ctx(ctx)?;
×
1596
                            }
1597

1598
                            ctx.write_all(b")")?;
×
1599
                        } else {
1600
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
1601
                            {
1602
                                ctx.write_all(b"()")?;
1603
                            }
1604
                            #[cfg(feature = "quirk_id_empty_to_nil")]
1605
                            {
1606
                                ctx.write_all(b"NIL")?;
×
1607
                            }
1608
                        }
1609
                    }
1610
                    None => {
1611
                        ctx.write_all(b"NIL")?;
2✔
1612
                    }
1613
                }
1614
            }
1615
            #[cfg(feature = "ext_metadata")]
1616
            Data::Metadata { mailbox, items } => {
4✔
1617
                ctx.write_all(b"* METADATA ")?;
4✔
1618
                mailbox.encode_ctx(ctx)?;
4✔
1619
                ctx.write_all(b" ")?;
4✔
1620
                items.encode_ctx(ctx)?;
4✔
1621
            }
1622
            #[cfg(feature = "ext_condstore_qresync")]
1623
            Data::Vanished {
1624
                earlier,
×
1625
                known_uids,
×
1626
            } => {
1627
                ctx.write_all(b"* VANISHED")?;
×
1628
                if *earlier {
×
1629
                    ctx.write_all(b" (EARLIER)")?;
×
1630
                }
×
1631
                ctx.write_all(b" ")?;
×
1632
                known_uids.encode_ctx(ctx)?;
×
1633
            }
1634

1635
            #[cfg(feature = "ext_namespace")]
1636
            Data::Namespace {
NEW
1637
                personal,
×
NEW
1638
                other,
×
NEW
1639
                shared,
×
1640
            } => {
NEW
1641
                ctx.write_all(b"* NAMESPACE ")?;
×
NEW
1642
                encode_namespaces(ctx, personal)?;
×
NEW
1643
                ctx.write_all(b" ")?;
×
NEW
1644
                encode_namespaces(ctx, other)?;
×
NEW
1645
                ctx.write_all(b" ")?;
×
NEW
1646
                encode_namespaces(ctx, shared)?;
×
1647
            }
1648
        }
1649

1650
        ctx.write_all(b"\r\n")
718✔
1651
    }
718✔
1652
}
1653

1654
impl EncodeIntoContext for FlagNameAttribute<'_> {
1655
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
1656
        write!(ctx, "{self}")
90✔
1657
    }
90✔
1658
}
1659

1660
impl EncodeIntoContext for QuotedChar {
1661
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
242✔
1662
        match self.inner() {
242✔
1663
            '\\' => ctx.write_all(b"\\\\"),
×
1664
            '"' => ctx.write_all(b"\\\""),
×
1665
            other => ctx.write_all(&[other as u8]),
242✔
1666
        }
1667
    }
242✔
1668
}
1669

1670
impl EncodeIntoContext for StatusDataItem {
1671
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
52✔
1672
        match self {
52✔
1673
            Self::Messages(count) => {
20✔
1674
                ctx.write_all(b"MESSAGES ")?;
20✔
1675
                count.encode_ctx(ctx)
20✔
1676
            }
1677
            Self::Recent(count) => {
2✔
1678
                ctx.write_all(b"RECENT ")?;
2✔
1679
                count.encode_ctx(ctx)
2✔
1680
            }
1681
            Self::UidNext(next) => {
18✔
1682
                ctx.write_all(b"UIDNEXT ")?;
18✔
1683
                next.encode_ctx(ctx)
18✔
1684
            }
1685
            Self::UidValidity(identifier) => {
2✔
1686
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1687
                identifier.encode_ctx(ctx)
2✔
1688
            }
1689
            Self::Unseen(count) => {
2✔
1690
                ctx.write_all(b"UNSEEN ")?;
2✔
1691
                count.encode_ctx(ctx)
2✔
1692
            }
1693
            Self::Deleted(count) => {
4✔
1694
                ctx.write_all(b"DELETED ")?;
4✔
1695
                count.encode_ctx(ctx)
4✔
1696
            }
1697
            Self::DeletedStorage(count) => {
4✔
1698
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1699
                count.encode_ctx(ctx)
4✔
1700
            }
1701
            #[cfg(feature = "ext_condstore_qresync")]
1702
            Self::HighestModSeq(value) => {
×
1703
                ctx.write_all(b"HIGHESTMODSEQ ")?;
×
1704
                value.encode_ctx(ctx)
×
1705
            }
1706
        }
1707
    }
52✔
1708
}
1709

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

1783
impl EncodeIntoContext for NString<'_> {
1784
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
318✔
1785
        match &self.0 {
318✔
1786
            Some(imap_str) => imap_str.encode_ctx(ctx),
188✔
1787
            None => ctx.write_all(b"NIL"),
130✔
1788
        }
1789
    }
318✔
1790
}
1791

1792
impl EncodeIntoContext for NString8<'_> {
1793
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1794
        match self {
8✔
1795
            NString8::NString(nstring) => nstring.encode_ctx(ctx),
6✔
1796
            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
2✔
1797
        }
1798
    }
8✔
1799
}
1800

1801
impl EncodeIntoContext for BodyStructure<'_> {
1802
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1803
        ctx.write_all(b"(")?;
32✔
1804
        match self {
32✔
1805
            BodyStructure::Single {
1806
                body,
20✔
1807
                extension_data: extension,
20✔
1808
            } => {
1809
                body.encode_ctx(ctx)?;
20✔
1810
                if let Some(extension) = extension {
20✔
1811
                    ctx.write_all(b" ")?;
4✔
1812
                    extension.encode_ctx(ctx)?;
4✔
1813
                }
16✔
1814
            }
1815
            BodyStructure::Multi {
1816
                bodies,
12✔
1817
                subtype,
12✔
1818
                extension_data,
12✔
1819
            } => {
1820
                for body in bodies.as_ref() {
12✔
1821
                    body.encode_ctx(ctx)?;
12✔
1822
                }
1823
                ctx.write_all(b" ")?;
12✔
1824
                subtype.encode_ctx(ctx)?;
12✔
1825

1826
                if let Some(extension) = extension_data {
12✔
1827
                    ctx.write_all(b" ")?;
×
1828
                    extension.encode_ctx(ctx)?;
×
1829
                }
12✔
1830
            }
1831
        }
1832
        ctx.write_all(b")")
32✔
1833
    }
32✔
1834
}
1835

1836
impl EncodeIntoContext for Body<'_> {
1837
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1838
        match self.specific {
20✔
1839
            SpecificFields::Basic {
1840
                r#type: ref type_,
4✔
1841
                ref subtype,
4✔
1842
            } => {
1843
                type_.encode_ctx(ctx)?;
4✔
1844
                ctx.write_all(b" ")?;
4✔
1845
                subtype.encode_ctx(ctx)?;
4✔
1846
                ctx.write_all(b" ")?;
4✔
1847
                self.basic.encode_ctx(ctx)
4✔
1848
            }
1849
            SpecificFields::Message {
1850
                ref envelope,
×
1851
                ref body_structure,
×
1852
                number_of_lines,
×
1853
            } => {
1854
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
1855
                self.basic.encode_ctx(ctx)?;
×
1856
                ctx.write_all(b" ")?;
×
1857
                envelope.encode_ctx(ctx)?;
×
1858
                ctx.write_all(b" ")?;
×
1859
                body_structure.encode_ctx(ctx)?;
×
1860
                ctx.write_all(b" ")?;
×
1861
                write!(ctx, "{number_of_lines}")
×
1862
            }
1863
            SpecificFields::Text {
1864
                ref subtype,
16✔
1865
                number_of_lines,
16✔
1866
            } => {
1867
                ctx.write_all(b"\"TEXT\" ")?;
16✔
1868
                subtype.encode_ctx(ctx)?;
16✔
1869
                ctx.write_all(b" ")?;
16✔
1870
                self.basic.encode_ctx(ctx)?;
16✔
1871
                ctx.write_all(b" ")?;
16✔
1872
                write!(ctx, "{number_of_lines}")
16✔
1873
            }
1874
        }
1875
    }
20✔
1876
}
1877

1878
impl EncodeIntoContext for BasicFields<'_> {
1879
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1880
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
20✔
1881
        ctx.write_all(b" ")?;
20✔
1882
        self.id.encode_ctx(ctx)?;
20✔
1883
        ctx.write_all(b" ")?;
20✔
1884
        self.description.encode_ctx(ctx)?;
20✔
1885
        ctx.write_all(b" ")?;
20✔
1886
        self.content_transfer_encoding.encode_ctx(ctx)?;
20✔
1887
        ctx.write_all(b" ")?;
20✔
1888
        write!(ctx, "{}", self.size)
20✔
1889
    }
20✔
1890
}
1891

1892
impl EncodeIntoContext for Envelope<'_> {
1893
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10✔
1894
        ctx.write_all(b"(")?;
10✔
1895
        self.date.encode_ctx(ctx)?;
10✔
1896
        ctx.write_all(b" ")?;
10✔
1897
        self.subject.encode_ctx(ctx)?;
10✔
1898
        ctx.write_all(b" ")?;
10✔
1899
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
10✔
1900
        ctx.write_all(b" ")?;
10✔
1901
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
10✔
1902
        ctx.write_all(b" ")?;
10✔
1903
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
10✔
1904
        ctx.write_all(b" ")?;
10✔
1905
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
10✔
1906
        ctx.write_all(b" ")?;
10✔
1907
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
10✔
1908
        ctx.write_all(b" ")?;
10✔
1909
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
10✔
1910
        ctx.write_all(b" ")?;
10✔
1911
        self.in_reply_to.encode_ctx(ctx)?;
10✔
1912
        ctx.write_all(b" ")?;
10✔
1913
        self.message_id.encode_ctx(ctx)?;
10✔
1914
        ctx.write_all(b")")
10✔
1915
    }
10✔
1916
}
1917

1918
impl EncodeIntoContext for Address<'_> {
1919
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
1920
        ctx.write_all(b"(")?;
48✔
1921
        self.name.encode_ctx(ctx)?;
48✔
1922
        ctx.write_all(b" ")?;
48✔
1923
        self.adl.encode_ctx(ctx)?;
48✔
1924
        ctx.write_all(b" ")?;
48✔
1925
        self.mailbox.encode_ctx(ctx)?;
48✔
1926
        ctx.write_all(b" ")?;
48✔
1927
        self.host.encode_ctx(ctx)?;
48✔
1928
        ctx.write_all(b")")?;
48✔
1929

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

1934
impl EncodeIntoContext for SinglePartExtensionData<'_> {
1935
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1936
        self.md5.encode_ctx(ctx)?;
6✔
1937

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

1943
        Ok(())
6✔
1944
    }
6✔
1945
}
1946

1947
impl EncodeIntoContext for MultiPartExtensionData<'_> {
1948
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1949
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1950

1951
        if let Some(disposition) = &self.tail {
×
1952
            ctx.write_all(b" ")?;
×
1953
            disposition.encode_ctx(ctx)?;
×
1954
        }
×
1955

1956
        Ok(())
×
1957
    }
×
1958
}
1959

1960
impl EncodeIntoContext for Disposition<'_> {
1961
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1962
        match &self.disposition {
6✔
1963
            Some((s, param)) => {
×
1964
                ctx.write_all(b"(")?;
×
1965
                s.encode_ctx(ctx)?;
×
1966
                ctx.write_all(b" ")?;
×
1967
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1968
                ctx.write_all(b")")?;
×
1969
            }
1970
            None => ctx.write_all(b"NIL")?,
6✔
1971
        }
1972

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

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

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

1986
        if let Some(location) = &self.tail {
6✔
1987
            ctx.write_all(b" ")?;
6✔
1988
            location.encode_ctx(ctx)?;
6✔
1989
        }
×
1990

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

1995
impl EncodeIntoContext for Location<'_> {
1996
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1997
        self.location.encode_ctx(ctx)?;
6✔
1998

1999
        for body_extension in &self.extensions {
10✔
2000
            ctx.write_all(b" ")?;
4✔
2001
            body_extension.encode_ctx(ctx)?;
4✔
2002
        }
2003

2004
        Ok(())
6✔
2005
    }
6✔
2006
}
2007

2008
impl EncodeIntoContext for BodyExtension<'_> {
2009
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
2010
        match self {
6✔
2011
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
2012
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
2013
            BodyExtension::List(list) => {
2✔
2014
                ctx.write_all(b"(")?;
2✔
2015
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
2016
                ctx.write_all(b")")
2✔
2017
            }
2018
        }
2019
    }
6✔
2020
}
2021

2022
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
2023
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
2024
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
14✔
2025
    }
14✔
2026
}
2027

2028
impl EncodeIntoContext for CommandContinuationRequest<'_> {
2029
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
2030
        match self {
8✔
2031
            Self::Basic(continue_basic) => match continue_basic.code() {
8✔
2032
                Some(code) => {
×
2033
                    ctx.write_all(b"+ [")?;
×
2034
                    code.encode_ctx(ctx)?;
×
2035
                    ctx.write_all(b"] ")?;
×
2036
                    continue_basic.text().encode_ctx(ctx)?;
×
2037
                    ctx.write_all(b"\r\n")
×
2038
                }
2039
                None => {
2040
                    ctx.write_all(b"+ ")?;
8✔
2041
                    continue_basic.text().encode_ctx(ctx)?;
8✔
2042
                    ctx.write_all(b"\r\n")
8✔
2043
                }
2044
            },
2045
            Self::Base64(data) => {
×
2046
                ctx.write_all(b"+ ")?;
×
2047
                ctx.write_all(base64.encode(data).as_bytes())?;
×
2048
                ctx.write_all(b"\r\n")
×
2049
            }
2050
        }
2051
    }
8✔
2052
}
2053

2054
pub(crate) mod utils {
2055
    use std::io::Write;
2056

2057
    use super::{EncodeContext, EncodeIntoContext};
2058

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

2061
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
2062

2063
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
916✔
2064
        elements: &[I],
916✔
2065
        sep: &[u8],
916✔
2066
        ctx: &mut EncodeContext,
916✔
2067
    ) -> std::io::Result<()> {
916✔
2068
        if let Some((last, head)) = elements.split_last() {
916✔
2069
            for item in head {
1,346✔
2070
                item.encode_ctx(ctx)?;
594✔
2071
                ctx.write_all(sep)?;
594✔
2072
            }
2073

2074
            last.encode_ctx(ctx)
752✔
2075
        } else {
2076
            Ok(())
164✔
2077
        }
2078
    }
916✔
2079

2080
    impl<T> EncodeIntoContext for List1OrNil<'_, T>
2081
    where
2082
        T: EncodeIntoContext,
2083
    {
2084
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
66✔
2085
            if let Some((last, head)) = self.0.split_last() {
66✔
2086
                ctx.write_all(b"(")?;
40✔
2087

2088
                for item in head {
48✔
2089
                    item.encode_ctx(ctx)?;
8✔
2090
                    ctx.write_all(self.1)?;
8✔
2091
                }
2092

2093
                last.encode_ctx(ctx)?;
40✔
2094

2095
                ctx.write_all(b")")
40✔
2096
            } else {
2097
                ctx.write_all(b"NIL")
26✔
2098
            }
2099
        }
66✔
2100
    }
2101

2102
    impl<T> EncodeIntoContext for List1AttributeValueOrNil<'_, T>
2103
    where
2104
        T: EncodeIntoContext,
2105
    {
2106
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
2107
            if let Some((last, head)) = self.0.split_last() {
20✔
2108
                ctx.write_all(b"(")?;
8✔
2109

2110
                for (attribute, value) in head {
8✔
2111
                    attribute.encode_ctx(ctx)?;
×
2112
                    ctx.write_all(b" ")?;
×
2113
                    value.encode_ctx(ctx)?;
×
2114
                    ctx.write_all(b" ")?;
×
2115
                }
2116

2117
                let (attribute, value) = last;
8✔
2118
                attribute.encode_ctx(ctx)?;
8✔
2119
                ctx.write_all(b" ")?;
8✔
2120
                value.encode_ctx(ctx)?;
8✔
2121

2122
                ctx.write_all(b")")
8✔
2123
            } else {
2124
                ctx.write_all(b"NIL")
12✔
2125
            }
2126
        }
20✔
2127
    }
2128
}
2129

2130
#[cfg(test)]
2131
mod tests {
2132
    use std::num::NonZeroU32;
2133

2134
    use imap_types::{
2135
        auth::AuthMechanism,
2136
        command::{Command, CommandBody},
2137
        core::{AString, Literal, NString, Vec1},
2138
        fetch::MessageDataItem,
2139
        response::{Data, Response},
2140
        utils::escape_byte_string,
2141
    };
2142

2143
    use super::*;
2144

2145
    #[test]
2146
    fn test_api_encoder_usage() {
2✔
2147
        let cmd = Command::new(
2✔
2148
            "A",
2149
            CommandBody::login(
2✔
2150
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
2151
                "password",
2152
            )
2153
            .unwrap(),
2✔
2154
        )
2155
        .unwrap();
2✔
2156

2157
        // Dump.
2158
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
2159

2160
        // Encoded.
2161
        let encoded = CommandCodec::default().encode(&cmd);
2✔
2162

2163
        let mut out = Vec::new();
2✔
2164

2165
        for x in encoded {
8✔
2166
            match x {
6✔
2167
                Fragment::Line { data } => {
4✔
2168
                    println!("C: {}", escape_byte_string(&data));
4✔
2169
                    out.extend_from_slice(&data);
4✔
2170
                }
4✔
2171
                Fragment::Literal { data, mode } => {
2✔
2172
                    match mode {
2✔
2173
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
2174
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
2175
                    }
2176

2177
                    println!("C: {}", escape_byte_string(&data));
2✔
2178
                    out.extend_from_slice(&data);
2✔
2179
                }
2180
            }
2181
        }
2182

2183
        assert_eq!(got_encoded, out);
2✔
2184
    }
2✔
2185

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

2258
    #[test]
2259
    fn test_encode_response() {
2✔
2260
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
2261
            (
2✔
2262
                Response::Data(Data::Fetch {
2✔
2263
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2264
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2265
                        section: None,
2✔
2266
                        origin: None,
2✔
2267
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
2268
                    }),
2✔
2269
                }),
2✔
2270
                [
2✔
2271
                    Fragment::Line {
2✔
2272
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
2273
                    },
2✔
2274
                    Fragment::Literal {
2✔
2275
                        data: b"ABCDE".to_vec(),
2✔
2276
                        mode: LiteralMode::Sync,
2✔
2277
                    },
2✔
2278
                    Fragment::Line {
2✔
2279
                        data: b")\r\n".to_vec(),
2✔
2280
                    },
2✔
2281
                ]
2✔
2282
                .as_ref(),
2✔
2283
            ),
2✔
2284
            (
2✔
2285
                Response::Data(Data::Fetch {
2✔
2286
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2287
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2288
                        section: None,
2✔
2289
                        origin: None,
2✔
2290
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
2291
                    }),
2✔
2292
                }),
2✔
2293
                [
2✔
2294
                    Fragment::Line {
2✔
2295
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
2296
                    },
2✔
2297
                    Fragment::Literal {
2✔
2298
                        data: b"ABCDE".to_vec(),
2✔
2299
                        mode: LiteralMode::NonSync,
2✔
2300
                    },
2✔
2301
                    Fragment::Line {
2✔
2302
                        data: b")\r\n".to_vec(),
2✔
2303
                    },
2✔
2304
                ]
2✔
2305
                .as_ref(),
2✔
2306
            ),
2✔
2307
        ])
2✔
2308
    }
2✔
2309

2310
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
2311
    where
4✔
2312
        E: Encoder<Message<'a> = M> + Default,
4✔
2313
        F: AsRef<[Fragment]>,
4✔
2314
    {
2315
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
2316
            println!("# Testing {i}");
16✔
2317

2318
            let encoder = E::default().encode(obj);
16✔
2319
            let actions = actions.as_ref();
16✔
2320

2321
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2322
        }
2323
    }
4✔
2324
}
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