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

duesee / imap-codec / 18634705484

19 Oct 2025 06:56PM UTC coverage: 91.776% (+0.008%) from 91.768%
18634705484

push

github

duesee
feat: Implement `UTF8={ACCEPT,ONLY}`

44 of 66 new or added lines in 11 files covered. (66.67%)

219 existing lines in 3 files now uncovered.

10311 of 11235 relevant lines covered (91.78%)

941.33 hits per line

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

83.01
/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
use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
87

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

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

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

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

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

142
        out
2,028✔
143
    }
2,028✔
144
}
145

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

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

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

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

164
//--------------------------------------------------------------------------------------------------
165

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

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

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

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

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

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

200
        items
2,496✔
201
    }
2,496✔
202

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

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

215
        out
450✔
216
    }
450✔
217
}
218

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

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

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

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

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

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

253
// -------------------------------------------------------------------------------------------------
254

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

259
// ----- Primitive ---------------------------------------------------------------------------------
260

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

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

273
// ----- Command -----------------------------------------------------------------------------------
274

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

536
                sequence_set.encode_ctx(ctx)?;
16✔
537
                ctx.write_all(b" ")?;
16✔
538

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

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

552
                ctx.write_all(b"FLAGS")?;
16✔
553

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

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

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

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

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

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

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

675
                ctx.write_all(b" ")?;
14✔
676
                mailbox.encode_ctx(ctx)?;
14✔
677

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

680
                if entries.as_ref().len() == 1 {
14✔
681
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
682
                } else {
UNCOV
683
                    ctx.write_all(b"(")?;
×
UNCOV
684
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
UNCOV
685
                    ctx.write_all(b")")
×
686
                }
687
            }
688
        }
689
    }
698✔
690
}
691

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

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

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

UNCOV
727
                if let Some(known_uids) = known_uids {
×
728
                    write!(ctx, " ")?;
×
729
                    known_uids.encode_ctx(ctx)?;
×
730
                }
×
731

732
                if let Some((known_sequence_set, known_uid_set)) = seq_match_data {
×
733
                    write!(ctx, " (")?;
×
734
                    known_sequence_set.encode_ctx(ctx)?;
×
735
                    write!(ctx, " ")?;
×
UNCOV
736
                    known_uid_set.encode_ctx(ctx)?;
×
737
                    write!(ctx, ")")?;
×
738
                }
×
739

740
                write!(ctx, ")")
×
741
            }
742
        }
743
    }
×
744
}
745

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

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

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

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

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

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

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

804
        ctx.push_line();
44✔
805
        ctx.write_all(self.as_ref())?;
44✔
806
        ctx.push_literal(self.mode());
44✔
807

808
        Ok(())
44✔
809
    }
44✔
810
}
811

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

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

827
impl EncodeIntoContext for MailboxOther<'_> {
828
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
536✔
829
        self.inner().encode_ctx(ctx)
536✔
830
    }
536✔
831
}
832

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

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

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

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

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

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

888
impl EncodeIntoContext for DateTime {
889
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
890
        self.as_ref().encode_ctx(ctx)
14✔
891
    }
14✔
892
}
893

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

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

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

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

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

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

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

1073
impl EncodeIntoContext for Macro {
1074
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1075
        write!(ctx, "{self}")
8✔
1076
    }
8✔
1077
}
1078

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

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

UNCOV
1122
                ctx.write_all(b"[")?;
×
UNCOV
1123
                join_serializable(section, b".", ctx)?;
×
UNCOV
1124
                ctx.write_all(b"]")?;
×
1125

UNCOV
1126
                if let Some((a, b)) = partial {
×
UNCOV
1127
                    ctx.write_all(b"<")?;
×
1128
                    a.encode_ctx(ctx)?;
×
1129
                    ctx.write_all(b".")?;
×
1130
                    b.encode_ctx(ctx)?;
×
UNCOV
1131
                    ctx.write_all(b">")?;
×
1132
                }
×
1133

1134
                Ok(())
×
1135
            }
