1#[cfg(feature = "serde")]
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
4use alloc::{
5 borrow::{Cow, ToOwned},
6 string::{String, ToString},
7 sync::Arc,
8};
9use core::{
10 cmp::Ordering,
11 hash::{Hash, Hasher},
12};
13
14#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct CheapStr<'a>(#[cfg_attr(feature = "serde", serde(borrow))] Inner<'a>);
35
36#[derive(Eq, Clone)]
37enum Inner<'a> {
38 Static(&'static str),
39 Borrowed(&'a str),
40 Owned(Arc<str>),
41}
42
43impl Default for Inner<'_> {
44 fn default() -> Self {
45 Self::Static("")
46 }
47}
48
49impl<'a> PartialEq for Inner<'a> {
50 fn eq(&self, other: &Inner<'a>) -> bool {
51 self.as_str() == other.as_str()
52 }
53}
54
55impl<'a> Ord for Inner<'a> {
56 fn cmp(&self, other: &Inner<'a>) -> Ordering {
57 self.as_str().cmp(other.as_str())
58 }
59}
60
61impl<'a> PartialOrd for Inner<'a> {
62 fn partial_cmp(&self, other: &Inner<'a>) -> Option<Ordering> {
63 Some(self.cmp(other))
64 }
65}
66
67impl Hash for Inner<'_> {
68 fn hash<H: Hasher>(&self, h: &mut H) {
69 self.as_str().hash(h)
70 }
71}
72
73impl Inner<'_> {
74 pub fn as_str(&self) -> &str {
76 match self {
77 Inner::Static(s) => s,
78 Inner::Borrowed(s) => s,
79 Inner::Owned(s) => s,
80 }
81 }
82}
83
84#[cfg(feature = "serde")]
85impl Serialize for Inner<'_> {
86 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
87 s.serialize_str(self.as_str())
88 }
89}
90
91#[cfg(feature = "serde")]
92impl<'de: 'a, 'a> Deserialize<'de> for Inner<'a> {
93 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94 where
95 D: Deserializer<'de>,
96 {
97 <&'a str>::deserialize(deserializer).map(Inner::Borrowed)
98 }
99}
100
101impl CheapStr<'_> {
102 pub const fn from_static(s: &'static str) -> Self {
104 CheapStr(Inner::Static(s))
105 }
106
107 pub fn as_ref(&self) -> CheapStr<'_> {
109 match &self.0 {
110 Inner::Static(s) => CheapStr(Inner::Static(s)),
111 Inner::Borrowed(s) => CheapStr(Inner::Borrowed(s)),
112 Inner::Owned(s) => CheapStr(Inner::Borrowed(s)),
113 }
114 }
115
116 pub fn as_str(&self) -> &str {
118 self.0.as_str()
119 }
120
121 pub fn to_owned(&self) -> CheapStr<'static> {
123 self.clone().into_owned()
124 }
125
126 pub fn into_owned(self) -> CheapStr<'static> {
128 match self.0 {
129 Inner::Static(s) => CheapStr(Inner::Static(s)),
130 Inner::Borrowed(s) => CheapStr(Inner::Owned(s.to_owned().into())),
131 Inner::Owned(s) => CheapStr(Inner::Owned(s)),
132 }
133 }
134}
135
136impl<'a> From<&'a str> for CheapStr<'a> {
137 fn from(value: &'a str) -> Self {
138 Self(Inner::Borrowed(value))
139 }
140}
141
142impl<'a> From<&'a String> for CheapStr<'a> {
143 fn from(value: &'a String) -> Self {
144 Self(Inner::Borrowed(value))
145 }
146}
147
148impl From<String> for CheapStr<'_> {
149 fn from(value: String) -> Self {
150 Self(Inner::Owned(value.into()))
151 }
152}
153
154impl From<Arc<str>> for CheapStr<'_> {
155 fn from(value: Arc<str>) -> Self {
156 Self(Inner::Owned(value))
157 }
158}
159
160impl<'a> From<Cow<'a, str>> for CheapStr<'a> {
161 fn from(value: Cow<'a, str>) -> Self {
162 match value {
163 Cow::Owned(value) => value.into(),
164 Cow::Borrowed(value) => value.into(),
165 }
166 }
167}
168
169impl<'a> From<CheapStr<'a>> for String {
170 fn from(value: CheapStr<'a>) -> String {
171 match value.0 {
172 Inner::Static(s) => s.into(),
173 Inner::Borrowed(s) => s.into(),
174 Inner::Owned(s) => s.to_string(),
175 }
176 }
177}
178
179impl<'a> From<&'a CheapStr<'_>> for &'a str {
180 fn from(value: &'a CheapStr<'_>) -> &'a str {
181 value.as_str()
182 }
183}
184
185impl core::ops::Deref for CheapStr<'_> {
186 type Target = str;
187
188 fn deref(&self) -> &Self::Target {
189 self.as_str()
190 }
191}
192
193impl PartialEq<str> for CheapStr<'_> {
194 fn eq(&self, other: &str) -> bool {
195 self.as_str() == other
196 }
197}
198
199impl PartialEq<&str> for CheapStr<'_> {
200 fn eq(&self, other: &&str) -> bool {
201 self.as_str() == *other
202 }
203}
204
205impl core::fmt::Debug for CheapStr<'_> {
206 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207 core::fmt::Debug::fmt(self.as_str(), f)
208 }
209}
210
211impl core::fmt::Display for CheapStr<'_> {
212 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
213 core::fmt::Display::fmt(self.as_str(), f)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::CheapStr;
220 use alloc::string::{String, ToString};
221
222 #[test]
223 fn from_string() {
224 let string = String::from("value");
225 let v = CheapStr::from(&string);
226 assert_eq!(v.as_str(), "value");
227 }
228
229 #[test]
230 fn test_ordering() {
231 let first = CheapStr::from("a".to_string());
232 let second = CheapStr::from_static("b");
233 assert!(first < second);
234 }
235}
236
237#[cfg(all(test, feature = "serde"))]
238mod serde_tests {
239 use super::CheapStr;
240 use alloc::string::String;
241
242 #[test]
243 fn serde_round_trip() {
244 let s = CheapStr::from("hello");
245 let json = serde_json::to_string(&s).unwrap();
246 assert_eq!(json, "\"hello\"");
247 let deserialized: CheapStr<'_> = serde_json::from_str(&json).unwrap();
248 assert_eq!(deserialized, s);
249
250 let owned = CheapStr::from(String::from("hello"));
252 assert_eq!(serde_json::to_string(&owned).unwrap(), json);
253 }
254
255 #[test]
256 fn serde_non_borrowable_input_errors() {
257 serde_json::from_str::<CheapStr<'_>>("\"a\\nb\"").unwrap_err();
260 }
261
262 #[test]
263 fn serde_borrowed_deserialization() {
264 let json = String::from("\"borrowed\"");
265 let s: CheapStr<'_> = serde_json::from_str(&json).unwrap();
266 assert_eq!(s.as_str(), "borrowed");
267 assert!(core::ptr::eq(s.as_str().as_ptr(), json[1..].as_ptr()));
269 }
270}