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

duesee / imap-codec / 6863839143

14 Nov 2023 12:48PM UTC coverage: 93.41%. First build
6863839143

push

github

duesee
refactor(encoder)!: Add `Fragment::AuthData`

14 of 18 new or added lines in 1 file covered. (77.78%)

8760 of 9378 relevant lines covered (93.41%)

925.38 hits per line

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

91.16
/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
//!         Fragment::AuthData { data } => {
37
//!             // Wait for a continuation request.
38
//!             println!("S: + ...");
39
//!
40
//!             println!("C: {}", String::from_utf8(data).unwrap());
41
//!         }
42
//!     }
43
//! }
44
//! ```
45
//!
46
//! Output of example:
47
//!
48
//! ```imap
49
//! C: A1 LOGIN alice {10}
50
//! S: + ...
51
//! C: Pa²²W0rD
52
//! ```
53

54
use std::{borrow::Borrow, io::Write, num::NonZeroU32};
55

56
use base64::{engine::general_purpose::STANDARD as base64, Engine};
57
use chrono::{DateTime as ChronoDateTime, FixedOffset};
58
use imap_types::{
59
    auth::{AuthMechanism, AuthenticateData},
60
    body::{
61
        BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
62
        MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
63
    },
64
    command::{Command, CommandBody},
65
    core::{
66
        AString, Atom, AtomExt, Charset, IString, Literal, LiteralMode, NString, Quoted,
67
        QuotedChar, Tag, Text,
68
    },
69
    datetime::{DateTime, NaiveDate},
70
    envelope::{Address, Envelope},
71
    extensions::idle::IdleDone,
72
    fetch::{
73
        Macro, MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName, Part, Section,
74
    },
75
    flag::{Flag, FlagFetch, FlagNameAttribute, FlagPerm, StoreResponse, StoreType},
76
    mailbox::{ListCharString, ListMailbox, Mailbox, MailboxOther},
77
    response::{
78
        Bye, Capability, Code, CodeOther, CommandContinuationRequest, Data, Greeting, GreetingKind,
79
        Response, Status, StatusBody, StatusKind, Tagged,
80
    },
81
    search::SearchKey,
82
    sequence::{SeqOrUid, Sequence, SequenceSet},
83
    status::{StatusDataItem, StatusDataItemName},
84
    utils::escape_quoted,
85
};
86
use utils::{join_serializable, List1AttributeValueOrNil, List1OrNil};
87

88
use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
89

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

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

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

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

138
        for fragment in self.items {
4,260✔
139
            match fragment {
2,141✔
140
                Fragment::Line { mut data } => out.append(&mut data),
2,130✔
141
                Fragment::Literal { mut data, .. } => out.append(&mut data),
11✔
NEW
142
                Fragment::AuthData { mut data } => out.append(&mut data),
×
143
            }
144
        }
145

146
        out
2,119✔
147
    }
2,119✔
148
}
149

150
impl Iterator for Encoded {
151
    type Item = Fragment;
152

153
    fn next(&mut self) -> Option<Self::Item> {
52✔
154
        if !self.items.is_empty() {
52✔
155
            Some(self.items.remove(0))
34✔
156
        } else {
157
            None
18✔
158
        }
159
    }
52✔
160
}
161

162
/// The intended action of a client or server.
163
#[derive(Clone, Debug, Eq, PartialEq)]
28✔
164
pub enum Fragment {
165
    /// A line that is ready to be send.
166
    Line { data: Vec<u8> },
167

168
    /// A literal that may require an action before it should be send.
169
    Literal { data: Vec<u8>, mode: LiteralMode },
170

171
    /// A auth data line that may require an action before it should be send.
172
    AuthData { data: Vec<u8> },
173
}
174

175
//--------------------------------------------------------------------------------------------------
176

177
#[derive(Clone, Debug, Default, Eq, PartialEq)]
2,515✔
178
pub(crate) struct EncodeContext {
179
    accumulator: Vec<u8>,
180
    items: Vec<Fragment>,
181
}
182

183
impl EncodeContext {
184
    pub fn new() -> Self {
2,515✔
185
        Self::default()
2,515✔
186
    }
2,515✔
187

188
    pub fn push_line(&mut self) {
43✔
189
        self.items.push(Fragment::Line {
43✔
190
            data: std::mem::take(&mut self.accumulator),
43✔
191
        })
43✔
192
    }
43✔
193

194
    pub fn push_literal(&mut self, mode: LiteralMode) {
43✔
195
        self.items.push(Fragment::Literal {
43✔
196
            data: std::mem::take(&mut self.accumulator),
43✔
197
            mode,
43✔
198
        })
43✔
199
    }
43✔
200

201
    pub fn push_authenticate_data(&mut self) {
2✔
202
        self.items.push(Fragment::AuthData {
2✔
203
            data: std::mem::take(&mut self.accumulator),
2✔
204
        })
2✔
205
    }
2✔
206

207
    pub fn into_items(self) -> Vec<Fragment> {
2,515✔
208
        let Self {
2,515✔
209
            accumulator,
2,515✔
210
            mut items,
2,515✔
211
        } = self;
2,515✔
212

2,515✔
213
        if !accumulator.is_empty() {
2,515✔
214
            items.push(Fragment::Line { data: accumulator });
2,513✔
215
        }
2,513✔
216

217
        items
2,515✔
218
    }
2,515✔
219

220
    #[cfg(test)]
221
    pub(crate) fn dump(self) -> Vec<u8> {
378✔
222
        let mut out = Vec::new();
378✔
223

224
        for item in self.into_items() {
426✔
225
            match item {
426✔
226
                Fragment::Line { data }
400✔
227
                | Fragment::Literal { data, .. }
24✔
228
                | Fragment::AuthData { data } => out.extend_from_slice(&data),
426✔
229
            }
230
        }
231

232
        out
378✔
233
    }
378✔
234
}
235

236
impl Write for EncodeContext {
237
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
19,236✔
238
        self.accumulator.extend_from_slice(buf);
19,236✔
239
        Ok(buf.len())
19,236✔
240
    }
19,236✔
241

242
    fn flush(&mut self) -> std::io::Result<()> {
×
243
        Ok(())
×
244
    }
×
245
}
246

247
macro_rules! impl_encoder_for_codec {
248
    ($codec:ty, $message:ty) => {
249
        impl Encoder for $codec {
250
            type Message<'a> = $message;
251

252
            fn encode(&self, message: &Self::Message<'_>) -> Encoded {
2,137✔
253
                let mut encode_context = EncodeContext::new();
2,137✔
254
                EncodeIntoContext::encode_ctx(message.borrow(), &mut encode_context).unwrap();
2,137✔
255

2,137✔
256
                Encoded {
2,137✔
257
                    items: encode_context.into_items(),
2,137✔
258
                }
2,137✔
259
            }
2,137✔
260
        }
261
    };
262
}
263

264
impl_encoder_for_codec!(GreetingCodec, Greeting<'a>);
265
impl_encoder_for_codec!(CommandCodec, Command<'a>);
266
impl_encoder_for_codec!(AuthenticateDataCodec, AuthenticateData);
267
impl_encoder_for_codec!(ResponseCodec, Response<'a>);
268
impl_encoder_for_codec!(IdleDoneCodec, IdleDone);
269

270
// -------------------------------------------------------------------------------------------------
271

272
pub(crate) trait EncodeIntoContext {
273
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
274
}
275

276
// ----- Primitive ---------------------------------------------------------------------------------
277

278
impl EncodeIntoContext for u32 {
279
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
34✔
280
        ctx.write_all(self.to_string().as_bytes())
34✔
281
    }
34✔
282
}
283

284
impl EncodeIntoContext for u64 {
285
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
4✔
286
        ctx.write_all(self.to_string().as_bytes())
4✔
287
    }
4✔
288
}
289

290
// ----- Command -----------------------------------------------------------------------------------
291

292
impl<'a> EncodeIntoContext for Command<'a> {
293
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
294
        self.tag.encode_ctx(ctx)?;
691✔
295
        ctx.write_all(b" ")?;
691✔
296
        self.body.encode_ctx(ctx)?;
691✔
297
        ctx.write_all(b"\r\n")
691✔
298
    }
691✔
299
}
300

301
impl<'a> EncodeIntoContext for Tag<'a> {
302
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,308✔
303
        ctx.write_all(self.inner().as_bytes())
1,308✔
304
    }
1,308✔
305
}
306