UNCOV
1136
            MessageDataItemName::BinarySize { section } => {
×
1137
                ctx.write_all(b"BINARY.SIZE")?;
×
1138

1139
                ctx.write_all(b"[")?;
×
UNCOV
1140
                join_serializable(section, b".", ctx)?;
×
1141
                ctx.write_all(b"]")
×
1142
            }
1143
            #[cfg(feature = "ext_condstore_qresync")]
1144
            MessageDataItemName::ModSeq => ctx.write_all(b"MODSEQ"),
×
1145
        }
1146
    }
54✔
1147
}
1148

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

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

1203
impl EncodeIntoContext for NonZeroU32 {
1204
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
246✔
1205
        write!(ctx, "{self}")
246✔
1206
    }
246✔
1207
}
1208

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

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

1222
// ----- Responses ---------------------------------------------------------------------------------
1223

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

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

1242
        if let Some(ref code) = self.code {
26✔
1243
            ctx.write_all(b"[")?;
12✔
1244
            code.encode_ctx(ctx)?;
12✔
1245
            ctx.write_all(b"] ")?;
12✔
1246
        }
14✔
1247

1248
        self.text.encode_ctx(ctx)?;
26✔
1249
        ctx.write_all(b"\r\n")
26✔
1250
    }
26✔
1251
}
1252

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

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

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

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

1399
impl EncodeIntoContext for CodeOther<'_> {
UNCOV
1400
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
UNCOV
1401
        ctx.write_all(self.inner())
×
UNCOV
1402
    }
×
1403
}
1404

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

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

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

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

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

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

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

1565
                match parameters {
2✔
UNCOV
1566
                    Some(parameters) => {
×
UNCOV
1567
                        if let Some((first, tail)) = parameters.split_first() {
×
UNCOV
1568
                            ctx.write_all(b"(")?;
×
1569

UNCOV
1570
                            first.0.encode_ctx(ctx)?;
×
UNCOV
1571
                            ctx.write_all(b" ")?;
×
UNCOV
1572
                            first.1.encode_ctx(ctx)?;
×
1573

UNCOV
1574
                            for parameter in tail {
×
UNCOV
1575
                                ctx.write_all(b" ")?;
×
UNCOV
1576
                                parameter.0.encode_ctx(ctx)?;
×
UNCOV
1577
                                ctx.write_all(b" ")?;
×
UNCOV
1578
                                parameter.1.encode_ctx(ctx)?;
×
1579
                            }
1580

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

1619
        ctx.write_all(b"\r\n")
718✔
1620
    }
718✔
1621
}
1622

1623
impl EncodeIntoContext for FlagNameAttribute<'_> {
1624
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
1625
        write!(ctx, "{self}")
90✔
1626
    }
90✔
1627
}
1628

1629
impl EncodeIntoContext for QuotedChar {
1630
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
242✔
1631
        match self.inner() {
242✔
UNCOV
1632
            '\\' => ctx.write_all(b"\\\\"),
×
UNCOV
1633
            '"' => ctx.write_all(b"\\\""),
×
1634
            other => ctx.write_all(&[other as u8]),
242✔
1635
        }
1636
    }
242✔
1637
}
1638

1639
impl EncodeIntoContext for StatusDataItem {
1640
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
52✔
1641
        match self {
52✔
1642
            Self::Messages(count) => {
20✔
1643
                ctx.write_all(b"MESSAGES ")?;
20✔
1644
                count.encode_ctx(ctx)
20✔
1645
            }
1646
            Self::Recent(count) => {
2✔
1647
                ctx.write_all(b"RECENT ")?;
2✔
1648
                count.encode_ctx(ctx)
2✔
1649
            }
1650
            Self::UidNext(next) => {
18✔
1651
                ctx.write_all(b"UIDNEXT ")?;
18✔
1652
                next.encode_ctx(ctx)
18✔
1653
            }
1654
            Self::UidValidity(identifier) => {
2✔
1655
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1656
                identifier.encode_ctx(ctx)
2✔
1657
            }
1658
            Self::Unseen(count) => {
2✔
1659
                ctx.write_all(b"UNSEEN ")?;
2✔
1660
                count.encode_ctx(ctx)
2✔
1661
            }
1662
            Self::Deleted(count) => {
4✔
1663
                ctx.write_all(b"DELETED ")?;
4✔
1664
                count.encode_ctx(ctx)
4✔
1665
            }
1666
            Self::DeletedStorage(count) => {
4✔
1667
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1668
                count.encode_ctx(ctx)
4✔
1669
            }
1670
            #[cfg(feature = "ext_condstore_qresync")]
UNCOV
1671
            Self::HighestModSeq(value) => {
×
UNCOV
1672
                ctx.write_all(b"HIGHESTMODSEQ ")?;
×
UNCOV
1673
                value.encode_ctx(ctx)
×
1674
            }
1675
        }
1676
    }
