Skip to content

Commit 3f23314

Browse files
committed
Rework Name and Fetch to use ouroboros.
Use a helper function in `parse_many_into` to support parsing into any container that implements Extend. Refactor Capabilities to use it. Delete ZeroCopy and associated bits. Move Flag into it's own module in types.
1 parent c2d3aed commit 3f23314

File tree

8 files changed

+296
-379
lines changed

8 files changed

+296
-379
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "imap"
3-
version = "3.0.0-alpha.4"
3+
version = "3.0.0-alpha.5"
44
authors = ["Jon Gjengset <jon@thesquareplanet.com>",
55
"Matt McCoy <mattnenterprise@yahoo.com>"]
66
documentation = "https://docs.rs/imap/"

src/client.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -565,39 +565,39 @@ impl<T: Read + Write> Session<T> {
565565
/// - `RFC822.HEADER`: Functionally equivalent to `BODY.PEEK[HEADER]`.
566566
/// - `RFC822.SIZE`: The [RFC-2822](https://tools.ietf.org/html/rfc2822) size of the message.
567567
/// - `UID`: The unique identifier for the message.
568-
pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
568+
pub fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> Result<Fetches>
569569
where
570570
S1: AsRef<str>,
571571
S2: AsRef<str>,
572572
{
573573
if sequence_set.as_ref().is_empty() {
574-
parse_fetches(vec![], &mut self.unsolicited_responses_tx)
574+
Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
575575
} else {
576576
self.run_command_and_read_response(&format!(
577577
"FETCH {} {}",
578578
validate_sequence_set(sequence_set.as_ref())?,
579579
validate_str_noquote(query.as_ref())?
580580
))
581-
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
581+
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
582582
}
583583
}
584584

585585
/// Equivalent to [`Session::fetch`], except that all identifiers in `uid_set` are
586586
/// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
587-
pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
587+
pub fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> Result<Fetches>
588588
where
589589
S1: AsRef<str>,
590590
S2: AsRef<str>,
591591
{
592592
if uid_set.as_ref().is_empty() {
593-
parse_fetches(vec![], &mut self.unsolicited_responses_tx)
593+
Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
594594
} else {
595595
self.run_command_and_read_response(&format!(
596596
"UID FETCH {} {}",
597597
validate_sequence_set(uid_set.as_ref())?,
598598
validate_str_noquote(query.as_ref())?
599599
))
600-
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
600+
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
601601
}
602602
}
603603

@@ -834,7 +834,7 @@ impl<T: Read + Write> Session<T> {
834834
/// Ok(())
835835
/// }
836836
/// ```
837-
pub fn store<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
837+
pub fn store<S1, S2>(&mut self, sequence_set: S1, query: S2) -> Result<Fetches>
838838
where
839839
S1: AsRef<str>,
840840
S2: AsRef<str>,
@@ -844,12 +844,12 @@ impl<T: Read + Write> Session<T> {
844844
sequence_set.as_ref(),
845845
query.as_ref()
846846
))
847-
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
847+
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
848848
}
849849

850850
/// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are
851851
/// [`Uid`]s. See also the [`UID` command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
852-
pub fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> ZeroCopyResult<Vec<Fetch>>
852+
pub fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> Result<Fetches>
853853
where
854854
S1: AsRef<str>,
855855
S2: AsRef<str>,
@@ -859,7 +859,7 @@ impl<T: Read + Write> Session<T> {
859859
uid_set.as_ref(),
860860
query.as_ref()
861861
))
862-
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
862+
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
863863
}
864864

865865
/// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the
@@ -988,13 +988,13 @@ impl<T: Read + Write> Session<T> {
988988
&mut self,
989989
reference_name: Option<&str>,
990990
mailbox_pattern: Option<&str>,
991-
) -> ZeroCopyResult<Vec<Name>> {
991+
) -> Result<Names> {
992992
self.run_command_and_read_response(&format!(
993993
"LIST {} {}",
994994
quote!(reference_name.unwrap_or("")),
995995
mailbox_pattern.unwrap_or("\"\"")
996996
))
997-
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx))
997+
.and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
998998
}
999999

10001000
/// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of
@@ -1016,13 +1016,13 @@ impl<T: Read + Write> Session<T> {
10161016
&mut self,
10171017
reference_name: Option<&str>,
10181018
mailbox_pattern: Option<&str>,
1019-
) -> ZeroCopyResult<Vec<Name>> {
1019+
) -> Result<Names> {
10201020
self.run_command_and_read_response(&format!(
10211021
"LSUB {} {}",
10221022
quote!(reference_name.unwrap_or("")),
10231023
mailbox_pattern.unwrap_or("")
10241024
))
1025-
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx))
1025+
.and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
10261026
}
10271027

10281028
/// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the

src/parse.rs

Lines changed: 32 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use lazy_static::lazy_static;
33
use regex::Regex;
44
use std::collections::HashSet;
55
use std::convert::TryFrom;
6+
use std::iter::Extend;
67
use std::sync::mpsc;
78

89
use super::error::{Error, ParseError, Result};
@@ -23,105 +24,51 @@ pub fn parse_authenticate_response(line: &str) -> Result<&str> {
2324
)))
2425
}
2526

