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
115impl From<String> for SharedString<'_> {
116    fn from(s: String) -> Self {
117        Self::Owned(Arc::new(s))
118    }
119}
120
121impl<'a> From<&'a str> for SharedString<'a> {
122    fn from(s: &'a str) -> Self {
123        Self::Borrowed(s)
124    }
125}
126
127impl<'a> TryFrom<&'a [u8]> for SharedString<'a> {
128    type Error = std::str::Utf8Error;
129    fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
130        let s = std::str::from_utf8(s)?;
131        Ok(Self::Borrowed(s))
132    }
133}
134
135pub trait IntoSharedString<'a> {
136    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance);
137}
138
139impl<'a> IntoSharedString<'a> for SharedString<'a> {
140    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
141        (self, MessageConformance::default())
142    }
143}
144
145impl<'a> IntoSharedString<'a> for String {
146    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
147        (
148            SharedString::Owned(Arc::new(self)),
149            MessageConformance::default(),
150        )
151    }
152}
153
154impl<'a> IntoSharedString<'a> for &'a str {
155    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
156        (SharedString::Borrowed(self), MessageConformance::default())
157    }
158}
159
160impl<'a> IntoSharedString<'a> for &'a [u8] {
161    fn into_shared_string(self) -> (SharedString<'a>, MessageConformance) {
162        match std::str::from_utf8(self) {
163            Ok(s) => (SharedString::Borrowed(s), MessageConformance::default()),
164            Err(_) => (
165                SharedString::Owned(Arc::new(String::from_utf8_lossy(self).to_string())),
166                MessageConformance::NEEDS_TRANSFER_ENCODING,
167            ),
168        }
169    }
170}