307
impl<'a> EncodeIntoContext for CommandBody<'a> {
308
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
691✔
309
        match self {
691✔
310
            CommandBody::Capability => ctx.write_all(b"CAPABILITY"),
54✔
311
            CommandBody::Noop => ctx.write_all(b"NOOP"),
24✔
312
            CommandBody::Logout => ctx.write_all(b"LOGOUT"),
18✔
313
            #[cfg(feature = "starttls")]
314
            CommandBody::StartTLS => ctx.write_all(b"STARTTLS"),
18✔
315
            CommandBody::Authenticate {
316
                mechanism,
10✔
317
                initial_response,
10✔
318
            } => {
10✔
319
                ctx.write_all(b"AUTHENTICATE")?;
10✔
320
                ctx.write_all(b" ")?;
10✔
321
                mechanism.encode_ctx(ctx)?;
10✔
322

323
                if let Some(ir) = initial_response {
10✔
324
                    ctx.write_all(b" ")?;
6✔
325

326
                    // RFC 4959 (https://datatracker.ietf.org/doc/html/rfc4959#section-3)
327
                    // "To send a zero-length initial response, the client MUST send a single pad character ("=").
328
                    // This indicates that the response is present, but is a zero-length string."
329
                    if ir.declassify().is_empty() {
6✔
330
                        ctx.write_all(b"=")?;
2✔
331
                    } else {
332
                        ctx.write_all(base64.encode(ir.declassify()).as_bytes())?;
4✔
333
                    };
334
                };
4✔
335

336
                Ok(())
10✔
337
            }
338
            CommandBody::Login { username, password } => {
61✔
339
                ctx.write_all(b"LOGIN")?;
61✔
340
                ctx.write_all(b" ")?;
61✔
341
                username.encode_ctx(ctx)?;
61✔
342
                ctx.write_all(b" ")?;
61✔
343
                password.declassify().encode_ctx(ctx)
61✔
344
            }
345
            CommandBody::Select { mailbox } => {
22✔
346
                ctx.write_all(b"SELECT")?;
22✔
347
                ctx.write_all(b" ")?;
22✔
348
                mailbox.encode_ctx(ctx)
22✔
349
            }
350
            CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
2✔
351
            CommandBody::Examine { mailbox } => {
9✔
352
                ctx.write_all(b"EXAMINE")?;
9✔
353
                ctx.write_all(b" ")?;
9✔
354
                mailbox.encode_ctx(ctx)
9✔
355
            }
356
            CommandBody::Create { mailbox } => {
18✔
357
                ctx.write_all(b"CREATE")?;
18✔
358
                ctx.write_all(b" ")?;
18✔
359
                mailbox.encode_ctx(ctx)
18✔
360
            }
361
            CommandBody::Delete { mailbox } => {
54✔
362
                ctx.write_all(b"DELETE")?;
54✔
363
                ctx.write_all(b" ")?;
54✔
364
                mailbox.encode_ctx(ctx)
54✔
365
            }
366
            CommandBody::Rename {
367
                from: mailbox,
27✔
368
                to: new_mailbox,
27✔
369
            } => {
27✔
370
                ctx.write_all(b"RENAME")?;
27✔
371
                ctx.write_all(b" ")?;
27✔
372
                mailbox.encode_ctx(ctx)?;
27✔
373
                ctx.write_all(b" ")?;
27✔
374
                new_mailbox.encode_ctx(ctx)
27✔
375
            }
376
            CommandBody::Subscribe { mailbox } => {
9✔
377
                ctx.write_all(b"SUBSCRIBE")?;
9✔
378
                ctx.write_all(b" ")?;
9✔
379
                mailbox.encode_ctx(ctx)
9✔
380
            }
381
            CommandBody::Unsubscribe { mailbox } => {
9✔
382
                ctx.write_all(b"UNSUBSCRIBE")?;
9✔
383
                ctx.write_all(b" ")?;
9✔
384
                mailbox.encode_ctx(ctx)
9✔
385
            }
386
            CommandBody::List {
387
                reference,
117✔
388
                mailbox_wildcard,
117✔
389
            } => {
117✔
390
                ctx.write_all(b"LIST")?;
117✔
391
                ctx.write_all(b" ")?;
117✔
392
                reference.encode_ctx(ctx)?;
117✔
393
                ctx.write_all(b" ")?;
117✔
394
                mailbox_wildcard.encode_ctx(ctx)
117✔
395
            }
396
            CommandBody::Lsub {
397
                reference,
18✔
398
                mailbox_wildcard,
18✔
399
            } => {
18✔
400
                ctx.write_all(b"LSUB")?;
18✔
401
                ctx.write_all(b" ")?;
18✔
402
                reference.encode_ctx(ctx)?;
18✔
403
                ctx.write_all(b" ")?;
18✔
404
                mailbox_wildcard.encode_ctx(ctx)
18✔
405
            }
406
            CommandBody::Status {
407
                mailbox,
11✔
408
                item_names,
11✔
409
            } => {
11✔
410
                ctx.write_all(b"STATUS")?;
11✔
411
                ctx.write_all(b" ")?;
11✔
412
                mailbox.encode_ctx(ctx)?;
11✔
413
                ctx.write_all(b" ")?;
11✔
414
                ctx.write_all(b"(")?;
11✔
415
                join_serializable(item_names, b" ", ctx)?;
11✔
416
                ctx.write_all(b")")
11✔
417
            }
418
            CommandBody::Append {
419
                mailbox,
×
420
                flags,
×
421
                date,
×
422
                message,
×
423
            } => {
×
424
                ctx.write_all(b"APPEND")?;
×
425
                ctx.write_all(b" ")?;
×
426
                mailbox.encode_ctx(ctx)?;
×
427

428
                if !flags.is_empty() {
×
429
                    ctx.write_all(b" ")?;
×
430
                    ctx.write_all(b"(")?;
×
431
                    join_serializable(flags, b" ", ctx)?;
×
432
                    ctx.write_all(b")")?;
×
433
                }
×
434

435
                if let Some(date) = date {
×
436
                    ctx.write_all(b" ")?;
×
437
                    date.encode_ctx(ctx)?;
×
438
                }
×
439

440
                ctx.write_all(b" ")?;
×
441
                message.encode_ctx(ctx)
×
442
            }
443
            CommandBody::Check => ctx.write_all(b"CHECK"),
9✔
444
            CommandBody::Close => ctx.write_all(b"CLOSE"),
9✔
445
            CommandBody::Expunge => ctx.write_all(b"EXPUNGE"),
18✔
446
            CommandBody::Search {
447
                charset,
18✔
448
                criteria,
18✔
449
                uid,
18✔
450
            } => {
18✔
451
                if *uid {
18✔
452
                    ctx.write_all(b"UID SEARCH")?;
×
453
                } else {
454
                    ctx.write_all(b"SEARCH")?;
18✔
455
                }
456
                if let Some(charset) = charset {
18✔
457
                    ctx.write_all(b" CHARSET ")?;
×
458
                    charset.encode_ctx(ctx)?;
×
459
                }
18✔
460
                ctx.write_all(b" ")?;
18✔
461
                criteria.encode_ctx(ctx)
18✔
462
            }
463
            CommandBody::Fetch {
464
                sequence_set,
45✔
465
                macro_or_item_names,
45✔
466
                uid,
45✔
467
            } => {
45✔
468
                if *uid {
45✔
469
                    ctx.write_all(b"UID FETCH ")?;
18✔
470
                } else {
471
                    ctx.write_all(b"FETCH ")?;
27✔
472
                }
473

474
                sequence_set.encode_ctx(ctx)?;
45✔
475
                ctx.write_all(b" ")?;
45✔
476
                macro_or_item_names.encode_ctx(ctx)
45✔
477
            }
478
            CommandBody::Store {
479
                sequence_set,
18✔
480
                kind,
18✔
481
                response,
18✔
482
                flags,
18✔
483
                uid,
18✔
484
            } => {
18✔
485
                if *uid {
18✔
486
                    ctx.write_all(b"UID STORE ")?;
×
487
                } else {
488
                    ctx.write_all(b"STORE ")?;
18✔
489
                }
490

491
                sequence_set.encode_ctx(ctx)?;
18✔
492
                ctx.write_all(b" ")?;
18✔
493

494
                match kind {
18✔
495
                    StoreType::Add => ctx.write_all(b"+")?,
18✔
496
                    StoreType::Remove => ctx.write_all(b"-")?,
×
497
                    StoreType::Replace => {}
×
498
                }
499

500
                ctx.write_all(b"FLAGS")?;
18✔
501

502
                match response {
18✔
503
                    StoreResponse::Answer => {}
18✔
504
                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
×
505
                }
506

507
                ctx.write_all(b" (")?;
18✔
508
                join_serializable(flags, b" ", ctx)?;
18✔
509
                ctx.write_all(b")")
18✔
510
            }
511
            CommandBody::Copy {
512
                sequence_set,
27✔
513
                mailbox,
27✔
514
                uid,
27✔
515
            } => {
27✔
516
                if *uid {
27✔
517
                    ctx.write_all(b"UID COPY ")?;
×
518
                } else {
519
                    ctx.write_all(b"COPY ")?;
27✔
520
                }
521
                sequence_set.encode_ctx(ctx)?;
27✔
522
                ctx.write_all(b" ")?;
27✔
523
                mailbox.encode_ctx(ctx)
27✔
524
            }
525
            CommandBody::Idle => ctx.write_all(b"IDLE"),
4✔
526
            CommandBody::Enable { capabilities } => {
24✔
527
                ctx.write_all(b"ENABLE ")?;
24✔
528
                join_serializable(capabilities.as_ref(), b" ", ctx)
24✔
529
            }
530
            CommandBody::Compress { algorithm } => {
4✔
531
                ctx.write_all(b"COMPRESS ")?;
4✔
532
                algorithm.encode_ctx(ctx)
4✔
533
            }
534
            CommandBody::GetQuota { root } => {
10✔
535
                ctx.write_all(b"GETQUOTA ")?;
10✔
536
                root.encode_ctx(ctx)
10✔
537
            }
538
            CommandBody::GetQuotaRoot { mailbox } => {
6✔
539
                ctx.write_all(b"GETQUOTAROOT ")?;
6✔
540
                mailbox.encode_ctx(ctx)
6✔
541
            }
542
            CommandBody::SetQuota { root, quotas } => {
12✔
543
                ctx.write_all(b"SETQUOTA ")?;
12✔
544
                root.encode_ctx(ctx)?;
12✔
545
                ctx.write_all(b" (")?;
12✔
546
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
547
                ctx.write_all(b")")
12✔
548
            }
549
            CommandBody::Move {
550
                sequence_set,
6✔
551
                mailbox,
6✔
552
                uid,
6✔
553
            } => {
6✔
554
                if *uid {
6✔
555
                    ctx.write_all(b"UID MOVE ")?;
2✔
556
                } else {
557
                    ctx.write_all(b"MOVE ")?;
4✔
558
                }
559
                sequence_set.encode_ctx(ctx)?;
6✔
560
                ctx.write_all(b" ")?;
6✔
561
                mailbox.encode_ctx(ctx)
6✔
562
            }
563
        }
564
    }