52✔
1677
}
1678

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

1752
impl EncodeIntoContext for NString<'_> {
1753
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
318✔
1754
        match &self.0 {
318✔
1755
            Some(imap_str) => imap_str.encode_ctx(ctx),
188✔
1756
            None => ctx.write_all(b"NIL"),
130✔
1757
        }
1758
    }
318✔
1759
}
1760

1761
impl EncodeIntoContext for NString8<'_> {
1762
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1763
        match self {
8✔
1764
            NString8::NString(nstring) => nstring.encode_ctx(ctx),
6✔
1765
            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
2✔
1766
        }
1767
    }
8✔
1768
}
1769

1770
impl EncodeIntoContext for BodyStructure<'_> {
1771
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1772
        ctx.write_all(b"(")?;
32✔
1773
        match self {
32✔
1774
            BodyStructure::Single {
1775
                body,
20✔
1776
                extension_data: extension,
20✔
1777
            } => {
1778
                body.encode_ctx(ctx)?;
20✔
1779
                if let Some(extension) = extension {
20✔
1780
                    ctx.write_all(b" ")?;
4✔
1781
                    extension.encode_ctx(ctx)?;
4✔
1782
                }
16✔
1783
            }
1784
            BodyStructure::Multi {
1785
                bodies,
12✔
1786
                subtype,
12✔
1787
                extension_data,
12✔
1788
            } => {
1789
                for body in bodies.as_ref() {
12✔
1790
                    body.encode_ctx(ctx)?;
12✔
1791
                }
1792
                ctx.write_all(b" ")?;
12✔
1793
                subtype.encode_ctx(ctx)?;
12✔
1794

1795
                if let Some(extension) = extension_data {
12✔
UNCOV
1796
                    ctx.write_all(b" ")?;
×
UNCOV
1797
                    extension.encode_ctx(ctx)?;
×
1798
                }
12✔
1799
            }
1800
        }
1801
        ctx.write_all(b")")
32✔
1802
    }
32✔
1803
}
1804

1805
impl EncodeIntoContext for Body<'_> {
1806
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1807
        match self.specific {
20✔
1808
            SpecificFields::Basic {
1809
                r#type: ref type_,
4✔
1810
                ref subtype,
4✔
1811
            } => {
1812
                type_.encode_ctx(ctx)?;
4✔
1813
                ctx.write_all(b" ")?;
4✔
1814
                subtype.encode_ctx(ctx)?;
4✔
1815
                ctx.write_all(b" ")?;
4✔
1816
                self.basic.encode_ctx(ctx)
4✔
1817
            }
1818
            SpecificFields::Message {
UNCOV
1819
                ref envelope,
×
UNCOV
1820
                ref body_structure,
×
UNCOV
1821
                number_of_lines,
×
1822
            } => {
UNCOV
1823
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
UNCOV
1824
                self.basic.encode_ctx(ctx)?;
×
UNCOV
1825
                ctx.write_all(b" ")?;
×
UNCOV
1826
                envelope.encode_ctx(ctx)?;
×
UNCOV
1827
                ctx.write_all(b" ")?;
×
UNCOV
1828
                body_structure.encode_ctx(ctx)?;
×
UNCOV
1829
                ctx.write_all(b" ")?;
×
UNCOV
1830
                write!(ctx, "{number_of_lines}")
×
1831
            }
1832
            SpecificFields::Text {
1833
                ref subtype,
16✔
1834
                number_of_lines,
16✔
1835
            } => {
1836
                ctx.write_all(b"\"TEXT\" ")?;
16✔
1837
                subtype.encode_ctx(ctx)?;
16✔
1838
                ctx.write_all(b" ")?;
16✔
1839
                self.basic.encode_ctx(ctx)?;
16✔
1840
                ctx.write_all(b" ")?;
16✔
1841
                write!(ctx, "{number_of_lines}")
16✔
1842
            }
1843
        }
