use std::collections::BTreeMap;
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::marker::PhantomData;
use std::num::{NonZeroU32, NonZeroU64};
use std::ops::Bound::{Excluded, Included, Unbounded};
use std::path::PathBuf;
use std::str::FromStr;
use chrono::prelude::*;
use serde::{Deserialize, Serialize};
use crate::account::mailbox::BufferedMessage;
use crate::mime::fetch;
use crate::support::error::Error;
#[derive(
Deserialize,
Serialize,
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
)]
#[serde(transparent)]
pub struct Cid(pub u32);
impl Cid {
pub const GENESIS: Self = Cid(0);
pub const MIN: Self = Cid(1);
pub const END: Self = Cid(4_000_000_000);
pub const MAX: Self = Cid(Cid::END.0 - 1);
pub fn next(self) -> Option<Self> {
if self < Cid::MAX {
Some(Cid(self.0 + 1))
} else {
None
}
}
}
#[derive(
Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(transparent)]
pub struct Uid(pub NonZeroU32);
impl fmt::Debug for Uid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Uid({})", self.0.get())
}
}
impl Default for Uid {
fn default() -> Self {
Uid::MIN
}
}
impl Uid {
pub const MIN: Self = unsafe { Uid(NonZeroU32::new_unchecked(1)) };
pub const MAX: Self = unsafe {
Uid(NonZeroU32::new_unchecked(
(((1u64 << 63) - 1) / (Cid::END.0 as u64)) as u32,
))
};
pub fn of(uid: u32) -> Option<Self> {
NonZeroU32::new(uid).map(Uid).filter(|&u| u <= Uid::MAX)
}
pub fn next(self) -> Option<Self> {
if Uid::MAX == self {
None
} else {
Some(Uid(NonZeroU32::new(self.0.get() + 1).unwrap()))
}
}
#[cfg(test)]
pub fn u(uid: u32) -> Self {
Uid::of(uid).unwrap()
}
}
impl TryFrom<u32> for Uid {
type Error = ();
fn try_from(v: u32) -> Result<Self, ()> {
Self::of(v).ok_or(())
}
}
impl Into<u32> for Uid {
fn into(self) -> u32 {
self.0.get()
}
}
#[derive(
Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(transparent)]
pub struct Seqnum(pub NonZeroU32);
impl Default for Seqnum {
fn default() -> Self {
Seqnum::MIN
}
}
impl Seqnum {
pub const MIN: Self = unsafe { Seqnum(NonZeroU32::new_unchecked(1)) };
pub fn of(seqnum: u32) -> Option<Self> {
NonZeroU32::new(seqnum).map(Seqnum)
}
#[cfg(test)]
pub fn u(seqnum: u32) -> Self {
Seqnum::of(seqnum).unwrap()
}
pub fn to_index(self) -> usize {
let u: Result<usize, _> = self.0.get().try_into();
u.unwrap() - 1
}
pub fn from_index(ix: usize) -> Self {
Seqnum::of((ix + 1).try_into().unwrap()).unwrap()
}
}
impl TryFrom<u32> for Seqnum {
type Error = ();
fn try_from(v: u32) -> Result<Self, ()> {
Self::of(v).ok_or(())
}
}
impl Into<u32> for Seqnum {
fn into(self) -> u32 {
self.0.get()
}
}
impl fmt::Debug for Seqnum {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Seqnum({})", self.0.get())
}
}
#[derive(
Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(transparent)]
pub struct Modseq(NonZeroU64);
impl Modseq {
pub const MIN: Self =
unsafe { Modseq(NonZeroU64::new_unchecked(Cid::END.0 as u64)) };
pub fn of(raw: u64) -> Option<Self> {
NonZeroU64::new(raw)
.map(Modseq)
.filter(|&m| m >= Modseq::MIN)
}
pub fn new(uid: Uid, cid: Cid) -> Self {
Modseq(
NonZeroU64::new(
(uid.0.get() as u64) * (Cid::END.0 as u64) + cid.0 as u64,
)
.unwrap(),
)
}
pub fn raw(self) -> NonZeroU64 {
self.0
}
pub fn uid(self) -> Uid {
Uid::of((self.0.get() / (Cid::END.0 as u64)) as u32).unwrap()
}
pub fn cid(self) -> Cid {
Cid((self.0.get() % (Cid::END.0 as u64)) as u32)
}
pub fn combine(self, other: Self) -> Self {
Modseq::new(self.uid().max(other.uid()), self.cid().max(other.cid()))
}
pub fn with_uid(self, uid: Uid) -> Self {
Modseq::new(uid, self.cid())
}
pub fn with_cid(self, cid: Cid) -> Self {
Modseq::new(self.uid(), cid)
}
pub fn next(self) -> Option<Self> {
self.cid().next().map(|cid| self.with_cid(cid))
}
}
impl fmt::Debug for Modseq {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Modseq({}:{}={})",
self.uid().0.get(),
self.cid().0,
self.0.get()
)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct SeqRange<T> {
parts: BTreeMap<u32, u32>,
_t: PhantomData<T>,
}
impl<T> SeqRange<T> {
pub fn new() -> Self {
SeqRange {
parts: BTreeMap::new(),
_t: PhantomData,
}
}
}
impl<T: TryFrom<u32> + Into<u32> + PartialOrd + Send + Sync> SeqRange<T> {
#[cfg(test)]
pub fn just(item: T) -> Self {
let mut this = SeqRange::new();
this.append(item);
this
}
pub fn range(start: T, end: T) -> Self {
let mut this = SeqRange::new();
this.insert(start, end);
this
}
pub fn append(&mut self, item: T) {
let item: u32 = item.into();
if let Some(end) = self.parts.values_mut().next_back() {
assert!(item > *end);
if item == *end + 1 {
*end = item;
return;
}
}
self.parts.insert(item, item);
}
pub fn insert(&mut self, start_incl: T, end_incl: T) {
assert!(end_incl >= start_incl);
self.insert_raw(start_incl.into(), end_incl.into());
}
fn insert_raw(&mut self, start_incl: u32, mut end_incl: u32) {
loop {
let following = self
.parts
.range((Excluded(start_incl), Unbounded))
.next()
.map(|(&start, &end)| (start, end));
if let Some((following_start, following_end)) = following {
if following_start - 1 <= end_incl {
end_incl = end_incl.max(following_end);
self.parts.remove(&following_start);
continue;
}
}
break;
}
let preceding = self
.parts
.range((Unbounded, Included(end_incl)))
.next_back()
.map(|(&start, &end)| (start, end));
if let Some((preceding_start, preceding_end)) = preceding {
if preceding_end + 1 >= start_incl {
if start_incl < preceding_start {
self.parts.remove(&preceding_start);
self.parts.insert(start_incl, end_incl.max(preceding_end));
} else {
self.parts
.insert(preceding_start, end_incl.max(preceding_end));
}
return;
}
}
self.parts.insert(start_incl, end_incl);
}
pub fn contains(&self, v: T) -> bool {
let v: u32 = v.into();
self.parts
.range(..=v)
.next_back()
.filter(|&(_, &end)| end >= v)
.is_some()
}
pub fn items<'a>(
&'a self,
max: impl Into<u32>,
) -> impl Iterator<Item = T> + 'a {
let max: u32 = max.into();
self.parts
.iter()
.map(|(&start, &end)| (start, end))
.filter(move |&(start, _)| start <= max)
.flat_map(move |(start, end)| start..=end.min(max))
.filter_map(|v| T::try_from(v).ok())
}
pub fn parse(raw: &str, splat: T) -> Option<Self> {
fn do_parse(r: &str, splat: u32) -> Option<u32> {
if "*" == r {
Some(splat)
} else {
r.parse().ok()
}
}
let splat = splat.into();
let mut this = Self::new();
for part in raw.split(',') {
let mut subs = part.split(':');
match (subs.next(), subs.next(), subs.next()) {
(Some(only), None, None) => {
let only = do_parse(only, splat)?;
this.insert_raw(only, only);
}
(Some(start), Some(end), None) => {
let start = do_parse(start, splat)?;
let end = do_parse(end, splat)?;
this.insert_raw(start.min(end), end.max(start));
}
_ => return None,
}
}
Some(this)
}
pub fn len(&self) -> usize {
self.parts
.iter()
.map(|(start, end)| end - start + 1)
.sum::<u32>() as usize
}
pub fn max(&self) -> Option<u32> {
self.parts.values().rev().copied().next()
}
pub fn clear(&mut self) {
self.parts.clear();
}
}
impl<T> SeqRange<T> {
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
}
impl<T> fmt::Display for SeqRange<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (ix, (&start, &end)) in self.parts.iter().enumerate() {
let delim = if 0 == ix { "" } else { "," };
if start == end {
write!(f, "{}{}", delim, start)?;
} else {
write!(f, "{}{}:{}", delim, start, end)?;
}
}
Ok(())
}
}
impl fmt::Debug for SeqRange<Seqnum> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[Seqnum {}]", self)
}
}
impl fmt::Debug for SeqRange<Uid> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[Uid {}]", self)
}
}
impl<T> Default for SeqRange<T> {
fn default() -> Self {
SeqRange::new()
}
}
#[derive(Clone, Serialize, Deserialize)]
pub enum Flag {
Answered,
Deleted,
Draft,
Flagged,
Seen,
Keyword(String),
}
impl fmt::Display for Flag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Flag::Answered => write!(f, "\\Answered"),
Flag::Deleted => write!(f, "\\Deleted"),
Flag::Draft => write!(f, "\\Draft"),
Flag::Flagged => write!(f, "\\Flagged"),
Flag::Seen => write!(f, "\\Seen"),
Flag::Keyword(ref kw) => write!(f, "{}", kw),
}
}
}
impl fmt::Debug for Flag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Flag as fmt::Display>::fmt(self, f)
}
}
impl FromStr for Flag {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
if s.eq_ignore_ascii_case("\\answered") {
Ok(Flag::Answered)
} else if s.eq_ignore_ascii_case("\\deleted") {
Ok(Flag::Deleted)
} else if s.eq_ignore_ascii_case("\\draft") {
Ok(Flag::Draft)
} else if s.eq_ignore_ascii_case("\\flagged") {
Ok(Flag::Flagged)
} else if s.eq_ignore_ascii_case("\\seen") {
Ok(Flag::Seen)
} else if s.starts_with('\\') {
Err(Error::NxFlag)
} else if s.as_bytes().iter().copied().all(is_atom_char) {
Ok(Flag::Keyword(s.to_owned()))
} else {
Err(Error::UnsafeName)
}
}
}
fn is_atom_char(ch: u8) -> bool {
match ch {
0..=b' ' => false,
127..=255 => false,
b'(' | b')' | b'{' | b'*' | b'%' | b'\\' | b'"' | b']' => false,
_ => true,
}
}
impl PartialEq for Flag {
fn eq(&self, other: &Flag) -> bool {
match (self, other) {
(&Flag::Answered, &Flag::Answered) => true,
(&Flag::Deleted, &Flag::Deleted) => true,
(&Flag::Draft, &Flag::Draft) => true,
(&Flag::Flagged, &Flag::Flagged) => true,
(&Flag::Seen, &Flag::Seen) => true,
(&Flag::Keyword(ref a), &Flag::Keyword(ref b)) => {
a.eq_ignore_ascii_case(b)
}
_ => false,
}
}
}
impl Eq for Flag {}
#[derive(
Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
)]
pub enum MailboxAttribute {
Noselect,
Noinferiors,
HasChildren,
HasNoChildren,
NonExistent,
Subscribed,
Archive,
Drafts,
Flagged,
Junk,
Sent,
Trash,
Important,
}
impl MailboxAttribute {
pub fn name(&self) -> &'static str {
match *self {
MailboxAttribute::Noselect => "\\Noselect",
MailboxAttribute::Noinferiors => "\\Noinferiors",
MailboxAttribute::HasChildren => "\\HasChildren",
MailboxAttribute::HasNoChildren => "\\HasNoChildren",
MailboxAttribute::NonExistent => "\\NonExistent",
MailboxAttribute::Subscribed => "\\Subscribed",
MailboxAttribute::Archive => "\\Archive",
MailboxAttribute::Drafts => "\\Drafts",
MailboxAttribute::Flagged => "\\Flagged",
MailboxAttribute::Junk => "\\Junk",
MailboxAttribute::Sent => "\\Sent",
MailboxAttribute::Trash => "\\Trash",
MailboxAttribute::Important => "\\Important",
}
}
}
impl fmt::Display for MailboxAttribute {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl fmt::Debug for MailboxAttribute {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<MailboxAttribute as fmt::Display>::fmt(self, f)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateRequest {
pub name: String,
pub special_use: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenameRequest {
pub existing_name: String,
pub new_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct StatusRequest {
pub name: String,
pub messages: bool,
pub recent: bool,
pub uidnext: bool,
pub uidvalidity: bool,
pub unseen: bool,
pub max_modseq: bool,
pub mailbox_id: bool,
pub size: bool,
pub deleted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct StatusResponse {
pub name: String,
pub messages: Option<usize>,
pub recent: Option<usize>,
pub uidnext: Option<Uid>,
pub uidvalidity: Option<u32>,
pub unseen: Option<usize>,
pub max_modseq: Option<u64>,
pub mailbox_id: Option<String>,
pub size: Option<u64>,
pub deleted: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ListRequest {
pub reference: String,
pub patterns: Vec<String>,
pub select_subscribed: bool,
pub select_special_use: bool,
pub recursive_match: bool,
pub return_subscribed: bool,
pub return_children: bool,
pub return_special_use: bool,
pub lsub_style: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, PartialOrd, Ord)]
pub struct ListResponse {
pub name: String,
pub attributes: Vec<MailboxAttribute>,
pub child_info: Vec<&'static str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectResponse {
pub flags: Vec<Flag>,
pub exists: usize,
pub recent: usize,
pub unseen: Option<Seqnum>,
pub uidnext: Uid,
pub uidvalidity: u32,
pub read_only: bool,
pub max_modseq: Option<Modseq>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PollResponse {
pub expunge: Vec<(Seqnum, Uid)>,
pub exists: Option<usize>,
pub recent: Option<usize>,
pub fetch: Vec<Uid>,
pub max_modseq: Option<Modseq>,
}
#[derive(Clone, Debug)]
pub struct QresyncRequest {
pub uid_validity: u32,
pub resync_from: Option<Modseq>,
pub known_uids: Option<SeqRange<Uid>>,
pub mapping_reference: Option<(SeqRange<Seqnum>, SeqRange<Uid>)>,
}
#[derive(Clone, Debug, Default)]
pub struct QresyncResponse {
pub expunged: SeqRange<Uid>,
pub changed: Vec<Uid>,
}
#[derive(Clone, Debug)]
pub struct StoreRequest<'a, ID>
where
SeqRange<ID>: fmt::Debug,
{
pub ids: &'a SeqRange<ID>,
pub flags: &'a [Flag],
pub remove_listed: bool,
pub remove_unlisted: bool,
pub loud: bool,
pub unchanged_since: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoreResponse<ID>
where
SeqRange<ID>: fmt::Debug,
{
pub ok: bool,
pub modified: SeqRange<ID>,
}
#[derive(Clone, Debug, Default)]
pub struct FetchRequest<ID>
where
SeqRange<ID>: fmt::Debug,
{
pub ids: SeqRange<ID>,
pub uid: bool,
pub flags: bool,
pub rfc822size: bool,
pub internal_date: bool,
pub envelope: bool,
pub bodystructure: bool,
pub sections: Vec<fetch::section::BodySection>,
pub modseq: bool,
pub changed_since: Option<Modseq>,
pub collect_vanished: bool,
pub email_id: bool,
pub thread_id: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FetchResponseKind {
Ok,
No,
Bye,
}
impl Default for FetchResponseKind {
fn default() -> Self {
FetchResponseKind::Ok
}
}
#[derive(Debug, Default)]
pub struct PrefetchResponse {
pub vanished: SeqRange<Uid>,
pub flags: Vec<Flag>,
}
#[derive(Debug, Default)]
pub struct FetchResponse {
pub kind: FetchResponseKind,
}
#[derive(Clone, Debug, Default)]
pub struct SearchRequest {
pub queries: Vec<SearchQuery>,
}
#[derive(Clone, Debug)]
pub enum SearchQuery {
SequenceSet(SeqRange<Seqnum>),
All,
Answered,
Bcc(String),
Before(NaiveDate),
Body(String),
Cc(String),
Deleted,
Draft,
Flagged,
From(String),
Header(String, String),
Keyword(String),
Larger(u32),
New,
Not(Box<SearchQuery>),
Old, On(NaiveDate),
Or(Box<SearchQuery>, Box<SearchQuery>),
Recent,
Seen,
SentBefore(NaiveDate),
SentOn(NaiveDate),
SentSince(NaiveDate),
Since(NaiveDate),
Smaller(u32),
Subject(String),
Text(String),
To(String),
UidSet(SeqRange<Uid>), Unanswered,
Undeleted,
Undraft,
Unflagged,
Unkeyword(String),
Unseen,
And(Vec<SearchQuery>),
Modseq(u64),
EmailId(String),
ThreadId(String),
}
#[derive(Clone, Debug)]
pub struct SearchResponse<ID> {
pub hits: Vec<ID>,
pub hit_uids: Vec<Uid>,
pub first_modseq: Option<Modseq>,
pub last_modseq: Option<Modseq>,
pub max_modseq: Option<Modseq>,
}
#[derive(Debug, Default)]
pub struct AppendRequest {
pub items: Vec<AppendItem>,
}
#[derive(Debug)]
pub struct AppendItem {
pub buffer_file: BufferedMessage,
pub flags: Vec<Flag>,
}
#[derive(Debug, Clone)]
pub struct AppendResponse {
pub uid_validity: u32,
pub uids: SeqRange<Uid>,
}
#[derive(Debug, Clone)]
pub struct CopyRequest<ID>
where
SeqRange<ID>: fmt::Debug,
{
pub ids: SeqRange<ID>,
}
#[derive(Debug, Clone, Default)]
pub struct CopyResponse {
pub uid_validity: u32,
pub from_uids: SeqRange<Uid>,
pub to_uids: SeqRange<Uid>,
}
#[derive(Debug, Clone, Default)]
pub struct SetUserConfigRequest {
pub internal_key_pattern: Option<String>,
pub external_key_pattern: Option<String>,
pub password: Option<String>,
}
#[derive(Clone, Debug)]
pub struct CommonPaths {
pub tmp: PathBuf,
pub garbage: PathBuf,
}
pub const EMAIL_ID_LEN: usize = 15;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct MessageMetadata {
#[serde(alias = "s")]
pub size: u32,
#[serde(alias = "d")]
pub internal_date: DateTime<FixedOffset>,
#[serde(alias = "i", with = "email_id_ser")]
pub email_id: [u8; EMAIL_ID_LEN],
}
impl MessageMetadata {
pub fn format_email_id(&self) -> String {
format!(
"E{}",
base64::encode_config(&self.email_id, base64::URL_SAFE)
)
}
}
mod email_id_ser {
use std::fmt;
use serde::{Deserializer, Serializer};
use super::EMAIL_ID_LEN;
pub fn serialize<S: Serializer>(
bytes: &[u8; EMAIL_ID_LEN],
ser: S,
) -> Result<S::Ok, S::Error> {
ser.serialize_bytes(bytes)
}
pub fn deserialize<'a, D: Deserializer<'a>>(
de: D,
) -> Result<[u8; EMAIL_ID_LEN], D::Error> {
use serde::de::Error;
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = [u8; EMAIL_ID_LEN];
fn visit_bytes<E: Error>(
self,
bytes: &[u8],
) -> Result<Self::Value, E> {
if EMAIL_ID_LEN != bytes.len() {
Err(Error::custom("Incorrect email_id length"))
} else {
let mut email_id = [0u8; EMAIL_ID_LEN];
email_id.copy_from_slice(bytes);
Ok(email_id)
}
}
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[u8;15]")
}
}
de.deserialize_bytes(Visitor)
}
}
#[cfg(test)]
impl Default for MessageMetadata {
fn default() -> Self {
MessageMetadata {
size: 0,
internal_date: FixedOffset::east(0).timestamp_millis(0),
email_id: Default::default(),
}
}
}
#[cfg(test)]
mod test {
use proptest::prelude::*;
use super::*;
fn assert_sr(
expected_content: &[u32],
expected_string: &str,
seqrange: SeqRange<Uid>,
) {
let actual: Vec<u32> =
seqrange.items(u32::MAX).map(|u| u.0.get()).collect();
assert_eq!(expected_content, &actual[..]);
assert_eq!(expected_string, &seqrange.to_string());
}
#[test]
fn seqrange_parsing() {
assert_sr(&[1], "1", SeqRange::parse("1", Uid::u(10)).unwrap());
assert_sr(&[10], "10", SeqRange::parse("*", Uid::u(10)).unwrap());
assert_sr(&[1, 2], "1:2", SeqRange::parse("1:2", Uid::u(10)).unwrap());
assert_sr(&[1, 2], "1:2", SeqRange::parse("2:1", Uid::u(10)).unwrap());
assert_sr(
&[9, 10],
"9:10",
SeqRange::parse("9:*", Uid::u(10)).unwrap(),
);
assert_sr(
&[9, 10],
"9:10",
SeqRange::parse("*:9", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 3, 5],
"1,3,5",
SeqRange::parse("1,3,5", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 3, 5],
"1,3,5",
SeqRange::parse("3,1,5", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 3, 5],
"1,3,5",
SeqRange::parse("3,5,1", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 9, 10],
"1:2,9:10",
SeqRange::parse("1:2,9:*", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 9, 10],
"1:2,9:10",
SeqRange::parse("*:9,2:1", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1,2,3,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:2,3,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:3,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1,2:3,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1,2:4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:2,3:4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:4,2:3", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("2:3,1:4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:4,2,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("2:4,1,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:4,1:2", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:2,1:4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1:4,1:4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("1,3:2,4", Uid::u(10)).unwrap(),
);
assert_sr(
&[1, 2, 3, 4],
"1:4",
SeqRange::parse("2,4:1,3", Uid::u(10)).unwrap(),
);
}
#[test]
fn seqrange_append() {
let mut seqrange = SeqRange::new();
seqrange.append(Uid::u(1));
assert_eq!("1", &seqrange.to_string());
seqrange.append(Uid::u(2));
assert_eq!("1:2", &seqrange.to_string());
seqrange.append(Uid::u(3));
assert_eq!("1:3", &seqrange.to_string());
seqrange.append(Uid::u(5));
assert_eq!("1:3,5", &seqrange.to_string());
seqrange.append(Uid::u(6));
assert_eq!("1:3,5:6", &seqrange.to_string());
}
proptest! {
#[test]
fn seqrange_properties(
ranges in prop::collection::vec((1u32..30, 1u32..=10), 1..=5)
) {
let mut expected = Vec::new();
let mut seqrange = SeqRange::new();
for &(start, extent) in &ranges {
seqrange.insert(Uid::u(start), Uid::u(start + extent));
expected.extend((start..=start + extent).into_iter());
}
expected.sort();
expected.dedup();
let actual: Vec<u32> = seqrange.items(u32::MAX).map(
|u| u.0.get()).collect();
assert_eq!(expected, actual);
for i in 1..50 {
assert_eq!(
expected.contains(&i),
seqrange.contains(Uid::u(i)),
"Bad contains result for {}",
i
);
}
assert_eq!(
seqrange,
SeqRange::parse(&seqrange.to_string(), Uid::MAX).unwrap());
}
}
}