Skip to content

Commit d69ebe4

Browse files
committed
feat: add quirk to dedup and sort sequence sets during encoding
1 parent 790307d commit d69ebe4

3 files changed

Lines changed: 133 additions & 1 deletion

File tree

imap-codec/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ quirk = [
3535
"quirk_spaces_between_addresses",
3636
"quirk_empty_continue_req",
3737
"quirk_body_fld_enc_nil_to_empty",
38+
"quirk_dedup_sort_seq_encoding",
3839
]
3940
# Make `\r` in `\r\n` optional.
4041
quirk_crlf_relaxed = []
@@ -60,6 +61,8 @@ quirk_trailing_space_search = []
6061
quirk_empty_continue_req = []
6162
# Encode NIL `body-fld-enc` as empty string.
6263
quirk_body_fld_enc_nil_to_empty = []
64+
# Dedup and sort sequence sets during encoding
65+
quirk_dedup_sort_seq_encoding = []
6366

6467
# <Forward to imap-types>
6568
arbitrary = ["imap-types/arbitrary"]

imap-codec/src/codec/encode.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,16 +1024,61 @@ impl EncodeIntoContext for SearchKey<'_> {
10241024
}
10251025

10261026
impl EncodeIntoContext for SequenceSet {
1027+
#[cfg(not(feature = "quirk_dedup_sort_seq_encoding"))]
10271028
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10281029
join_serializable(self.0.as_ref(), b",", ctx)
10291030
}
1031+
1032+
#[cfg(feature = "quirk_dedup_sort_seq_encoding")]
1033+
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1034+
let mut seq_set = self.0.as_ref().to_vec();
1035+
1036+
if seq_set.len() == 1 {
1037+
return seq_set.remove(0).encode_ctx(ctx);
1038+
}
1039+
1040+
seq_set.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1041+
1042+
let mut a = 0;
1043+
let mut b = 1;
1044+
1045+
while b < seq_set.len() {
1046+
if seq_set[a].partial_cmp(&seq_set[b]).is_some() {
1047+
a += 1;
1048+
b += 1;
1049+
continue;
1050+
}
1051+
1052+
match (&seq_set[a], &seq_set[b]) {
1053+
(Sequence::Single(_), _) => {
1054+
seq_set.remove(a);
1055+
}
1056+
(Sequence::Range(_, _), Sequence::Single(_)) => {
1057+
seq_set.remove(b);
1058+
}
1059+
(Sequence::Range(a1, a2), Sequence::Range(b1, b2)) => {
1060+
let min = a1.clone().min(a2.clone()).min(b1.clone()).min(b2.clone());
1061+
let max = a1.clone().max(a2.clone()).max(b1.clone()).max(b2.clone());
1062+
let _ = std::mem::replace(&mut seq_set[a], Sequence::Range(min, max));
1063+
seq_set.remove(b);
1064+
}
1065+
}
1066+
}
1067+
1068+
join_serializable(&seq_set, b",", ctx)
1069+
}
10301070
}
10311071

10321072
impl EncodeIntoContext for Sequence {
10331073
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
10341074
match self {
10351075
Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
10361076
Sequence::Range(from, to) => {
1077+
#[cfg(feature = "quirk_dedup_sort_seq_encoding")]
1078+
let from = from.min(to);
1079+
#[cfg(feature = "quirk_dedup_sort_seq_encoding")]
1080+
let to = from.max(to);
1081+
10371082
from.encode_ctx(ctx)?;
10381083
ctx.write_all(b":")?;
10391084
to.encode_ctx(ctx)
@@ -2128,6 +2173,32 @@ mod tests {
21282173

21292174
use super::*;
21302175

2176+
#[cfg(feature = "quirk_dedup_sort_seq_encoding")]
2177+
#[test]
2178+
fn test_sequence_reordering() {
2179+
let tests = [
2180+
("1,2,3,4", "1,2,3,4"),
2181+
("3,1,2,4", "1,2,3,4"),
2182+
("3:1,5", "1:3,5"),
2183+
("5,3:1", "1:3,5"),
2184+
("3,3:1", "1:3"),
2185+
("3:1,1", "1:3"),
2186+
("3:1,2:5", "1:5"),
2187+
("3:1,4:9", "1:3,4:9"),
2188+
("9:4,3:1", "1:3,4:9"),
2189+
("8:10,3:1,2:5,9", "1:5,8:10"),
2190+
];
2191+
2192+
for (expected, got) in tests {
2193+
let mut ctx = EncodeContext::default();
2194+
SequenceSet::try_from(expected)
2195+
.unwrap()
2196+
.encode_ctx(&mut ctx)
2197+
.unwrap();
2198+
assert_eq!(got, escape_byte_string(&ctx.accumulator));
2199+
}
2200+
}
2201+
21312202
#[test]
21322203
fn test_api_encoder_usage() {
21332204
let cmd = Command::new(

imap-types/src/sequence.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
cmp::max,
2+
cmp::{Ordering, max},
33
collections::VecDeque,
44
fmt::Debug,
55
iter::Rev,
@@ -140,6 +140,47 @@ pub enum Sequence {
140140
Range(SeqOrUid, SeqOrUid),
141141
}
142142

143+
impl PartialOrd for Sequence {
144+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
145+
match (self, other) {
146+
(Sequence::Single(a), Sequence::Single(b)) => a.partial_cmp(b),
147+
(Sequence::Single(a), Sequence::Range(b1, b2)) => {
148+
if a < b1.min(b2) {
149+
return Some(Ordering::Less);
150+
}
151+
152+
if a > b1.max(b2) {
153+
return Some(Ordering::Greater);
154+
}
155+
156+
None
157+
}
158+
(Sequence::Range(a1, a2), Sequence::Single(b)) => {
159+
if b < a1.min(a2) {
160+
return Some(Ordering::Greater);
161+
}
162+
163+
if b > a1.max(a2) {
164+
return Some(Ordering::Less);
165+
}
166+
167+
None
168+
}
169+
(Sequence::Range(a1, a2), Sequence::Range(b1, b2)) => {
170+
if a1.max(a2) < b1 && a1.max(a2) < b2 {
171+
return Some(Ordering::Less);
172+
}
173+
174+
if a1.min(a2) > b1 && a1.min(a2) > b2 {
175+
return Some(Ordering::Greater);
176+
}
177+
178+
None
179+
}
180+
}
181+
}
182+
}
183+
143184
impl From<SeqOrUid> for Sequence {
144185
fn from(value: SeqOrUid) -> Self {
145186
Self::Single(value)
@@ -192,6 +233,23 @@ pub enum SeqOrUid {
192233
Asterisk,
193234
}
194235

236+
impl Ord for SeqOrUid {
237+
fn cmp(&self, other: &Self) -> Ordering {
238+
match (self, other) {
239+
(SeqOrUid::Asterisk, SeqOrUid::Asterisk) => Ordering::Equal,
240+
(_, SeqOrUid::Asterisk) => Ordering::Greater,
241+
(SeqOrUid::Asterisk, _) => Ordering::Less,
242+
(SeqOrUid::Value(a), SeqOrUid::Value(b)) => a.cmp(b),
243+
}
244+
}
245+
}
246+
247+
impl PartialOrd for SeqOrUid {
248+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
249+
Some(self.cmp(other))
250+
}
251+
}
252+
195253
impl From<NonZeroU32> for SeqOrUid {
196254
fn from(value: NonZeroU32) -> Self {
197255
Self::Value(value)

0 commit comments

Comments
 (0)