stack_string/
stack_cow.rs

1use derive_more::Display;
2use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
3use std::{
4    borrow::{Borrow, Cow},
5    convert::Infallible,
6    ffi::OsStr,
7    fmt::{self, Write as FmtWrite},
8    iter::FromIterator,
9    ops::Deref,
10    path::Path,
11    str::FromStr,
12    string::FromUtf8Error,
13};
14
15use crate::stack_string::StackString;
16
17#[cfg(feature = "postgres_types")]
18use bytes::BytesMut;
19#[cfg(feature = "postgres_types")]
20use postgres_types::{FromSql, IsNull, ToSql, Type};
21
22#[cfg(feature = "utoipa_types")]
23use utoipa::{PartialSchema, ToSchema};
24
25#[cfg(feature = "axum_types")]
26use axum::response::IntoResponse;
27
28#[cfg(feature = "axum_types")]
29use axum::body::Body;
30
31#[derive(Display, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
32pub enum StackCow<'a> {
33    Borrowed(&'a str),
34    Owned(StackString),
35}
36
37impl Default for StackCow<'_> {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl<'a> StackCow<'a> {
44    #[must_use]
45    pub fn new() -> Self {
46        Self::Owned(StackString::new())
47    }
48
49    #[must_use]
50    pub fn to_owned(&self) -> StackCow<'static> {
51        self.clone().into_owned()
52    }
53
54    #[must_use]
55    pub fn into_owned(self) -> StackCow<'static> {
56        match self {
57            Self::Borrowed(b) => StackCow::Owned(b.into()),
58            Self::Owned(o) => StackCow::Owned(o),
59        }
60    }
61
62    #[must_use]
63    pub fn is_borrowed(&self) -> bool {
64        match self {
65            Self::Borrowed(_) => true,
66            Self::Owned(_) => false,
67        }
68    }
69
70    #[must_use]
71    pub fn is_owned(&self) -> bool {
72        !self.is_borrowed()
73    }
74
75    #[must_use]
76    pub fn as_str(&self) -> &str {
77        match self {
78            Self::Borrowed(s) => s,
79            Self::Owned(o) => o.as_str(),
80        }
81    }
82
83    /// Construct a `StackCow` from a `Vec<u8>`
84    /// # Errors
85    ///
86    /// Will return an Error if the byte slice is not utf8 compliant
87    pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
88        String::from_utf8(vec).map(Into::into)
89    }
90
91    #[must_use]
92    pub fn from_utf8_lossy(v: &'a [u8]) -> Self {
93        StackString::from_utf8_lossy(v).into()
94    }
95
96    /// # Panics
97    /// `from_display` panics if a formatting trait implementation returns an
98    /// error. This indicates an incorrect implementation
99    /// since `fmt::Write for String` never returns an error itself.
100    pub fn from_display(buf: impl fmt::Display) -> Self {
101        let mut s = StackString::new();
102        write!(s, "{buf}").unwrap();
103        s.into()
104    }
105}
106
107impl Deref for StackCow<'_> {
108    type Target = str;
109
110    fn deref(&self) -> &Self::Target {
111        match self {
112            Self::Borrowed(b) => b,
113            Self::Owned(o) => o,
114        }
115    }
116}
117
118impl Serialize for StackCow<'_> {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: Serializer,
122    {
123        serializer.serialize_str(self.as_str())
124    }
125}
126
127impl<'de> Deserialize<'de> for StackCow<'_> {
128    fn deserialize<D>(deserializer: D) -> Result<StackCow<'static>, D::Error>
129    where
130        D: Deserializer<'de>,
131    {
132        StackString::deserialize(deserializer).map(Into::into)
133    }
134}
135
136impl From<StackString> for StackCow<'_> {
137    fn from(item: StackString) -> Self {
138        Self::Owned(item)
139    }
140}
141
142impl<'a> From<StackCow<'a>> for StackString {
143    fn from(item: StackCow<'a>) -> Self {
144        match item {
145            StackCow::Borrowed(s) => s.into(),
146            StackCow::Owned(s) => s,
147        }
148    }
149}
150
151impl<'a> From<Cow<'a, str>> for StackCow<'a> {
152    fn from(item: Cow<'a, str>) -> Self {
153        match item {
154            Cow::Borrowed(s) => Self::Borrowed(s),
155            Cow::Owned(s) => Self::Owned(s.into()),
156        }
157    }
158}
159
160impl From<StackCow<'_>> for String {
161    fn from(item: StackCow) -> Self {
162        match item {
163            StackCow::Borrowed(s) => s.into(),
164            StackCow::Owned(s) => s.into(),
165        }
166    }
167}
168
169impl From<&StackCow<'_>> for String {
170    fn from(item: &StackCow) -> Self {
171        item.as_str().into()
172    }
173}
174
175impl From<String> for StackCow<'_> {
176    fn from(item: String) -> Self {
177        Self::Owned(item.into())
178    }
179}
180
181impl<'a> From<&'a String> for StackCow<'a> {
182    fn from(item: &'a String) -> Self {
183        Self::Borrowed(item.as_str())
184    }
185}
186
187impl<'a> From<&'a str> for StackCow<'a> {
188    fn from(item: &'a str) -> Self {
189        StackCow::Borrowed(item)
190    }
191}
192
193impl<'a> From<&'a StackCow<'a>> for &'a str {
194    fn from(item: &'a StackCow) -> &'a str {
195        item.as_str()
196    }
197}
198
199impl Borrow<str> for StackCow<'_> {
200    fn borrow(&self) -> &str {
201        self.as_str()
202    }
203}
204
205impl AsRef<str> for StackCow<'_> {
206    fn as_ref(&self) -> &str {
207        self.as_str()
208    }
209}
210
211impl AsRef<[u8]> for StackCow<'_> {
212    fn as_ref(&self) -> &[u8] {
213        self.as_str().as_bytes()
214    }
215}
216
217impl AsRef<OsStr> for StackCow<'_> {
218    fn as_ref(&self) -> &OsStr {
219        self.as_str().as_ref()
220    }
221}
222
223impl AsRef<Path> for StackCow<'_> {
224    fn as_ref(&self) -> &Path {
225        Path::new(self)
226    }
227}
228
229impl FromStr for StackCow<'_> {
230    type Err = Infallible;
231    fn from_str(s: &str) -> Result<Self, Self::Err> {
232        Ok(Self::Owned(s.into()))
233    }
234}
235
236impl<'a> PartialEq<Cow<'a, str>> for StackCow<'_> {
237    #[inline]
238    fn eq(&self, other: &Cow<'a, str>) -> bool {
239        PartialEq::eq(&self[..], &other[..])
240    }
241}
242
243impl<'a> PartialOrd<Cow<'a, str>> for StackCow<'_> {
244    fn partial_cmp(&self, other: &Cow<'a, str>) -> Option<std::cmp::Ordering> {
245        PartialOrd::partial_cmp(&self[..], &other[..])
246    }
247}
248
249impl PartialEq<String> for StackCow<'_> {
250    #[inline]
251    fn eq(&self, other: &String) -> bool {
252        PartialEq::eq(&self[..], &other[..])
253    }
254}
255
256impl PartialOrd<String> for StackCow<'_> {
257    fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
258        PartialOrd::partial_cmp(&self[..], &other[..])
259    }
260}
261
262impl PartialEq<str> for StackCow<'_> {
263    #[inline]
264    fn eq(&self, other: &str) -> bool {
265        let s: &str = self.as_ref();
266        PartialEq::eq(s, other)
267    }
268}
269
270impl PartialEq<&str> for StackCow<'_> {
271    #[inline]
272    fn eq(&self, other: &&str) -> bool {
273        PartialEq::eq(&self[..], &other[..])
274    }
275}
276
277impl<'a> PartialEq<StackCow<'a>> for &str {
278    fn eq(&self, other: &StackCow<'a>) -> bool {
279        self.eq(&other.as_str())
280    }
281}
282
283impl FromIterator<char> for StackCow<'_> {
284    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
285        Self::Owned(StackString::from_iter(iter))
286    }
287}
288
289impl PartialOrd<str> for StackCow<'_> {
290    fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
291        self.as_str().partial_cmp(other)
292    }
293}
294
295impl PartialOrd<&str> for StackCow<'_> {
296    fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
297        self.as_str().partial_cmp(*other)
298    }
299}
300
301impl<'a> PartialOrd<StackCow<'a>> for &str {
302    fn partial_cmp(&self, other: &StackCow<'a>) -> Option<std::cmp::Ordering> {
303        self.partial_cmp(&other.as_str())
304    }
305}
306
307#[cfg(feature = "postgres_types")]
308impl<'a> FromSql<'a> for StackCow<'a> {
309    fn from_sql(
310        ty: &Type,
311        raw: &'a [u8],
312    ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
313        let s = <&'a str as FromSql>::from_sql(ty, raw)?;
314        Ok(s.into())
315    }
316
317    fn accepts(ty: &Type) -> bool {
318        <&'a str as FromSql>::accepts(ty)
319    }
320}
321
322#[cfg(feature = "postgres_types")]
323impl<'a> ToSql for StackCow<'a> {
324    fn to_sql(
325        &self,
326        ty: &Type,
327        out: &mut BytesMut,
328    ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>>
329    where
330        Self: Sized,
331    {
332        ToSql::to_sql(&self.as_str(), ty, out)
333    }
334
335    fn accepts(ty: &Type) -> bool
336    where
337        Self: Sized,
338    {
339        <String as ToSql>::accepts(ty)
340    }
341
342    fn to_sql_checked(
343        &self,
344        ty: &Type,
345        out: &mut BytesMut,
346    ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
347        self.as_str().to_sql_checked(ty, out)
348    }
349}
350
351#[cfg(feature = "utoipa_types")]
352impl<'a> PartialSchema for StackCow<'a> {
353    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
354        str::schema()
355    }
356}
357
358#[cfg(feature = "utoipa_types")]
359impl<'a> ToSchema for StackCow<'a> {
360    fn name() -> Cow<'static, str> {
361        str::name()
362    }
363}
364
365#[cfg(feature = "axum_types")]
366impl<'a> IntoResponse for StackCow<'a> {
367    fn into_response(self) -> axum::response::Response {
368        let s: String = self.into();
369        s.into_response()
370    }
371}
372
373#[cfg(feature = "axum_types")]
374impl<'a> From<StackCow<'a>> for Body {
375    fn from(value: StackCow<'a>) -> Self {
376        let s: String = value.into();
377        s.into()
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use rand::{rng as thread_rng, Rng};
384    use serde::Deserialize;
385
386    use crate::{StackCow, StackString};
387
388    #[test]
389    fn test_default() {
390        assert_eq!(StackCow::new(), StackCow::default());
391    }
392
393    #[test]
394    fn test_from_utf8() {
395        let mut rng = thread_rng();
396        let v: Vec<_> = (0..20).map(|_| rng.random::<u8>() & 0x7f).collect();
397        let s0 = String::from_utf8(v.clone()).unwrap();
398        let s1 = StackCow::from_utf8(v).unwrap();
399        assert_eq!(s0.as_str(), s1.as_str());
400
401        let v: Vec<_> = (0..20).map(|_| rng.random::<u8>()).collect();
402        let s0 = String::from_utf8(v.clone());
403        let s1 = StackCow::from_utf8(v);
404
405        match s0 {
406            Ok(s) => assert_eq!(s.as_str(), s1.unwrap().as_str()),
407            Err(e) => assert_eq!(e, s1.unwrap_err()),
408        }
409    }
410
411    #[test]
412    fn test_string_from_stack_cow() {
413        let s0 = StackCow::from("Hello there");
414        let s1: String = s0.clone().into();
415        assert_eq!(s0.as_str(), s1.as_str());
416    }
417
418    #[test]
419    fn test_stack_cow_from_string() {
420        let s0 = String::from("Hello there");
421        let s1: StackCow = s0.clone().into();
422        assert_eq!(s0.as_str(), s1.as_str());
423        let s1: StackCow = (&s0).into();
424        assert_eq!(s0.as_str(), s1.as_str());
425    }
426
427    #[test]
428    fn test_borrow() {
429        use std::borrow::Borrow;
430        let s = StackCow::from("Hello");
431        let st: &str = s.borrow();
432        assert_eq!(st, "Hello");
433    }
434
435    #[test]
436    fn test_as_ref() {
437        use std::path::Path;
438
439        let s = StackCow::from("Hello");
440        let st: &str = s.as_ref();
441        assert_eq!(st, s.as_str());
442        let bt: &[u8] = s.as_ref();
443        assert_eq!(bt, s.as_bytes());
444        let pt: &Path = s.as_ref();
445        assert_eq!(pt, Path::new("Hello"));
446    }
447
448    #[test]
449    fn test_from_str() {
450        let s = StackCow::from("Hello");
451        assert_eq!(s, StackCow::Borrowed("Hello"));
452    }
453
454    #[test]
455    fn test_partialeq_cow() {
456        use std::path::Path;
457        let p = Path::new("Hello");
458        let ps = p.to_string_lossy();
459        let s = StackCow::from("Hello");
460        assert_eq!(s, ps);
461        let p = Path::new("alpha");
462        let ps: StackCow<'_> = p.to_string_lossy().into();
463        let s = StackCow::from("beta");
464        assert!(s > ps);
465    }
466
467    #[test]
468    fn test_partial_eq_string() {
469        assert_eq!(StackCow::from("Hello"), String::from("Hello"));
470        assert_eq!(StackCow::from("Hello"), "Hello");
471        assert_eq!(&StackCow::from("Hello"), "Hello");
472        assert!(StackCow::from("alpha") < "beta");
473        assert!("beta" > StackCow::from("alpha"));
474    }
475
476    #[test]
477    fn test_from_iterator_char() {
478        let mut rng = thread_rng();
479        let v: Vec<char> = (0..20).map(|_| rng.random::<char>()).collect();
480        let s0: StackCow = v.iter().map(|x| *x).collect();
481        let s1: String = v.iter().map(|x| *x).collect();
482        assert_eq!(s0, s1);
483    }
484
485    #[test]
486    fn test_contains_stack_cow() {
487        let a: StackCow = "hey there".into();
488        let b: StackCow = "hey".into();
489        assert!(a.contains(b.as_str()));
490    }
491
492    #[test]
493    fn test_contains_char() {
494        let a: StackCow = "hey there".into();
495        assert!(a.contains(' '));
496    }
497
498    #[test]
499    fn test_equality() {
500        let s: StackCow = "hey".into();
501        assert_eq!(Some(&s).map(Into::into), Some("hey"));
502    }
503
504    #[cfg(feature = "postgres_types")]
505    use bytes::BytesMut;
506    #[cfg(feature = "postgres_types")]
507    use postgres_types::{FromSql, IsNull, ToSql, Type};
508
509    #[cfg(feature = "postgres_types")]
510    #[test]
511    fn test_from_sql() {
512        let raw = b"Hello There";
513        let t = Type::TEXT;
514        let s = StackCow::from_sql(&t, raw).unwrap();
515        assert_eq!(s, StackCow::from("Hello There"));
516
517        assert!(<StackCow as FromSql>::accepts(&t));
518    }
519
520    #[cfg(feature = "postgres_types")]
521    #[test]
522    fn test_to_sql() {
523        let s = StackCow::from("Hello There");
524        let t = Type::TEXT;
525        assert!(<StackCow as ToSql>::accepts(&t));
526        let mut buf = BytesMut::new();
527        match s.to_sql(&t, &mut buf).unwrap() {
528            IsNull::Yes => assert!(false),
529            IsNull::No => {}
530        }
531        assert_eq!(buf.as_ref(), b"Hello There");
532    }
533
534    #[test]
535    fn test_from_display() {
536        use std::fmt::Display;
537
538        struct Test {}
539
540        impl Display for Test {
541            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542                f.write_str("THIS IS A TEST")
543            }
544        }
545
546        let t = Test {};
547        let s = StackCow::from_display(t);
548        assert_eq!(s, StackCow::from(StackString::from("THIS IS A TEST")));
549    }
550
551    #[test]
552    fn test_from_utf8_lossy() {
553        let mut v = Vec::new();
554        v.extend_from_slice("this is a test".as_bytes());
555        v.push(0xff);
556        v.extend_from_slice("yes".as_bytes());
557        let s = StackCow::from_utf8_lossy(&v);
558        assert_eq!(s.len(), 20);
559        assert_eq!(s.is_owned(), true);
560        let s: StackString = s.into();
561        assert_eq!(s.len(), 20);
562        assert_eq!(s.is_inline(), true);
563    }
564
565    #[test]
566    fn test_serde() {
567        let s = StackCow::from("HELLO");
568        let t = "HELLO";
569        let s = serde_json::to_vec(&s).unwrap();
570        let t = serde_json::to_vec(t).unwrap();
571        assert_eq!(s, t);
572
573        let s = r#"{"a": "b"}"#;
574
575        #[derive(Deserialize)]
576        struct A<'a> {
577            a: StackCow<'a>,
578        }
579
580        #[derive(Deserialize)]
581        struct B {
582            a: String,
583        }
584
585        let a: A = serde_json::from_str(s).unwrap();
586        let b: B = serde_json::from_str(s).unwrap();
587        assert_eq!(a.a.as_str(), b.a.as_str());
588    }
589}