1844
    }
20✔
1845
}
1846

1847
impl EncodeIntoContext for BasicFields<'_> {
1848
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1849
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
20✔
1850
        ctx.write_all(b" ")?;
20✔
1851
        self.id.encode_ctx(ctx)?;
20✔
1852
        ctx.write_all(b" ")?;
20✔
1853
        self.description.encode_ctx(ctx)?;
20✔
1854
        ctx.write_all(b" ")?;
20✔
1855
        self.content_transfer_encoding.encode_ctx(ctx)?;
20✔
1856
        ctx.write_all(b" ")?;
20✔
1857
        write!(ctx, "{}", self.size)
20✔
1858
    }
20✔
1859
}
1860

1861
impl EncodeIntoContext for Envelope<'_> {
1862
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10✔
1863
        ctx.write_all(b"(")?;
10✔
1864
        self.date.encode_ctx(ctx)?;
10✔
1865
        ctx.write_all(b" ")?;
10✔
1866
        self.subject.encode_ctx(ctx)?;
10✔
1867
        ctx.write_all(b" ")?;
10✔
1868
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
10✔
1869
        ctx.write_all(b" ")?;
10✔
1870
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
10✔
1871
        ctx.write_all(b" ")?;
10✔
1872
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
10✔
1873
        ctx.write_all(b" ")?;
10✔
1874
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
10✔
1875
        ctx.write_all(b" ")?;
10✔
1876
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
10✔
1877
        ctx.write_all(b" ")?;
10✔
1878
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
10✔
1879
        ctx.write_all(b" ")?;
10✔
1880
        self.in_reply_to.encode_ctx(ctx)?;
10✔
1881
        ctx.write_all(b" ")?;
10✔
1882
        self.message_id.encode_ctx(ctx)?;
10✔
1883
        ctx.write_all(b")")
10✔
1884
    }
10✔
1885
}
1886

1887
impl EncodeIntoContext for Address<'_> {
1888
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
1889
        ctx.write_all(b"(")?;
48✔
1890
        self.name.encode_ctx(ctx)?;
48✔
1891
        ctx.write_all(b" ")?;
48✔
1892
        self.adl.encode_ctx(ctx)?;
48✔
1893
        ctx.write_all(b" ")?;
48✔
1894
        self.mailbox.encode_ctx(ctx)?;
48✔
1895
        ctx.write_all(b" ")?;
48✔
1896
        self.host.encode_ctx(ctx)?;
48✔
1897
        ctx.write_all(b")")?;
48✔
1898

1899
        Ok(())
48✔
1900
    }
48✔
1901
}
1902

1903
impl EncodeIntoContext for SinglePartExtensionData<'_> {
1904
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1905
        self.md5.encode_ctx(ctx)?;
6✔
1906

1907
        if let Some(disposition) = &self.tail {
6✔
1908
            ctx.write_all(b" ")?;
6✔
1909
            disposition.encode_ctx(ctx)?;
6✔
UNCOV
1910
        }
×
1911

1912
        Ok(())
6✔
1913
    }
6✔
1914
}
1915

1916
impl EncodeIntoContext for MultiPartExtensionData<'_> {
UNCOV
1917
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
UNCOV
1918
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1919

UNCOV
1920
        if let Some(disposition) = &self.tail {
×
UNCOV
1921
            ctx.write_all(b" ")?;
×
UNCOV
1922
            disposition.encode_ctx(ctx)?;
×
UNCOV
1923
        }
×
1924

1925
        Ok(())
×
UNCOV
1926
    }
×
1927
}
1928