691✔
565
}
566

567
impl<'a> EncodeIntoContext for AuthMechanism<'a> {
568
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
569
        write!(ctx, "{}", self)
20✔
570
    }
20✔
571
}
572

573
impl EncodeIntoContext for AuthenticateData {
574
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2✔
575
        let encoded = base64.encode(self.0.declassify());
2✔
576
        ctx.write_all(encoded.as_bytes())?;
2✔
577
        ctx.write_all(b"\r\n")?;
2✔
578
        ctx.push_authenticate_data();
2✔
579

2✔
580
        Ok(())
2✔
581
    }
2✔
582
}
583

584
impl<'a> EncodeIntoContext for AString<'a> {
585
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
821✔
586
        match self {
821✔
587
            AString::Atom(atom) => atom.encode_ctx(ctx),
617✔
588
            AString::String(imap_str) => imap_str.encode_ctx(ctx),
204✔
589
        }
590
    }
821✔
591
}
592

593
impl<'a> EncodeIntoContext for Atom<'a> {
594
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
595
        ctx.write_all(self.inner().as_bytes())
6✔
596
    }
6✔
597
}
598

599
impl<'a> EncodeIntoContext for AtomExt<'a> {
600
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
617✔
601
        ctx.write_all(self.inner().as_bytes())
617✔
602
    }
617✔
603
}
604

605
impl<'a> EncodeIntoContext for IString<'a> {
606
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
525✔
607
        match self {
525✔
608
            Self::Literal(val) => val.encode_ctx(ctx),
43✔
609
            Self::Quoted(val) => val.encode_ctx(ctx),
482✔
610
        }
611
    }
525✔
612
}
613

614
impl<'a> EncodeIntoContext for Literal<'a> {
615
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
43✔
616
        match self.mode() {
43✔
617
            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
29✔
618
            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
14✔
619
        }
620

621
        ctx.push_line();
43✔
622
        ctx.write_all(self.as_ref())?;
43✔
623
        ctx.push_literal(self.mode());
43✔
624

43✔
625
        Ok(())
43✔
626
    }
43✔
627
}
628

629
impl<'a> EncodeIntoContext for Quoted<'a> {
630
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
490✔
631
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
490✔
632
    }
490✔
633
}
634

635
impl<'a> EncodeIntoContext for Mailbox<'a> {
636
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
662✔
637
        match self {
662✔
638
            Mailbox::Inbox => ctx.write_all(b"INBOX"),
69✔
639
            Mailbox::Other(other) => other.encode_ctx(ctx),
593✔
640
        }
641
    }
662✔
642
}
643

644
impl<'a> EncodeIntoContext for MailboxOther<'a> {
645
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
593✔
646
        self.inner().encode_ctx(ctx)
593✔
647
    }
593✔
648
}
649

650
impl<'a> EncodeIntoContext for ListMailbox<'a> {
651
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
135✔
652
        match self {
135✔
653
            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
90✔
654
            ListMailbox::String(istr) => istr.encode_ctx(ctx),
45✔
655
        }
656
    }
135✔
657
}
658

659
impl<'a> EncodeIntoContext for ListCharString<'a> {
660
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
661
        ctx.write_all(self.as_ref())
90✔
662
    }
90✔
663
}
664

665
impl EncodeIntoContext for StatusDataItemName {
666
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
38✔
667
        match self {
38✔
668
            Self::Messages => ctx.write_all(b"MESSAGES"),
13✔
669
            Self::Recent => ctx.write_all(b"RECENT"),
2✔
670
            Self::UidNext => ctx.write_all(b"UIDNEXT"),
11✔
671
            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
2✔
672
            Self::Unseen => ctx.write_all(b"UNSEEN"),
2✔
673
            Self::Deleted => ctx.write_all(b"DELETED"),
4✔
674
            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
4✔
675
            #[cfg(feature = "ext_condstore_qresync")]
676
            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
×
677
        }
678
    }
38✔
679
}
680

681
impl<'a> EncodeIntoContext for Flag<'a> {
682
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
351✔
683
        write!(ctx, "{}", self)
351✔
684
    }
351✔
685
}
686

687
impl<'a> EncodeIntoContext for FlagFetch<'a> {
688
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
135✔
689
        match self {
135✔
690
            Self::Flag(flag) => flag.encode_ctx(ctx),
135✔
691
            Self::Recent => ctx.write_all(b"\\Recent"),
×
692
        }
693
    }
135✔
694
}
695

696
impl<'a> EncodeIntoContext for FlagPerm<'a> {
697
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
27✔
698
        match self {
27✔
699
            Self::Flag(flag) => flag.encode_ctx(ctx),
18✔
700
            Self::Asterisk => ctx.write_all(b"\\*"),
9✔
701
        }
702
    }
27✔
703
}
704

705
impl EncodeIntoContext for DateTime {
706
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
15✔
707
        self.as_ref().encode_ctx(ctx)
15✔
708
    }
15✔
709
}
710

711
impl<'a> EncodeIntoContext for Charset<'a> {
712
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10✔
713
        match self {
10✔
714
            Charset::Atom(atom) => atom.encode_ctx(ctx),
2✔
715
            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
8✔
716
        }
717
    }
10✔
718
}
719

720
impl<'a> EncodeIntoContext for SearchKey<'a> {
721
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
142✔
722
        match self {
142✔
723
            SearchKey::All => ctx.write_all(b"ALL"),
2✔
724
            SearchKey::Answered => ctx.write_all(b"ANSWERED"),
6✔
725
            SearchKey::Bcc(astring) => {
2✔
726
                ctx.write_all(b"BCC ")?;
2✔
727
                astring.encode_ctx(ctx)
2✔
728
            }
729
            SearchKey::Before(date) => {
2✔
730
                ctx.write_all(b"BEFORE ")?;
2✔
731
                date.encode_ctx(ctx)
2✔
732
            }
733
            SearchKey::Body(astring) => {
2✔
734
                ctx.write_all(b"BODY ")?;
2✔
735
                astring.encode_ctx(ctx)
2✔
736
            }
737
            SearchKey::Cc(astring) => {
2✔
738
                ctx.write_all(b"CC ")?;
2✔
739
                astring.encode_ctx(ctx)
2✔
740
            }
741
            SearchKey::Deleted => ctx.write_all(b"DELETED"),
2✔
742
            SearchKey::Flagged => ctx.write_all(b"FLAGGED"),
11✔
743
            SearchKey::From(astring) => {
11✔
744
                ctx.write_all(b"FROM ")?;
11✔
745
                astring.encode_ctx(ctx)
11✔
746
            }
747
            SearchKey::Keyword(flag_keyword) => {
2✔
748
                ctx.write_all(b"KEYWORD ")?;
2✔
749
                flag_keyword.encode_ctx(ctx)
2✔
750
            }
751
            SearchKey::New => ctx.write_all(b"NEW"),
6✔
752
            SearchKey::Old => ctx.write_all(b"OLD"),
2✔
753
            SearchKey::On(date) => {
2✔
754
                ctx.write_all(b"ON ")?;
2✔
755
                date.encode_ctx(ctx)
2✔
756
            }
757
            SearchKey::Recent => ctx.write_all(b"RECENT"),
4✔
758
            SearchKey::Seen => ctx.write_all(b"SEEN"),
4✔
759
            SearchKey::Since(date) => {
11✔
760
                ctx.write_all(b"SINCE ")?;
11✔
761
                date.encode_ctx(ctx)
11✔
762
            }
763
            SearchKey::Subject(astring) => {
2✔
764
                ctx.write_all(b"SUBJECT ")?;
2✔
765
                astring.encode_ctx(ctx)
2✔
766
            }
767
            SearchKey::Text(astring) => {
11✔
768
                ctx.write_all(b"TEXT ")?;
11✔
769
                astring.encode_ctx(ctx)
11✔
770
            }
771
            SearchKey::To(astring) => {
2✔
772
                ctx.write_all(b"TO ")?;
2✔
773
                astring.encode_ctx(ctx)
2✔
774
            }
775
            SearchKey::Unanswered => ctx.write_all(b"UNANSWERED"),
2✔
776
            SearchKey::Undeleted => ctx.write_all(b"UNDELETED"),
2✔
777
            SearchKey::Unflagged => ctx.write_all(b"UNFLAGGED"),
2✔
778
            SearchKey::Unkeyword(flag_keyword) => {
2✔
779
                ctx.write_all(b"UNKEYWORD ")?;
2✔
780
                flag_keyword.encode_ctx(ctx)
2✔
781
            }
782
            SearchKey::Unseen => ctx.write_all(b"UNSEEN"),
2✔
783
            SearchKey::Draft => ctx.write_all(b"DRAFT"),
2✔
784
            SearchKey::Header(header_fld_name, astring) => {
2✔
785
                ctx.write_all(b"HEADER ")?;
2✔
786
                header_fld_name.encode_ctx(ctx)?;
2✔
787
                ctx.write_all(b" ")?;
2✔
788
                astring.encode_ctx(ctx)
2✔
789
            }
790
            SearchKey::Larger(number) => write!(ctx, "LARGER {number}"),
2✔
791
            SearchKey::Not(search_key) => {
11✔
792
                ctx.write_all(b"NOT ")?;
11✔
793
                search_key.encode_ctx(ctx)
11✔
794
            }
795
            SearchKey::Or(search_key_a, search_key_b) => {
2✔
796
                ctx.write_all(b"OR ")?;
2✔
797
                search_key_a.encode_ctx(ctx)?;
2✔
798
                ctx.write_all(b" ")?;
2✔
799
                search_key_b.encode_ctx(ctx)
2✔
800
            }
801
            SearchKey::SentBefore(date) => {
2✔
802
                ctx.write_all(b"SENTBEFORE ")?;
2✔
803
                date.encode_ctx(ctx)
2✔
804
            }
805
            SearchKey::SentOn(date) => {
2✔
806
                ctx.write_all(b"SENTON ")?;
2✔
807
                date.encode_ctx(ctx)
2✔
808
            }
809
            SearchKey::SentSince(date) => {
2✔
810
                ctx.write_all(b"SENTSINCE ")?;
2✔
811
                date.encode_ctx(ctx)
2✔
812
            }
813
            SearchKey::Smaller(number) => write!(ctx, "SMALLER {number}"),
2✔
814
            SearchKey::Uid(sequence_set) => {
2✔
815
                ctx.write_all(b"UID ")?;
2✔
816
                sequence_set.encode_ctx(ctx)
2✔
817
            }
818
            SearchKey::Undraft => ctx.write_all(b"UNDRAFT"),
2✔
819
            SearchKey::SequenceSet(sequence_set) => sequence_set.encode_ctx(ctx),
2✔
820
            SearchKey::And(search_keys) => {
13✔
821
                ctx.write_all(b"(")?;
13✔
822
                join_serializable(search_keys.as_ref(), b" ", ctx)?;
13✔
823
                ctx.write_all(b")")
13✔
824
            }
825
        }
826
    }
142✔
827
}
828

829
impl EncodeIntoContext for SequenceSet {
830
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
100✔
831
        join_serializable(self.0.as_ref(), b",", ctx)
100✔
832
    }
100✔
833
}
834

835
impl EncodeIntoContext for Sequence {
836
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
115✔
837
        match self {
115✔
838
            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
46✔
839
            Sequence::Range(from, to) => {
69✔
840
                from.encode_ctx(ctx)?;
69✔
841
                ctx.write_all(b":")?;
69✔
842
                to.encode_ctx(ctx)
69✔
843
            }
844
        }
845
    }
115✔
846
}
847

848
impl EncodeIntoContext for SeqOrUid {
849
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
184✔
850
        match self {
184✔
851
            SeqOrUid::Value(number) => write!(ctx, "{number}"),
167✔
852
            SeqOrUid::Asterisk => ctx.write_all(b"*"),
17✔
853
        }
854
    }
184✔
855
}
856

857
impl EncodeIntoContext for NaiveDate {
858
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
21✔
859
        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
21✔
860
    }
21✔
861
}
862

863
impl<'a> EncodeIntoContext for MacroOrMessageDataItemNames<'a> {
864
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
45✔
865
        match self {
45✔
866
            Self::Macro(m) => m.encode_ctx(ctx),
9✔
867
            Self::MessageDataItemNames(item_names) => {
36✔
868
                if item_names.len() == 1 {
36✔
869
                    item_names[0].encode_ctx(ctx)
27✔
870
                } else {
871
                    ctx.write_all(b"(")?;
9✔
872
                    join_serializable(item_names.as_slice(), b" ", ctx)?;
9✔
873
                    ctx.write_all(b")")
9✔
874
                }
875
            }
876
        }
877
    }
45✔
878
}
879

880
impl EncodeIntoContext for Macro {
881
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
9✔
882
        write!(ctx, "{}", self)
9✔
883
    }
9✔
884
}
885

886
impl<'a> EncodeIntoContext for MessageDataItemName<'a> {
887
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
67✔
888
        match self {
67✔
889
            Self::Body => ctx.write_all(b"BODY"),
2✔
890
            Self::BodyExt {
29✔
891
                section,
29✔
892
                partial,
29✔
893
                peek,
29✔
894
            } => {
29✔
895
                if *peek {
29✔
896
                    ctx.write_all(b"BODY.PEEK[")?;
9✔
897
                } else {
898
                    ctx.write_all(b"BODY[")?;
20✔
899
                }
900
                if let Some(section) = section {
29✔
901
                    section.encode_ctx(ctx)?;
27✔
902
                }
2✔
903
                ctx.write_all(b"]")?;
29✔
904
                if let Some((a, b)) = partial {
29✔
905
                    write!(ctx, "<{a}.{b}>")?;
9✔
906
                }
20✔
907

908
                Ok(())
29✔
909
            }
910
            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
2✔
911
            Self::Envelope => ctx.write_all(b"ENVELOPE"),
2✔
912
            Self::Flags => ctx.write_all(b"FLAGS"),
20✔
913
            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
2✔
914
            Self::Rfc822 => ctx.write_all(b"RFC822"),
2✔
915
            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
2✔
916
            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
2✔
917
            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
2✔
918
            Self::Uid => ctx.write_all(b"UID"),
2✔
919
        }
920
    }
67✔
921
}
922

923
impl<'a> EncodeIntoContext for Section<'a> {
924
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
56✔
925
        match self {
56✔
926
            Section::Part(part) => part.encode_ctx(ctx),
2✔
927
            Section::Header(maybe_part) => match maybe_part {
22✔
928
                Some(part) => {
2✔
929
                    part.encode_ctx(ctx)?;
2✔
930
                    ctx.write_all(b".HEADER")
2✔
931
                }
932
                None => ctx.write_all(b"HEADER"),
20✔
933
            },
934
            Section::HeaderFields(maybe_part, header_list) => {
13✔
935
                match maybe_part {
13✔
936
                    Some(part) => {
2✔
937
                        part.encode_ctx(ctx)?;
2✔
938
                        ctx.write_all(b".HEADER.FIELDS (")?;
2✔
939
                    }
940
                    None => ctx.write_all(b"HEADER.FIELDS (")?,
11✔
941
                };
942
                join_serializable(header_list.as_ref(), b" ", ctx)?;
13✔
943
                ctx.write_all(b")")
13✔
944
            }
945
            Section::HeaderFieldsNot(maybe_part, header_list) => {
4✔
946
                match maybe_part {
4✔
947
                    Some(part) => {
2✔
948
                        part.encode_ctx(ctx)?;
2✔
949
                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
2✔
950
                    }
951
                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
2✔
952
                };
953
                join_serializable(header_list.as_ref(), b" ", ctx)?;
4✔
954
                ctx.write_all(b")")
4✔
955
            }
956
            Section::Text(maybe_part) => match maybe_part {
4✔
957
                Some(part) => {
2✔
958
                    part.encode_ctx(ctx)?;
2✔
959
                    ctx.write_all(b".TEXT")
2✔
960
                }
961
                None => ctx.write_all(b"TEXT"),
2✔
962
            },
963
            Section::Mime(part) => {
11✔
964
                part.encode_ctx(ctx)?;
11✔
965
                ctx.write_all(b".MIME")
11✔
966
            }
967
        }
968
    }
56✔
969
}
970

971
impl EncodeIntoContext for Part {
972
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
21✔
973
        join_serializable(self.0.as_ref(), b".", ctx)
21✔
974
    }
21✔
975
}
976

977
impl EncodeIntoContext for NonZeroU32 {
978
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
221✔
979
        write!(ctx, "{self}")
221✔
980
    }
221✔
981
}
982

983
impl<'a> EncodeIntoContext for Capability<'a> {
984
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
258✔
985
        write!(ctx, "{}", self)
258✔
986
    }
258✔
987
}
988

989
// ----- Responses ---------------------------------------------------------------------------------
990

991
impl<'a> EncodeIntoContext for Response<'a> {
992
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,587✔
993
        match self {
1,587✔
994
            Response::Status(status) => status.encode_ctx(ctx),
840✔
995
            Response::Data(data) => data.encode_ctx(ctx),
738✔
996
            Response::CommandContinuationRequest(continue_request) => {
9✔
997
                continue_request.encode_ctx(ctx)
9✔
998
            }
999
        }
1000
    }
1,587✔
1001
}
1002

1003
impl<'a> EncodeIntoContext for Greeting<'a> {
1004
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1005
        ctx.write_all(b"* ")?;
27✔
1006
        self.kind.encode_ctx(ctx)?;
27✔
1007
        ctx.write_all(b" ")?;
27✔
1008

1009
        if let Some(ref code) = self.code {
27✔
1010
            ctx.write_all(b"[")?;
12✔
1011
            code.encode_ctx(ctx)?;
12✔
1012
            ctx.write_all(b"] ")?;
12✔
1013
        }
15✔
1014

1015
        self.text.encode_ctx(ctx)?;
27✔
1016
        ctx.write_all(b"\r\n")
27✔
1017
    }
27✔
1018
}
1019

1020
impl EncodeIntoContext for GreetingKind {
1021
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
27✔
1022
        match self {
27✔
1023
            GreetingKind::Ok => ctx.write_all(b"OK"),
12✔
1024
            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
13✔
1025
            GreetingKind::Bye => ctx.write_all(b"BYE"),
2✔
1026
        }
1027
    }
27✔
1028
}
1029

1030
impl<'a> EncodeIntoContext for Status<'a> {
1031
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
840✔
1032
        fn format_status(
945✔
1033
            tag: Option<&Tag>,
945✔
1034
            status: &str,
945✔
1035
            code: &Option<Code>,
945✔
1036
            comment: &Text,
945✔
1037
            ctx: &mut EncodeContext,
945✔
1038
        ) -> std::io::Result<()> {
945✔
1039
            match tag {
945✔
1040
                Some(tag) => tag.encode_ctx(ctx)?,
914✔
1041
                None => ctx.write_all(b"*")?,
871✔
1042
            }
840✔
1043
            ctx.write_all(b" ")?;
945✔
1044
            ctx.write_all(status.as_bytes())?;
945✔
1045
            ctx.write_all(b" ")?;
945✔
1046
            if let Some(code) = code {
945✔
1047
                ctx.write_all(b"[")?;
863✔
1048
                code.encode_ctx(ctx)?;
863✔
1049
                ctx.write_all(b"] ")?;
863✔
1050
            }
922✔
1051
            comment.encode_ctx(ctx)?;
945✔
1052
            ctx.write_all(b"\r\n")
945✔
1053
        }
945✔
1054

840✔
1055
        match self {
840✔
1056
            Self::Untagged(StatusBody { kind, code, text }) => match kind {
194✔
1057
                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
130✔
1058
                StatusKind::No => format_status(None, "NO", code, text, ctx),
33✔
1059
                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
31✔
1060
            },
1061
            Self::Tagged(Tagged {
617✔
1062
                tag,
617✔
1063
                body: StatusBody { kind, code, text },
617✔
1064
            }) => match kind {
617✔
1065
                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
580✔
1066
                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
24✔
1067
                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
13✔
1068
            },
1069
            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
29✔
1070
        }
1071
    }
840✔
1072
}
1073

1074
impl<'a> EncodeIntoContext for Code<'a> {
1075
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
156✔
1076
        match self {
156✔
1077
            Code::Alert => ctx.write_all(b"ALERT"),
25✔
1078
            Code::BadCharset { allowed } => {
2✔
1079
                if allowed.is_empty() {
2✔
1080
                    ctx.write_all(b"BADCHARSET")
2✔
1081
                } else {
1082
                    ctx.write_all(b"BADCHARSET (")?;
×
1083
                    join_serializable(allowed, b" ", ctx)?;
×
1084
                    ctx.write_all(b")")
×
1085
                }
1086
            }
1087
            Code::Capability(caps) => {
4✔
1088
                ctx.write_all(b"CAPABILITY ")?;
4✔
1089
                join_serializable(caps.as_ref(), b" ", ctx)
4✔
1090
            }
1091
            Code::Parse => ctx.write_all(b"PARSE"),
×
1092
            Code::PermanentFlags(flags) => {
18✔
1093
                ctx.write_all(b"PERMANENTFLAGS (")?;
18✔
1094
                join_serializable(flags, b" ", ctx)?;
18✔
1095
                ctx.write_all(b")")
18✔
1096
            }
1097
            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
9✔
1098
            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
18✔
1099
            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
×
1100
            Code::UidNext(next) => {
18✔
1101
                ctx.write_all(b"UIDNEXT ")?;
18✔
1102
                next.encode_ctx(ctx)
18✔
1103
            }
1104
            Code::UidValidity(validity) => {
27✔
1105
                ctx.write_all(b"UIDVALIDITY ")?;
27✔
1106
                validity.encode_ctx(ctx)
27✔
1107
            }
1108
            Code::Unseen(seq) => {
31✔
1109
                ctx.write_all(b"UNSEEN ")?;
31✔
1110
                seq.encode_ctx(ctx)
31✔
1111
            }
1112
            // RFC 2221
1113
            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1114
            Code::Referral(url) => {
×
1115
                ctx.write_all(b"REFERRAL ")?;
×
1116
                ctx.write_all(url.as_bytes())
×
1117
            }
1118
            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
×
1119
            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
4✔
1120
            Code::TooBig => ctx.write_all(b"TOOBIG"),
×
1121
            Code::Other(unknown) => unknown.encode_ctx(ctx),
×
1122
        }
1123
    }
156✔
1124
}
1125

1126
impl<'a> EncodeIntoContext for CodeOther<'a> {
1127
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1128
        ctx.write_all(self.inner())
×
1129
    }
×
1130
}
1131

1132
impl<'a> EncodeIntoContext for Text<'a> {
1133
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
876✔
1134
        ctx.write_all(self.inner().as_bytes())
876✔
1135
    }
876✔
1136
}
1137

