mailparsing/
strings.rs

1use crate::MessageConformance;
2use std::sync::Arc;
3
4/// Helper for holding either an owned or borrowed string,
5/// and where the slice method is aware of that borrowing,
6/// allowing for efficient copying and slicing without
7/// making extraneous additional copies
8pub enum SharedString<'a> {
9    Owned(Arc<String>),
10    Borrowed(&'a str),
11    Sliced {
12        other: Arc<String>,
13        range: std::ops::Range<usize>,
14    },
15}
16
17impl std::cmp::PartialEq<Self> for SharedString<'_> {
18    fn eq(&self, other: &Self) -> bool {
19        self.as_str().eq(other.as_str())
20    }
21}
22
23impl std::cmp::PartialEq<&str> for SharedString<'_> {
24    fn eq(&self, other: &&str) -> bool {
25        self.as_str().eq(*other)
26    }
27}
28
29impl std::fmt::Display for SharedString<'_> {
30    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
31        let str = self.as_str();
32        fmt.write_str(str)
33    }
34}
35
36impl std::fmt::Debug for SharedString<'_> {
37    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
38        let str = self.as_str();
39        write!(fmt, "{str:?}")
40    }
41}
42
43impl std::ops::Deref for SharedString<'_> {
44    type Target = str;
45    fn deref(&self) -> &str {
46        self.as_str()
47    }
48}
49
50impl std::ops::Index<usize> for SharedString<'_> {
51    type Output = u8;
52    fn index(&self, index: usize) -> &u8 {
53        &self.as_str().as_bytes()[index]
54    }
55}
56
57impl Clone for SharedString<'_> {
58    fn clone(&self) -> Self {
59        match self {
60            Self::Owned(s) => Self::Sliced {
61                other: Arc::clone(s),
62                range: 0..s.len(),
63            },
64            Self::Borrowed(s) => Self::Borrowed(s),
65            Self::Sliced { other, range } => Self::Sliced {
66                other: Arc::clone(other),
67                range: range.clone(),
68            },
69        }
70    }
71}
72
73impl SharedString<'_> {
74    pub fn slice(&self, slice_range: std::ops::Range<usize>) -> Self {
75        self.assert_slice(slice_range.clone());
76        match self {
77            Self::Owned(s) => Self::Sliced {
78                other: Arc::clone(s),
79                range: slice_range,
80            },
81            Self::Borrowed(s) => Self::Borrowed(s.get(slice_range).unwrap()),
82            Self::Sliced { other, range } => {
83                let len = slice_range.end - slice_range.start;
84                Self::Sliced {
85                    other: Arc::clone(other),
86                    range: range.start + slice_range.start..range.start + slice_range.start + len,
87                }
88            }
89        }
90    }
91
92    fn assert_slice(&self, slice_range: std::ops::Range<usize>) {
93        if self.as_str().get(slice_range.clone()).is_none() {
94            panic!("slice range {slice_range:?} is invalid for {self:?}");
95        }
96    }
97
98    pub fn as_str(&self) -> &str {
99        match self {
100            Self::Owned(s) => s.as_str(),
101            Self::Borrowed(s) => s,
102            Self::Sliced { other, range } => other.as_str().get(range.clone()).unwrap(),
103        }
104    }
105
106    pub fn len(&self) -> usize {
107        match self {
108            Self::Owned(s) => s.len(),
109            Self::Borrowed(s) => s.len(),
110            Self::Sliced { range, .. } => range.len(),
111        }
112    }
113
114    pub fn to_owned(&'_ self) -> SharedString<'static> {
115        match self {
116            SharedString::Owned(s) => SharedString::Owned(Arc::clone(s)),
117            SharedString::Borrowed(s) => SharedString::Owned(Arc::new(s.to_string())),
118            SharedString::Sliced { other, range } => SharedString::Sliced {
119                other: other.clone(),
120                range: range.clone(),
121            },
122        }
123    }
124}
125
126impl From<String> for SharedString<'_> {
127    fn from(s: String) -> Self {
128        Self::Owned(Arc::new(s))
129    }
130}
131
132impl<'a> From<&'a str> for SharedString<'a> {
133    fn from(s: &'a str) -> Self {
134        Self::Borrowed(s)
135    }
136}
137
138impl<'a> TryFrom<&'a [u8]> for SharedString<'a> {
139    type Error = std::str::Utf8Error;
140    fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
141        let s = std::str::from_utf8(s)?;
142        Ok(Self::Borrowed(s))
143    }
144}
145
146pub trait IntoSharedString<'a> {
147    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance);
148}
149
150impl<'a> IntoSharedString<'a> for SharedString<'a> {
151    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
152        (self, MessageConformance::default())
153    }
154}
155
156impl<'a> IntoSharedString<'a> for String {
157    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
158        (
159            SharedString::Owned(Arc::new(self)),
160            MessageConformance::default(),
161        )
162    }
163}
164
165impl<'a> IntoSharedString<'a> for &'a str {
166    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
167        (SharedString::Borrowed(self), MessageConformance::default())
168    }
169}
170
171impl<'a> IntoSharedString<'a> for &'a [u8] {
172    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
173        match std::str::from_utf8(self) {
174            Ok(s) => (SharedString::Borrowed(s), MessageConformance::default()),
175            Err(_) => (
176                SharedString::Owned(Arc::new(String::from_utf8_lossy(self).to_string())),
177                MessageConformance::NEEDS_TRANSFER_ENCODING,
178            ),
179        }
180    }
181}