1929
impl EncodeIntoContext for Disposition<'_> {
1930
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1931
        match &self.disposition {
6✔
1932
            Some((s, param)) => {
×
1933
                ctx.write_all(b"(")?;
×
UNCOV
1934
                s.encode_ctx(ctx)?;
×
1935
                ctx.write_all(b" ")?;
×
1936
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1937
                ctx.write_all(b")")?;
×
1938
            }
1939
            None => ctx.write_all(b"NIL")?,
6✔
1940
        }
1941

1942
        if let Some(language) = &self.tail {
6✔
1943
            ctx.write_all(b" ")?;
6✔
1944
            language.encode_ctx(ctx)?;
6✔
UNCOV
1945
        }
×
1946

1947
        Ok(())
6✔
1948
    }
6✔
1949
}
1950

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

1955
        if let Some(location) = &self.tail {
6✔
1956
            ctx.write_all(b" ")?;
6✔
1957
            location.encode_ctx(ctx)?;
6✔
UNCOV
1958
        }
×
1959

1960
        Ok(())
6✔
1961
    }
6✔
1962
}
1963

1964
impl EncodeIntoContext for Location<'_> {
1965
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1966
        self.location.encode_ctx(ctx)?;
6✔
1967

1968
        for body_extension in &self.extensions {
10✔
1969
            ctx.write_all(b" ")?;
4✔
1970
            body_extension.encode_ctx(ctx)?;
4✔
1971
        }
1972

1973
        Ok(())
6✔
1974
    }
6✔
1975
}
1976

1977
impl EncodeIntoContext for BodyExtension<'_> {
1978
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1979
        match self {
6✔
UNCOV
1980
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
1981
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
1982
            BodyExtension::List(list) => {
2✔
1983
                ctx.write_all(b"(")?;
2✔
1984
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
1985
                ctx.write_all(b")")
2✔
1986
            }
1987
        }
1988
    }
6✔
1989
}
1990

1991
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
1992
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
1993
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
14✔
1994
    }
14✔
1995
}
1996

1997
impl EncodeIntoContext for CommandContinuationRequest<'_> {
1998
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1999
        match self {
8✔
2000
            Self::Basic(continue_basic) => match continue_basic.code() {
8✔
UNCOV
2001
                Some(code) => {
×
UNCOV
2002
                    ctx.write_all(b"+ [")?;
×
UNCOV
2003
                    code.encode_ctx(ctx)?;
×
UNCOV
2004
                    ctx.write_all(b"] ")?;
×
UNCOV
2005
                    continue_basic.text().encode_ctx(ctx)?;
×
UNCOV
2006
                    ctx.write_all(b"\r\n")
×
2007
                }
2008
                None => {
2009
                    ctx.write_all(b"+ ")?;
8✔
2010
                    continue_basic.text().encode_ctx(ctx)?;
8✔
2011
                    ctx.write_all(b"\r\n")
8✔
2012
                }
2013
            },
UNCOV
2014
            Self::Base64(data) => {
×
UNCOV
2015
                ctx.write_all(b"+ ")?;
×
2016
                ctx.write_all(base64.encode(data).as_bytes())?;
×
2017
                ctx.write_all(b"\r\n")
×
2018
            }
2019
        }
2020
    }
8✔
2021
}
2022

2023
pub(crate) mod utils {
2024
    use std::io::Write;
2025

2026
    use super::{EncodeContext, EncodeIntoContext};
2027

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

2030
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
2031

2032
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
916✔
2033
        elements: &[I],
916✔
2034
        sep: &[u8],
916✔
2035
        ctx: &mut EncodeContext,
916✔
2036
    ) -> std::io::Result<()> {
916✔
2037
        if let Some((last, head)) = elements.split_last() {
916✔
2038
            for item in head {
1,346✔
2039
                item.encode_ctx(ctx)?;
594✔
2040
                ctx.write_all(sep)?;
594✔
2041
            }
2042

2043
            last.encode_ctx(ctx)
752✔
2044
        } else {
2045
            Ok(())
164✔
2046
        }
2047
    }
916✔
2048

2049
    impl<T> EncodeIntoContext for List1OrNil<'_, T>
