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

duesee / imap-codec / 12845794151

18 Jan 2025 04:52PM UTC coverage: 91.608% (-1.3%) from 92.896%
12845794151

Pull #631

github

web-flow
Merge 9bf9a4eca into a4498b1ec
Pull Request #631: feat: Implement CONDSTORE/QRESYNC (3/N)

179 of 367 new or added lines in 11 files covered. (48.77%)

3 existing lines in 3 files now uncovered.

11462 of 12512 relevant lines covered (91.61%)

886.54 hits per line

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

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

48
#[cfg(feature = "ext_condstore_qresync")]
49
use std::num::NonZeroU64;
50
use std::{borrow::Borrow, collections::VecDeque, io::Write, num::NonZeroU32};
51

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

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

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

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

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

130
impl Encoded {
131
    /// Dump the (remaining) encoded data without being guided by [`Fragment`]s.
132
    pub fn dump(self) -> Vec<u8> {
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,494✔
174
        Self::default()
2,494✔
175
    }
2,494✔
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,494✔
191
        let Self {
2,494✔
192
            accumulator,
2,494✔
193
            mut items,
2,494✔
194
        } = self;
2,494✔
195

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

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

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

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

215
        out
448✔
216
    }
448✔
217
}
218

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

1,540✔
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
            } => {
10✔
302
                ctx.write_all(b"AUTHENTICATE")?;
10✔
303
                ctx.write_all(b" ")?;
10✔
304
                mechanism.encode_ctx(ctx)?;
10✔
305

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

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

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

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

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

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

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

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

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

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

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

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

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

551
                sequence_set.encode_ctx(ctx)?;
16✔
552
                ctx.write_all(b" ")?;
16✔
553

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

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

567
                ctx.write_all(b"FLAGS")?;
16✔
568

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

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

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

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

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

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

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

690
                ctx.write_all(b" ")?;
14✔
691
                mailbox.encode_ctx(ctx)?;
14✔
692

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

695
                if entries.as_ref().len() == 1 {
14✔
696
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
697
                } else {
698
                    ctx.write_all(b"(")?;
×
699
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
700
                    ctx.write_all(b")")
×
701
                }
702
            }
703
        }
704
    }
698✔
705
}
706

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

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

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

NEW
742
                if let Some(known_uids) = known_uids {
×
NEW
743
                    write!(ctx, " ")?;
×
NEW
744
                    known_uids.encode_ctx(ctx)?;
×
NEW
745
                }
×
746

NEW
747
                if let Some((known_sequence_set, known_uid_set)) = seq_match_data {
×
NEW
748
                    write!(ctx, " (")?;
×
NEW
749
                    known_sequence_set.encode_ctx(ctx)?;
×
NEW
750
                    write!(ctx, " ")?;
×
NEW
751
                    known_uid_set.encode_ctx(ctx)?;
×
NEW
752
                    write!(ctx, ")")?;
×
NEW
753
                }
×
754

NEW
755
                write!(ctx, ")")
×
756
            }
757
        }
NEW
758
    }
×
759
}
760

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

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

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

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

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

801
impl EncodeIntoContext for IString<'_> {
802
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
516✔
803
        match self {
516✔
804
            Self::Literal(val) => val.encode_ctx(ctx),
44✔
805
            Self::Quoted(val) => val.encode_ctx(ctx),
472✔
806
        }
807
    }
516✔
808
}
809

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

817
        ctx.push_line();
44✔
818
        ctx.write_all(self.as_ref())?;
44✔
819
        ctx.push_literal(self.mode());
44✔
820

44✔
821
        Ok(())
44✔
822
    }
44✔
823
}
824

825
impl EncodeIntoContext for Quoted<'_> {
826
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
480✔
827
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
480✔
828
    }
480✔
829
}
830

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

840
impl EncodeIntoContext for MailboxOther<'_> {
841
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
536✔
842
        self.inner().encode_ctx(ctx)
536✔
843
    }
536✔
844
}
845

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

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

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

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

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

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

901
impl EncodeIntoContext for DateTime {
902
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
903
        self.as_ref().encode_ctx(ctx)
14✔
904
    }
14✔
905
}
906

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1238
// ----- Responses ---------------------------------------------------------------------------------
1239

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1635
        ctx.write_all(b"\r\n")
716✔
1636
    }
716✔
1637
}
1638

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

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

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

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

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

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

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

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

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

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

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

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

1915
        Ok(())
48✔
1916
    }
48✔
1917
}
1918

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

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

1928
        Ok(())
6✔
1929
    }
6✔
1930
}
1931

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

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

1941
        Ok(())
×
1942
    }
×
1943
}
1944

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

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

1963
        Ok(())
6✔
1964
    }
6✔
1965
}
1966

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

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

1976
        Ok(())
6✔
1977
    }
6✔
1978
}
1979

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

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

1989
        Ok(())
6✔
1990
    }
6✔
1991
}
1992

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

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

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

2039
pub(crate) mod utils {
2040
    use std::io::Write;
2041

2042
    use super::{EncodeContext, EncodeIntoContext};
2043

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

2046
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
2047

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

2059
            last.encode_ctx(ctx)
750✔
2060
        } else {
2061
            Ok(())
164✔
2062
        }
2063
    }
914✔
2064

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

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

2078
                last.encode_ctx(ctx)?;
40✔
2079

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

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

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

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

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

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

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

2128
    use super::*;
2129

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

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

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

2✔
2148
        let mut out = Vec::new();
2✔
2149

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

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

2168
        assert_eq!(got_encoded, out);
2✔
2169
    }
2✔
2170

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

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

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

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

16✔
2306
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2307
        }
2308
    }
4✔
2309
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc