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