2050
    where
2051
        T: EncodeIntoContext,
2052
    {
2053
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
66✔
2054
            if let Some((last, head)) = self.0.split_last() {
66✔
2055
                ctx.write_all(b"(")?;
40✔
2056

2057
                for item in head {
48✔
2058
                    item.encode_ctx(ctx)?;
8✔
2059
                    ctx.write_all(self.1)?;
8✔
2060
                }
2061

2062
                last.encode_ctx(ctx)?;
40✔
2063

2064
                ctx.write_all(b")")
40✔
2065
            } else {
2066
                ctx.write_all(b"NIL")
26✔
2067
            }
2068
        }
66✔
2069
    }
2070

2071
    impl<T> EncodeIntoContext for List1AttributeValueOrNil<'_, T>
2072
    where
2073
        T: EncodeIntoContext,
2074
    {
2075
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
2076
            if let Some((last, head)) = self.0.split_last() {
20✔
2077
                ctx.write_all(b"(")?;
8✔
2078

2079
                for (attribute, value) in head {
8✔
UNCOV
2080
                    attribute.encode_ctx(ctx)?;
×
UNCOV
2081
                    ctx.write_all(b" ")?;
×
UNCOV
2082
                    value.encode_ctx(ctx)?;
×
UNCOV
2083
                    ctx.write_all(b" ")?;
×
2084
                }
2085

2086
                let (attribute, value) = last;
8✔
2087
                attribute.encode_ctx(ctx)?;
8✔
2088
                ctx.write_all(b" ")?;
8✔
2089
                value.encode_ctx(ctx)?;
8✔
2090

2091
                ctx.write_all(b")")
8✔
2092
            } else {
2093
                ctx.write_all(b"NIL")
12✔
2094
            }
2095
        }
20✔
2096
    }
2097
}
2098

2099
#[cfg(test)]
2100
mod tests {
2101
    use std::num::NonZeroU32;
2102

2103
    use imap_types::{
2104
        auth::AuthMechanism,
2105
        command::{Command, CommandBody},
2106
        core::{AString, Literal, NString, Vec1},
2107
        fetch::MessageDataItem,
2108
        response::{Data, Response},
2109
        utils::escape_byte_string,
2110
    };
2111

2112
    use super::*;
2113

2114
    #[test]
2115
    fn test_api_encoder_usage() {
2✔
2116
        let cmd = Command::new(
2✔
2117
            "A",
2118
            CommandBody::login(
2✔
2119
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
2120
                "password",
2121
            )
2122
            .unwrap(),
2✔
2123
        )
2124
        .unwrap();
2✔
2125

2126
        // Dump.
2127
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
2128

2129
        // Encoded.
2130
        let encoded = CommandCodec::default().encode(&cmd);
2✔
2131

2132
        let mut out = Vec::new();
2✔
2133

2134
        for x in encoded {
8✔
2135
            match x {
6✔
2136
                Fragment::Line { data } => {
4✔
2137
                    println!("C: {}", escape_byte_string(&data));
4✔
2138
                    out.extend_from_slice(&data);
4✔
2139
                }
4✔
2140
                Fragment::Literal { data, mode } => {
2✔
2141
                    match mode {
2✔
UNCOV
2142
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
2143
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
2144
                    }
2145

2146
                    println!("C: {}", escape_byte_string(&data));
2✔
2147
                    out.extend_from_slice(&data);
2✔
2148
                }
2149
            }
2150
        }
2151

2152
        assert_eq!(got_encoded, out);
2✔
2153
    }
2✔
2154

2155
    #[test]
2156
    fn test_encode_command() {
2✔
2157
        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2✔
2158
            (
2✔
2159
                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2✔
2160
                [Fragment::Line {
2✔
2161
                    data: b"A LOGIN alice pass\r\n".to_vec(),
2✔
2162
                }]
2✔
2163
                .as_ref(),
2✔
2164
            ),
2✔
2165
            (
2✔
2166
                Command::new(
2✔
2167
                    "A",
2✔
2168
                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2✔
2169
                )
2✔
2170
                .unwrap(),
2✔
2171
                [
2✔
2172
                    Fragment::Line {
2✔
2173
                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
2174
                    },
2✔
2175
                    Fragment::Literal {
2✔
2176
                        data: b"\xCA\xFE".to_vec(),
2✔
2177
                        mode: LiteralMode::Sync,
2✔
2178
                    },
2✔
2179
                    Fragment::Line {
2✔
2180
                        data: b"\r\n".to_vec(),
2✔
2181
                    },
2✔
2182
                ]
2✔
2183
                .as_ref(),
2✔
2184
            ),
2✔
2185
            (
2✔
2186
                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2✔
2187
                [Fragment::Line {
2✔
2188
                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2✔
2189
                }]
2✔
2190
                .as_ref(),
2✔
2191
            ),
2✔
2192
            (
2✔
2193
                Command::new(
2✔
2194
                    "A",
2✔
2195
                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2✔
2196
                )
2✔
2197
                .unwrap(),
2✔
2198
                [Fragment::Line {
2✔
2199
                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2✔
2200
                }]
2✔
2201
                .as_ref(),
2✔
2202
            ),
2✔
2203
            (
2✔
2204
                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2✔
2205
                [Fragment::Line {
2✔
2206
                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2✔
2207
                }]
2✔
2208
                .as_ref(),
2✔
2209
            ),
2✔
2210
            (
2✔
2211
                Command::new(
2✔
2212
                    "A",
2✔
2213
                    CommandBody::authenticate_with_ir(
2✔
2214
                        AuthMechanism::Plain,
2✔
2215
                        b"\x00alice\x00pass".as_ref(),
2✔
2216
                    ),
2✔
2217
                )
2✔
2218
                .unwrap(),
2✔
2219
                [Fragment::Line {
2✔
2220
                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2✔
2221
                }]
2✔
2222
                .as_ref(),
2✔
2223
            ),
2✔
2224
        ]);