1138
impl<'a> EncodeIntoContext for Data<'a> {
1139
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
738✔
1140
        match self {
738✔
1141
            Data::Capability(caps) => {
71✔
1142
                ctx.write_all(b"* CAPABILITY ")?;
71✔
1143
                join_serializable(caps.as_ref(), b" ", ctx)?;
71✔
1144
            }
1145
            Data::List {
1146
                items,
236✔
1147
                delimiter,
236✔
1148
                mailbox,
236✔
1149
            } => {
236✔
1150
                ctx.write_all(b"* LIST (")?;
236✔
1151
                join_serializable(items, b" ", ctx)?;
236✔
1152
                ctx.write_all(b") ")?;
236✔
1153

1154
                if let Some(delimiter) = delimiter {
236✔
1155
                    ctx.write_all(b"\"")?;
236✔
1156
                    delimiter.encode_ctx(ctx)?;
236✔
1157
                    ctx.write_all(b"\"")?;
236✔
1158
                } else {
1159
                    ctx.write_all(b"NIL")?;
×
1160
                }
1161
                ctx.write_all(b" ")?;
236✔
1162
                mailbox.encode_ctx(ctx)?;
236✔
1163
            }
1164
            Data::Lsub {
1165
                items,
36✔
1166
                delimiter,
36✔
1167
                mailbox,
36✔
1168
            } => {
36✔
1169
                ctx.write_all(b"* LSUB (")?;
36✔
1170
                join_serializable(items, b" ", ctx)?;
36✔
1171
                ctx.write_all(b") ")?;
36✔
1172

1173
                if let Some(delimiter) = delimiter {
36✔
1174
                    ctx.write_all(b"\"")?;
36✔
1175
                    delimiter.encode_ctx(ctx)?;
36✔
1176
                    ctx.write_all(b"\"")?;
36✔
1177
                } else {
1178
                    ctx.write_all(b"NIL")?;
×
1179
                }
1180
                ctx.write_all(b" ")?;
36✔
1181
                mailbox.encode_ctx(ctx)?;
36✔
1182
            }
1183
            Data::Status { mailbox, items } => {
20✔
1184
                ctx.write_all(b"* STATUS ")?;
20✔
1185
                mailbox.encode_ctx(ctx)?;
20✔
1186
                ctx.write_all(b" (")?;
20✔
1187
                join_serializable(items, b" ", ctx)?;
20✔
1188
                ctx.write_all(b")")?;
20✔
1189
            }
1190
            Data::Search(seqs) => {
42✔
1191
                if seqs.is_empty() {
42✔
1192
                    ctx.write_all(b"* SEARCH")?;
9✔
1193
                } else {
1194
                    ctx.write_all(b"* SEARCH ")?;
33✔
1195
                    join_serializable(seqs, b" ", ctx)?;
33✔
1196
                }
1197
            }
1198
            Data::Flags(flags) => {
36✔
1199
                ctx.write_all(b"* FLAGS (")?;
36✔
1200
                join_serializable(flags, b" ", ctx)?;
36✔
1201
                ctx.write_all(b")")?;
36✔
1202
            }
1203
            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
47✔
1204
            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
47✔
1205
            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
56✔
1206
            Data::Fetch { seq, items } => {
107✔
1207
                write!(ctx, "* {seq} FETCH (")?;
107✔
1208
                join_serializable(items.as_ref(), b" ", ctx)?;
107✔
1209
                ctx.write_all(b")")?;
107✔
1210
            }
1211
            Data::Enabled { capabilities } => {
18✔
1212
                write!(ctx, "* ENABLED")?;
18✔
1213

1214
                for cap in capabilities {
36✔
1215
                    ctx.write_all(b" ")?;
18✔
1216
                    cap.encode_ctx(ctx)?;
18✔
1217
                }
1218
            }
1219
            Data::Quota { root, quotas } => {
12✔
1220
                ctx.write_all(b"* QUOTA ")?;
12✔
1221
                root.encode_ctx(ctx)?;
12✔
1222
                ctx.write_all(b" (")?;
12✔
1223
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
1224
                ctx.write_all(b")")?;
12✔
1225
            }
1226
            Data::QuotaRoot { mailbox, roots } => {
10✔
1227
                ctx.write_all(b"* QUOTAROOT ")?;
10✔
1228
                mailbox.encode_ctx(ctx)?;
10✔
1229
                for root in roots {
20✔
1230
                    ctx.write_all(b" ")?;
10✔
1231
                    root.encode_ctx(ctx)?;
10✔
1232
                }
1233
            }
1234
        }
1235

1236
        ctx.write_all(b"\r\n")
738✔
1237
    }
738✔
1238
}
1239

1240
impl<'a> EncodeIntoContext for FlagNameAttribute<'a> {
1241
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
101✔
1242
        write!(ctx, "{}", self)
101✔
1243
    }
101✔
1244
}
1245

1246
impl EncodeIntoContext for QuotedChar {
1247
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
272✔
1248
        match self.inner() {
272✔
1249
            '\\' => ctx.write_all(b"\\\\"),
×
1250
            '"' => ctx.write_all(b"\\\""),
×
1251
            other => ctx.write_all(&[other as u8]),
272✔
1252
        }
1253
    }
272✔
1254
}
1255

1256
impl EncodeIntoContext for StatusDataItem {
1257
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
56✔
1258
        match self {
56✔
1259
            Self::Messages(count) => {
22✔
1260
                ctx.write_all(b"MESSAGES ")?;
22✔
1261
                count.encode_ctx(ctx)
22✔
1262
            }
1263
            Self::Recent(count) => {
2✔
1264
                ctx.write_all(b"RECENT ")?;
2✔
1265
                count.encode_ctx(ctx)
2✔
1266
            }
1267
            Self::UidNext(next) => {
20✔
1268
                ctx.write_all(b"UIDNEXT ")?;
20✔
1269
                next.encode_ctx(ctx)
20✔
1270
            }
1271
            Self::UidValidity(identifier) => {
2✔
1272
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1273
                identifier.encode_ctx(ctx)
2✔
1274
            }
1275
            Self::Unseen(count) => {
2✔
1276
                ctx.write_all(b"UNSEEN ")?;
2✔
1277
                count.encode_ctx(ctx)
2✔
1278
            }
1279
            Self::Deleted(count) => {
4✔
1280
                ctx.write_all(b"DELETED ")?;
4✔
1281
                count.encode_ctx(ctx)
4✔
1282
            }
1283
            Self::DeletedStorage(count) => {
4✔
1284
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1285
                count.encode_ctx(ctx)
4✔
1286
            }
1287
        }
1288
    }
56✔
1289
}
1290

1291
impl<'a> EncodeIntoContext for MessageDataItem<'a> {
1292
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
203✔
1293
        match self {
203✔
1294
            Self::BodyExt {
17✔
1295
                section,
17✔
1296
                origin,
17✔
1297
                data,
17✔
1298
            } => {
17✔
1299
                ctx.write_all(b"BODY[")?;
17✔
1300
                if let Some(section) = section {
17✔
1301
                    section.encode_ctx(ctx)?;
9✔
1302
                }
8✔
1303
                ctx.write_all(b"]")?;
17✔
1304
                if let Some(origin) = origin {
17✔
1305
                    write!(ctx, "<{origin}>")?;
2✔
1306
                }
15✔
1307
                ctx.write_all(b" ")?;
17✔
1308
                data.encode_ctx(ctx)
17✔
1309
            }
1310
            // FIXME: do not return body-ext-1part and body-ext-mpart here
1311
            Self::Body(body) => {
11✔
1312
                ctx.write_all(b"BODY ")?;
11✔
1313
                body.encode_ctx(ctx)
11✔
1314
            }
1315
            Self::BodyStructure(body) => {
4✔
1316
                ctx.write_all(b"BODYSTRUCTURE ")?;
4✔
1317
                body.encode_ctx(ctx)
4✔
1318
            }
1319
            Self::Envelope(envelope) => {
11✔
1320
                ctx.write_all(b"ENVELOPE ")?;
11✔
1321
                envelope.encode_ctx(ctx)
11✔
1322
            }
1323
            Self::Flags(flags) => {
92✔
1324
                ctx.write_all(b"FLAGS (")?;
92✔
1325
                join_serializable(flags, b" ", ctx)?;
92✔
1326
                ctx.write_all(b")")
92✔
1327
            }
1328
            Self::InternalDate(datetime) => {
11✔
1329
                ctx.write_all(b"INTERNALDATE ")?;
11✔
1330
                datetime.encode_ctx(ctx)
11✔
1331
            }
1332
            Self::Rfc822(nstring) => {
4✔
1333
                ctx.write_all(b"RFC822 ")?;
4✔
1334
                nstring.encode_ctx(ctx)
4✔
1335
            }
1336
            Self::Rfc822Header(nstring) => {
2✔
1337
                ctx.write_all(b"RFC822.HEADER ")?;
2✔
1338
                nstring.encode_ctx(ctx)
2✔
1339
            }
1340
            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
20✔
1341
            Self::Rfc822Text(nstring) => {
2✔
1342
                ctx.write_all(b"RFC822.TEXT ")?;
2✔
1343
                nstring.encode_ctx(ctx)
2✔
1344
            }
1345
            Self::Uid(uid) => write!(ctx, "UID {uid}"),
29✔
1346
        }
1347
    }
203✔
1348
}
1349

1350
impl<'a> EncodeIntoContext for NString<'a> {
1351
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
339✔
1352
        match &self.0 {
339✔
1353
            Some(imap_str) => imap_str.encode_ctx(ctx),
200✔
1354
            None => ctx.write_all(b"NIL"),
139✔
1355
        }
1356
    }
339✔
1357
}
1358

1359
impl<'a> EncodeIntoContext for BodyStructure<'a> {
1360
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1361
        ctx.write_all(b"(")?;
33✔
1362
        match self {
33✔
1363
            BodyStructure::Single {
1364
                body,
21✔
1365
                extension_data: extension,
21✔
1366
            } => {
21✔
1367
                body.encode_ctx(ctx)?;
21✔
1368
                if let Some(extension) = extension {
21✔
1369
                    ctx.write_all(b" ")?;
4✔
1370
                    extension.encode_ctx(ctx)?;
4✔
1371
                }
17✔
1372
            }
1373
            BodyStructure::Multi {
1374
                bodies,
12✔
1375
                subtype,
12✔
1376
                extension_data,
12✔
1377
            } => {
1378
                for body in bodies.as_ref() {
12✔
1379
                    body.encode_ctx(ctx)?;
12✔
1380
                }
1381
                ctx.write_all(b" ")?;
12✔
1382
                subtype.encode_ctx(ctx)?;
12✔
1383

1384
                if let Some(extension) = extension_data {
12✔
1385
                    ctx.write_all(b" ")?;
×
1386
                    extension.encode_ctx(ctx)?;
×
1387
                }
12✔
1388
            }
1389
        }
1390
        ctx.write_all(b")")
33✔
1391
    }