26-
enum MapOrNot<T> {
27+
pub(crate) enum MapOrNot<'a, T> {
2728
Map(T),
28-
Not(Response<'static>),
29+
MapVec(Vec<T>),
30+
Not(Response<'a>),
2931
#[allow(dead_code)]
3032
Ignore,
3133
}
3234

33-
unsafe fn parse_many<T, F>(
34-
lines: Vec<u8>,
35-
mut map: F,
35+
/// Parse many `T` Responses with `F` and extend `into` with them.
36+
/// Responses other than `T` go into the `unsolicited` channel.
37+
pub(crate) fn parse_many_into<'input, T, F>(
38+
input: &'input [u8],
39+
into: &mut impl Extend<T>,
3640
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
37-
) -> ZeroCopyResult<Vec<T>>
41+
mut map: F,
42+
) -> Result<()>
3843
where
39-
F: FnMut(Response<'static>) -> Result<MapOrNot<T>>,
44+
F: FnMut(Response<'input>) -> Result<MapOrNot<'input, T>>,
4045
{
41-
let f = |mut lines: &'static [u8]| {
42-
let mut things = Vec::new();
43-
loop {
44-
if lines.is_empty() {
45-
break Ok(things);
46-
}
47-
48-
match imap_proto::parser::parse_response(lines) {
49-
Ok((rest, resp)) => {
50-
lines = rest;
51-
52-
match map(resp)? {
53-
MapOrNot::Map(t) => things.push(t),
54-
MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) {
55-
Some(Response::Fetch(..)) => continue,
56-
Some(resp) => break Err(resp.into()),
57-
None => {}
58-
},
59-
MapOrNot::Ignore => continue,
60-
}
61-
}
62-
_ => {
63-
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
64-
}
65-
}
46+
let mut lines = input;
47+
loop {
48+
if lines.is_empty() {
49+
break Ok(());
6650
}
67-
};
6851

69-
ZeroCopy::make(lines, f)
70-
}
71-
72-
pub fn parse_names(
73-
lines: Vec<u8>,
74-
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
75-
) -> ZeroCopyResult<Vec<Name>> {
76-
let f = |resp| match resp {
77-
// https://github.com/djc/imap-proto/issues/4
78-
Response::MailboxData(MailboxDatum::List {
79-
flags,
80-
delimiter,
81-
name,
82-
}) => Ok(MapOrNot::Map(Name {
83-
attributes: flags.into_iter().map(NameAttribute::from).collect(),
84-
delimiter,
85-
name,
86-
})),
87-
resp => Ok(MapOrNot::Not(resp)),
88-
};
89-
90-
unsafe { parse_many(lines, f, unsolicited) }
91-
}
52+
match imap_proto::parser::parse_response(lines) {
53+
Ok((rest, resp)) => {
54+
lines = rest;
9255

93-
pub fn parse_fetches(
94-
lines: Vec<u8>,
95-
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
96-
) -> ZeroCopyResult<Vec<Fetch>> {
97-
let f = |resp| match resp {
98-
Response::Fetch(num, attrs) => {
99-
let mut fetch = Fetch {
100-
message: num,
101-
flags: vec![],
102-
uid: None,
103-
size: None,
104-
fetch: attrs,
105-
};
106-
107-
// set some common fields eaglery
108-
for attr in &fetch.fetch {
109-
match attr {
110-
AttributeValue::Flags(flags) => {
111-
fetch.flags.extend(Flag::from_strs(flags));
112-
}
113-
AttributeValue::Uid(uid) => fetch.uid = Some(*uid),
114-
AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz),
115-
_ => {}
56+
match map(resp)? {
57+
MapOrNot::Map(t) => into.extend(Some(t)),
58+
MapOrNot::MapVec(t) => into.extend(t),
59+
MapOrNot::Not(resp) => match try_handle_unilateral(resp, unsolicited) {
60+
Some(Response::Fetch(..)) => continue,
61+
Some(resp) => break Err(resp.into()),
62+
None => {}
63+
},
64+
MapOrNot::Ignore => continue,
11665
}
11766
}
118-
119-
Ok(MapOrNot::Map(fetch))
67+
_ => {
68+
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
69+
}
12070
}
121-
resp => Ok(MapOrNot::Not(resp)),
122-
};
123-
124-
unsafe { parse_many(lines, f, unsolicited) }
71+
}
12572
}
12673

12774
pub fn parse_expunge(

src/types/capabilities.rs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::error::{Error, ParseError};
2-
use crate::parse::try_handle_unilateral;
1+
use crate::error::Error;
2+
use crate::parse::{parse_many_into, MapOrNot};
33
use crate::types::UnsolicitedResponse;
44
use imap_proto::{Capability, Response};
55
use ouroboros::self_referencing;
@@ -52,29 +52,12 @@ impl Capabilities {
5252
CapabilitiesTryBuilder {
5353
data: owned,
5454
capabilities_builder: |input| {
55-
let mut lines = input;
5655
let mut caps = HashSet::new();
57-
loop {
58-
match imap_proto::parser::parse_response(lines) {
59-
Ok((rest, Response::Capabilities(c))) => {
60-
lines = rest;
61-
caps.extend(c);
62-
}
63-
Ok((rest, data)) => {
64-
lines = rest;
65-
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
66-
break Err(resp.into());
67-
}
68-
}
69-
_ => {
70-
break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
71-
}
72-
}
73-
74-
if lines.is_empty() {
75-
break Ok(caps);
76-
}
77-
}
56+
parse_many_into(input, &mut caps, unsolicited, |response| match response {
57+
Response::Capabilities(c) => Ok(MapOrNot::MapVec(c)),
58+
resp => Ok(MapOrNot::Not(resp)),
59+
})?;
60+
Ok(caps)
7861
},
7962
}
8063
.try_build()

0 commit comments

Comments
 (0)