2✔
2225
    }
2✔
2226

2227
    #[test]
2228
    fn test_encode_response() {
2✔
2229
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
2230
            (
2✔
2231
                Response::Data(Data::Fetch {
2✔
2232
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2233
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2234
                        section: None,
2✔
2235
                        origin: None,
2✔
2236
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
2237
                    }),
2✔
2238
                }),
2✔
2239
                [
2✔
2240
                    Fragment::Line {
2✔
2241
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
2242
                    },
2✔
2243
                    Fragment::Literal {
2✔
2244
                        data: b"ABCDE".to_vec(),
2✔
2245
                        mode: LiteralMode::Sync,
2✔
2246
                    },
2✔
2247
                    Fragment::Line {
2✔
2248
                        data: b")\r\n".to_vec(),
2✔
2249
                    },
2✔
2250
                ]
2✔
2251
                .as_ref(),
2✔
2252
            ),
2✔
2253
            (
2✔
2254
                Response::Data(Data::Fetch {
2✔
2255
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2256
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2257
                        section: None,
2✔
2258
                        origin: None,
2✔
2259
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
2260
                    }),
2✔
2261
                }),
2✔
2262
                [
2✔
2263
                    Fragment::Line {
2✔
2264
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
2265
                    },
2✔
2266
                    Fragment::Literal {
2✔
2267
                        data: b"ABCDE".to_vec(),
2✔
2268
                        mode: LiteralMode::NonSync,
2✔
2269
                    },
2✔
2270
                    Fragment::Line {
2✔
2271
                        data: b")\r\n".to_vec(),
2✔
2272
                    },
2✔
2273
                ]
2✔
2274
                .as_ref(),
2✔
2275
            ),
2✔
2276
        ])
2✔
2277
    }
2✔
2278

2279
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
2280
    where
4✔
2281
        E: Encoder<Message<'a> = M> + Default,
4✔
2282
        F: AsRef<[Fragment]>,
4✔
2283
    {
2284
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
2285
            println!("# Testing {i}");
16✔
2286

2287
            let encoder = E::default().encode(obj);
16✔
2288
            let actions = actions.as_ref();
16✔
2289

2290
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2291
        }
2292
    }
4✔
2293
}
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