33✔
1392
}
1393

1394
impl<'a> EncodeIntoContext for Body<'a> {
1395
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
21✔
1396
        match self.specific {
21✔
1397
            SpecificFields::Basic {
1398
                r#type: ref type_,
4✔
1399
                ref subtype,
4✔
1400
            } => {
4✔
1401
                type_.encode_ctx(ctx)?;
4✔
1402
                ctx.write_all(b" ")?;
4✔
1403
                subtype.encode_ctx(ctx)?;
4✔
1404
                ctx.write_all(b" ")?;
4✔
1405
                self.basic.encode_ctx(ctx)
4✔
1406
            }
1407
            SpecificFields::Message {
1408
                ref envelope,
×
1409
                ref body_structure,
×
1410
                number_of_lines,
×
1411
            } => {
×
1412
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
1413
                self.basic.encode_ctx(ctx)?;
×
1414
                ctx.write_all(b" ")?;
×
1415
                envelope.encode_ctx(ctx)?;
×
1416
                ctx.write_all(b" ")?;
×
1417
                body_structure.encode_ctx(ctx)?;
×
1418
                ctx.write_all(b" ")?;
×
1419
                write!(ctx, "{number_of_lines}")
×
1420
            }
1421
            SpecificFields::Text {
1422
                ref subtype,
17✔
1423
                number_of_lines,
17✔
1424
            } => {
17✔
1425
                ctx.write_all(b"\"TEXT\" ")?;
17✔
1426
                subtype.encode_ctx(ctx)?;
17✔
1427
                ctx.write_all(b" ")?;
17✔
1428
                self.basic.encode_ctx(ctx)?;
17✔
1429
                ctx.write_all(b" ")?;
17✔
1430
                write!(ctx, "{number_of_lines}")
17✔
1431
            }
1432
        }
1433
    }
21✔
1434
}
1435

1436
impl<'a> EncodeIntoContext for BasicFields<'a> {
1437
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1438
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
21✔
1439
        ctx.write_all(b" ")?;
21✔
1440
        self.id.encode_ctx(ctx)?;
21✔
1441
        ctx.write_all(b" ")?;
21✔
1442
        self.description.encode_ctx(ctx)?;
21✔
1443
        ctx.write_all(b" ")?;
21✔
1444
        self.content_transfer_encoding.encode_ctx(ctx)?;
21✔
1445
        ctx.write_all(b" ")?;
21✔
1446
        write!(ctx, "{}", self.size)
21✔
1447
    }
21✔
1448
}
1449

1450
impl<'a> EncodeIntoContext for Envelope<'a> {
1451
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1452
        ctx.write_all(b"(")?;
11✔
1453
        self.date.encode_ctx(ctx)?;
11✔
1454
        ctx.write_all(b" ")?;
11✔
1455
        self.subject.encode_ctx(ctx)?;
11✔
1456
        ctx.write_all(b" ")?;
11✔
1457
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
11✔
1458
        ctx.write_all(b" ")?;
11✔
1459
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
11✔
1460
        ctx.write_all(b" ")?;
11✔
1461
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
11✔
1462
        ctx.write_all(b" ")?;
11✔
1463
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
11✔
1464
        ctx.write_all(b" ")?;
11✔
1465
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
11✔
1466
        ctx.write_all(b" ")?;
11✔
1467
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
11✔
1468
        ctx.write_all(b" ")?;
11✔
1469
        self.in_reply_to.encode_ctx(ctx)?;
11✔
1470
        ctx.write_all(b" ")?;
11✔
1471
        self.message_id.encode_ctx(ctx)?;
11✔
1472
        ctx.write_all(b")")
11✔
1473
    }
11✔
1474
}
1475

1476
impl<'a> EncodeIntoContext for Address<'a> {
1477
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1478
        ctx.write_all(b"(")?;
54✔
1479
        self.name.encode_ctx(ctx)?;
54✔
1480
        ctx.write_all(b" ")?;
54✔
1481
        self.adl.encode_ctx(ctx)?;
54✔
1482
        ctx.write_all(b" ")?;
54✔
1483
        self.mailbox.encode_ctx(ctx)?;
54✔
1484
        ctx.write_all(b" ")?;
54✔
1485
        self.host.encode_ctx(ctx)?;
54✔
1486
        ctx.write_all(b")")?;
54✔
1487

1488
        Ok(())
54✔
1489
    }
54✔
1490
}
1491

1492
impl<'a> EncodeIntoContext for SinglePartExtensionData<'a> {
1493
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1494
        self.md5.encode_ctx(ctx)?;
6✔
1495

1496
        if let Some(disposition) = &self.tail {
6✔
1497
            ctx.write_all(b" ")?;
6✔
1498
            disposition.encode_ctx(ctx)?;
6✔
1499
        }
×
1500

1501
        Ok(())
6✔
1502
    }
6✔
1503
}
1504

1505
impl<'a> EncodeIntoContext for MultiPartExtensionData<'a> {
1506
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1507
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1508

1509
        if let Some(disposition) = &self.tail {
×
1510
            ctx.write_all(b" ")?;
×
1511
            disposition.encode_ctx(ctx)?;
×
1512
        }
×
1513

1514
        Ok(())
×
1515
    }
×
1516
}
1517

1518
impl<'a> EncodeIntoContext for Disposition<'a> {
1519
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1520
        match &self.disposition {
6✔
1521
            Some((s, param)) => {
×
1522
                ctx.write_all(b"(")?;
×
1523
                s.encode_ctx(ctx)?;
×
1524
                ctx.write_all(b" ")?;
×
1525
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1526
                ctx.write_all(b")")?;
×
1527
            }
1528
            None => ctx.write_all(b"NIL")?,
6✔
1529
        }
1530

1531
        if let Some(language) = &self.tail {
6✔
1532
            ctx.write_all(b" ")?;
6✔
1533
            language.encode_ctx(ctx)?;
6✔
1534
        }
×
1535

1536
        Ok(())
6✔
1537
    }
6✔
1538
}
1539

1540
impl<'a> EncodeIntoContext for Language<'a> {
1541
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1542
        List1OrNil(&self.language, b" ").encode_ctx(ctx)?;
6✔
1543

1544
        if let Some(location) = &self.tail {
6✔
1545
            ctx.write_all(b" ")?;
6✔
1546
            location.encode_ctx(ctx)?;
6✔
1547
        }
×
1548

1549
        Ok(())
6✔
1550
    }
6✔
1551
}
1552

1553
impl<'a> EncodeIntoContext for Location<'a> {
1554
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1555
        self.location.encode_ctx(ctx)?;
6✔
1556

1557
        for body_extension in &self.extensions {
10✔
1558
            ctx.write_all(b" ")?;
4✔
1559
            body_extension.encode_ctx(ctx)?;
4✔
1560
        }
1561

1562
        Ok(())
6✔
1563
    }
6✔
1564
}
1565

1566
impl<'a> EncodeIntoContext for BodyExtension<'a> {
1567
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1568
        match self {
6✔
1569
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
1570
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
1571
            BodyExtension::List(list) => {
2✔
1572
                ctx.write_all(b"(")?;
2✔
1573
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
1574
                ctx.write_all(b")")
2✔
1575
            }
1576
        }
1577
    }
6✔
1578
}
1579

1580
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
1581
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
15✔
1582
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
15✔
1583
    }
15✔
1584
}
1585

1586
impl<'a> EncodeIntoContext for CommandContinuationRequest<'a> {
1587
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
9✔
1588
        match self {
9✔
1589
            Self::Basic(continue_basic) => match continue_basic.code() {
9✔
1590
                Some(code) => {
×
1591
                    ctx.write_all(b"+ [")?;
×
1592
                    code.encode_ctx(ctx)?;
×
1593
                    ctx.write_all(b"] ")?;
×
1594
                    continue_basic.text().encode_ctx(ctx)?;
×
1595
                    ctx.write_all(b"\r\n")
×
1596
                }
1597
                None => {
1598
                    ctx.write_all(b"+ ")?;
9✔
1599
                    continue_basic.text().encode_ctx(ctx)?;
9✔
1600
                    ctx.write_all(b"\r\n")
9✔
1601
                }
1602
            },
1603
            Self::Base64(data) => {
×
1604
                ctx.write_all(b"+ ")?;
×
1605
                ctx.write_all(base64.encode(data).as_bytes())?;
×
1606
                ctx.write_all(b"\r\n")
×
1607
            }
1608
        }
1609
    }
9✔
1610
}
1611

1612
mod utils {
1613
    use std::io::Write;
1614

1615
    use super::{EncodeContext, EncodeIntoContext};
1616

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

1619
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
1620

1621
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
1622
        elements: &[I],
1623
        sep: &[u8],
1624
        ctx: &mut EncodeContext,
1625
    ) -> std::io::Result<()> {
1626
        if let Some((last, head)) = elements.split_last() {
892✔
1627
            for item in head {
1,340✔
1628
                item.encode_ctx(ctx)?;
632✔
1629
                ctx.write_all(sep)?;
632✔
1630
            }
1631

1632
            last.encode_ctx(ctx)
708✔
1633
        } else {
1634
            Ok(())
184✔
1635
        }
1636
    }
892✔
1637

1638
    impl<'a, T> EncodeIntoContext for List1OrNil<'a, T>
1639
    where
1640
        T: EncodeIntoContext,
1641
    {
1642
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1643
            if let Some((last, head)) = self.0.split_last() {
72✔
1644
                ctx.write_all(b"(")?;
45✔
1645

1646
                for item in head {
54✔
1647
                    item.encode_ctx(ctx)?;
9✔
1648
                    ctx.write_all(self.1)?;
9✔
1649
                }
1650

1651
                last.encode_ctx(ctx)?;
45✔
1652

1653
                ctx.write_all(b")")
45✔
1654
            } else {
1655
                ctx.write_all(b"NIL")
27✔
1656
            }
1657
        }
72✔
1658
    }
1659

1660
    impl<'a, T> EncodeIntoContext for List1AttributeValueOrNil<'a, T>
1661
    where
1662
        T: EncodeIntoContext,
1663
    {
1664
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1665
            if let Some((last, head)) = self.0.split_last() {
21✔
1666
                ctx.write_all(b"(")?;
9✔
1667

1668
                for (attribute, value) in head {
9✔
1669
                    attribute.encode_ctx(ctx)?;
×
1670
                    ctx.write_all(b" ")?;
×
1671
                    value.encode_ctx(ctx)?;
×
1672
                    ctx.write_all(b" ")?;
×
1673
                }
1674

1675
                let (attribute, value) = last;
9✔
1676
                attribute.encode_ctx(ctx)?;
9✔
1677
                ctx.write_all(b" ")?;
9✔
1678
                value.encode_ctx(ctx)?;
9✔
1679

1680
                ctx.write_all(b")")
9✔
1681
            } else {
1682
                ctx.write_all(b"NIL")
12✔
1683
            }
1684
        }
21✔
1685
    }
1686
}
1687

1688
#[cfg(test)]
1689
mod tests {
1690
    use std::num::NonZeroU32;
1691

1692
    use imap_types::{
1693
        auth::AuthMechanism,
1694
        command::{Command, CommandBody},
1695
        core::{AString, Literal, NString, NonEmptyVec},
1696
        fetch::MessageDataItem,
1697
        response::{Data, Response},
1698
        utils::escape_byte_string,
1699
    };
1700

1701
    use super::*;
1702

1703
    #[test]
2✔
1704
    fn test_api_encoder_usage() {
2✔
1705
        let cmd = Command::new(
2✔
1706
            "A",
2✔
1707
            CommandBody::login(
2✔
1708
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
1709
                "password",
2✔
1710
            )
2✔
1711
            .unwrap(),
2✔
1712
        )
2✔
1713
        .unwrap();
2✔
1714

2✔
1715
        // Dump.
2✔
1716
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
1717

2✔
1718
        // Encoded.
2✔
1719
        let encoded = CommandCodec::default().encode(&cmd);
2✔
1720

2✔
1721
        let mut out = Vec::new();
2✔
1722

1723
        for x in encoded {
8✔
1724
            match x {
6✔
1725
                Fragment::Line { data } => {
4✔
1726
                    println!("C: {}", escape_byte_string(&data));
4✔
1727
                    out.extend_from_slice(&data);
4✔
1728
                }
4✔
1729
                Fragment::Literal { data, mode } => {
2✔
1730
                    match mode {
2✔
1731
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
1732
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
1733
                    }
1734

1735
                    println!("C: {}", escape_byte_string(&data));
2✔
1736
                    out.extend_from_slice(&data);
2✔
1737
                }
NEW
1738
                Fragment::AuthData { data } => {
×
NEW
1739
                    println!("C: <Waiting for continuation request>");
×
NEW
1740

×
1741
                    println!("C: {}", escape_byte_string(&data));
×
1742
                    out.extend_from_slice(&data);
×
1743
                }
×
1744
            }
1745
        }
1746

1747
        assert_eq!(got_encoded, out);
2✔
1748
    }
2✔
1749

1750
    #[test]
2✔
1751
    fn test_encode_command() {
2✔
1752
        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2✔
1753
            (
2✔
1754
                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2✔
1755
                [Fragment::Line {
2✔
1756
                    data: b"A LOGIN alice pass\r\n".to_vec(),
2✔
1757
                }]
2✔
1758
                .as_ref(),
2✔
1759
            ),
2✔
1760
            (
2✔
1761
                Command::new(
2✔
1762
                    "A",
2✔
1763
                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2✔
1764
                )
2✔
1765
                .unwrap(),
2✔
1766
                [
2✔
1767
                    Fragment::Line {
2✔
1768
                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
1769
                    },
2✔
1770
                    Fragment::Literal {
2✔
1771
                        data: b"\xCA\xFE".to_vec(),
2✔
1772
                        mode: LiteralMode::Sync,
2✔
1773
                    },
2✔
1774
                    Fragment::Line {
2✔
1775
                        data: b"\r\n".to_vec(),
2✔
1776
                    },
2✔
1777
                ]
2✔
1778
                .as_ref(),
2✔
1779
            ),
2✔
1780
            (
2✔
1781
                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2✔
1782
                [Fragment::Line {
2✔
1783
                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2✔
1784
                }]
2✔
1785
                .as_ref(),
2✔
1786
            ),
2✔
1787
            (
2✔
1788
                Command::new(
2✔
1789
                    "A",
2✔
1790
                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2✔
1791
                )
2✔
1792
                .unwrap(),
2✔
1793
                [Fragment::Line {
2✔
1794
                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2✔
1795
                }]
2✔
1796
                .as_ref(),
2✔
1797
            ),
2✔
1798
            (
2✔
1799
                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2✔
1800
                [Fragment::Line {
2✔
1801
                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2✔
1802
                }]
2✔
1803
                .as_ref(),
2✔
1804
            ),
2✔
1805
            (
2✔
1806
                Command::new(
2✔
1807
                    "A",
2✔
1808
                    CommandBody::authenticate_with_ir(
2✔
1809
                        AuthMechanism::Plain,
2✔
1810
                        b"\x00alice\x00pass".as_ref(),
2✔
1811
                    ),
2✔
1812
                )
2✔
1813
                .unwrap(),
2✔
1814
                [Fragment::Line {
2✔
1815
                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2✔
1816
                }]
2✔
1817
                .as_ref(),
2✔
1818
            ),
2✔
1819
        ]);
2✔
1820
    }
2✔
1821

1822
    #[test]
2✔
1823
    fn test_encode_response() {
2✔
1824
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
1825
            (
2✔
1826
                Response::Data(Data::Fetch {
2✔
1827
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
1828
                    items: NonEmptyVec::from(MessageDataItem::BodyExt {
2✔
1829
                        section: None,
2✔
1830
                        origin: None,
2✔
1831
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
1832
                    }),
2✔
1833
                }),
2✔
1834
                [
2✔
1835
                    Fragment::Line {
2✔
1836
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
1837
                    },
2✔
1838
                    Fragment::Literal {
2✔
1839
                        data: b"ABCDE".to_vec(),
2✔
1840
                        mode: LiteralMode::Sync,
2✔
1841
                    },
2✔
1842
                    Fragment::Line {
2✔
1843
                        data: b")\r\n".to_vec(),
2✔
1844
                    },
2✔
1845
                ]
2✔
1846
                .as_ref(),
2✔
1847
            ),
2✔
1848
            (
2✔
1849
                Response::Data(Data::Fetch {
2✔
1850
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
1851
                    items: NonEmptyVec::from(MessageDataItem::BodyExt {
2✔
1852
                        section: None,
2✔
1853
                        origin: None,
2✔
1854
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
1855
                    }),
2✔
1856
                }),
2✔
1857
                [
2✔
1858
                    Fragment::Line {
2✔
1859
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
1860
                    },
2✔
1861
                    Fragment::Literal {
2✔
1862
                        data: b"ABCDE".to_vec(),
2✔
1863
                        mode: LiteralMode::NonSync,
2✔
1864
                    },
2✔
1865
                    Fragment::Line {
2✔
1866
                        data: b")\r\n".to_vec(),
2✔
1867
                    },
2✔
1868
                ]
2✔
1869
                .as_ref(),
2✔
1870
            ),
2✔
1871
        ])
2✔
1872
    }
2✔
1873

1874
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
1875
    where
4✔
1876
        E: Encoder<Message<'a> = M> + Default,
4✔
1877
        F: AsRef<[Fragment]>,
4✔
1878
    {
4✔
1879
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
1880
            println!("# Testing {i}");
16✔
1881

16✔
1882
            let encoder = E::default().encode(obj);
16✔
1883
            let actions = actions.as_ref();
16✔
1884

16✔
1885
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
1886
        }
1887
    }
4✔
1888
}
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