stack_string/
stack_string.rs

1use derive_more::{Deref, DerefMut, Display, From, Index, IndexMut, Into};
2use serde::{Deserialize, Serialize};
3use smartstring::alias::String as SmartString;
4use std::{
5    borrow::{Borrow, BorrowMut, Cow},
6    convert::Infallible,
7    ffi::OsStr,
8    fmt::{self, Write as FmtWrite},
9    iter::FromIterator,
10    path::Path,
11    str,
12    str::{FromStr, Utf8Error},
13    string::FromUtf8Error,
14};
15
16use crate::MAX_INLINE;
17
18#[cfg(feature = "postgres_types")]
19use bytes::BytesMut;
20#[cfg(feature = "postgres_types")]
21use postgres_types::{FromSql, IsNull, ToSql, Type};
22
23#[cfg(feature = "rweb-openapi")]
24use rweb::openapi::{
25    ComponentDescriptor, ComponentOrInlineSchema, Entity, ResponseEntity, Responses,
26};
27
28#[cfg(feature = "rweb-openapi")]
29use hyper::Body;
30
31#[cfg(feature = "async_graphql")]
32use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
33
34#[derive(
35    Display,
36    Serialize,
37    Deserialize,
38    Deref,
39    DerefMut,
40    Index,
41    IndexMut,
42    Debug,
43    Clone,
44    Into,
45    From,
46    PartialEq,
47    Eq,
48    Hash,
49    Default,
50    PartialOrd,
51    Ord,
52)]
53pub struct StackString(SmartString);
54
55impl StackString {
56    #[must_use]
57    pub fn new() -> Self {
58        Self(SmartString::new())
59    }
60
61    #[must_use]
62    pub fn as_str(&self) -> &str {
63        self.0.as_str()
64    }
65
66    #[must_use]
67    pub fn split_off(&mut self, index: usize) -> Self {
68        Self(self.0.split_off(index))
69    }
70
71    /// Construct a `StackString` from a `&[u8]`
72    /// # Errors
73    ///
74    /// Will return an Error if the byte slice is not utf8 compliant
75    pub fn from_utf8(v: &[u8]) -> Result<Self, Utf8Error> {
76        str::from_utf8(v).map(Into::into)
77    }
78
79    /// Construct a `StackString` from a `Vec<u8>`
80    /// # Errors
81    ///
82    /// Will return an Error if the byte slice is not utf8 compliant
83    pub fn from_utf8_vec(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
84        String::from_utf8(vec).map(Into::into)
85    }
86
87    #[must_use]
88    pub fn from_utf8_lossy(v: &[u8]) -> Self {
89        if v.len() > MAX_INLINE {
90            match String::from_utf8_lossy(v) {
91                Cow::Borrowed(s) => s.into(),
92                Cow::Owned(s) => s.into(),
93            }
94        } else {
95            let (v, up_to, error_len) = match str::from_utf8(v) {
96                Ok(s) => return s.into(),
97                Err(error) => (v, error.valid_up_to(), error.error_len()),
98            };
99            let mut buf = StackString::new();
100            let (valid, after_valid) = v.split_at(up_to);
101            buf.push_str(unsafe { str::from_utf8_unchecked(valid) });
102            buf.push('\u{FFFD}');
103            let mut input = after_valid;
104            if let Some(invalid_sequence_length) = error_len {
105                input = &after_valid[invalid_sequence_length..];
106            }
107            loop {
108                match str::from_utf8(input) {
109                    Ok(s) => {
110                        buf.push_str(s);
111                        break;
112                    }
113                    Err(error) => {
114                        let (valid, after_valid) = input.split_at(error.valid_up_to());
115                        buf.push_str(unsafe { str::from_utf8_unchecked(valid) });
116                        buf.push('\u{FFFD}');
117                        if let Some(invalid_sequence_length) = error.error_len() {
118                            input = &after_valid[invalid_sequence_length..];
119                        } else {
120                            break;
121                        }
122                    }
123                }
124            }
125            buf
126        }
127    }
128
129    /// # Panics
130    /// `from_display` panics if a formatting trait implementation returns an
131    /// error. This indicates an incorrect implementation
132    /// since `fmt::Write for String` never returns an error itself.
133    pub fn from_display(buf: impl fmt::Display) -> Self {
134        let mut s = Self::new();
135        write!(s, "{buf}").unwrap();
136        s
137    }
138}
139
140impl From<StackString> for String {
141    fn from(item: StackString) -> Self {
142        item.0.into()
143    }
144}
145
146impl From<&StackString> for String {
147    fn from(item: &StackString) -> Self {
148        item.as_str().into()
149    }
150}
151
152impl From<&StackString> for StackString {
153    fn from(item: &StackString) -> Self {
154        item.clone()
155    }
156}
157
158impl From<String> for StackString {
159    fn from(item: String) -> Self {
160        Self(item.into())
161    }
162}
163
164impl From<&String> for StackString {
165    fn from(item: &String) -> Self {
166        Self(item.into())
167    }
168}
169
170impl From<&str> for StackString {
171    fn from(item: &str) -> Self {
172        Self(item.into())
173    }
174}
175
176impl<'a> From<&'a StackString> for &'a str {
177    fn from(item: &StackString) -> &str {
178        item.as_str()
179    }
180}
181
182impl<'a> From<Cow<'a, str>> for StackString {
183    fn from(item: Cow<'a, str>) -> Self {
184        match item {
185            Cow::Borrowed(s) => s.into(),
186            Cow::Owned(s) => s.into(),
187        }
188    }
189}
190
191impl From<StackString> for Cow<'_, str> {
192    fn from(item: StackString) -> Self {
193        Cow::Owned(item.into())
194    }
195}
196
197impl Borrow<str> for StackString {
198    fn borrow(&self) -> &str {
199        self.0.borrow()
200    }
201}
202
203impl BorrowMut<str> for StackString {
204    fn borrow_mut(&mut self) -> &mut str {
205        self.0.borrow_mut()
206    }
207}
208
209impl AsRef<str> for StackString {
210    fn as_ref(&self) -> &str {
211        self.0.as_str()
212    }
213}
214
215impl AsRef<[u8]> for StackString {
216    fn as_ref(&self) -> &[u8] {
217        self.0.as_ref()
218    }
219}
220
221impl AsRef<OsStr> for StackString {
222    fn as_ref(&self) -> &OsStr {
223        self.as_str().as_ref()
224    }
225}
226
227impl AsRef<Path> for StackString {
228    fn as_ref(&self) -> &Path {
229        Path::new(self)
230    }
231}
232
233impl FromStr for StackString {
234    type Err = Infallible;
235    fn from_str(s: &str) -> Result<Self, Self::Err> {
236        Ok(s.into())
237    }
238}
239
240impl<'a> PartialEq<Cow<'a, str>> for StackString {
241    #[inline]
242    fn eq(&self, other: &Cow<'a, str>) -> bool {
243        PartialEq::eq(&self[..], &other[..])
244    }
245}
246
247impl PartialEq<String> for StackString {
248    #[inline]
249    fn eq(&self, other: &String) -> bool {
250        PartialEq::eq(&self[..], &other[..])
251    }
252}
253
254impl PartialEq<str> for StackString {
255    #[inline]
256    fn eq(&self, other: &str) -> bool {
257        PartialEq::eq(&self.0, other)
258    }
259}
260
261impl<'a> PartialEq<&'a str> for StackString {
262    #[inline]
263    fn eq(&self, other: &&'a str) -> bool {
264        PartialEq::eq(&self[..], &other[..])
265    }
266}
267
268impl FromIterator<char> for StackString {
269    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
270        let s = SmartString::from_iter(iter);
271        Self(s)
272    }
273}
274
275#[cfg(feature = "postgres_types")]
276impl<'a> FromSql<'a> for StackString {
277    fn from_sql(
278        ty: &Type,
279        raw: &'a [u8],
280    ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
281        let s = <&'a str as FromSql>::from_sql(ty, raw)?;
282        Ok(s.into())
283    }
284
285    fn accepts(ty: &Type) -> bool {
286        <&'a str as FromSql>::accepts(ty)
287    }
288}
289
290#[cfg(feature = "postgres_types")]
291impl ToSql for StackString {
292    fn to_sql(
293        &self,
294        ty: &Type,
295        out: &mut BytesMut,
296    ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>>
297    where
298        Self: Sized,
299    {
300        ToSql::to_sql(&self.as_str(), ty, out)
301    }
302
303    fn accepts(ty: &Type) -> bool
304    where
305        Self: Sized,
306    {
307        <String as ToSql>::accepts(ty)
308    }
309
310    fn to_sql_checked(
311        &self,
312        ty: &Type,
313        out: &mut BytesMut,
314    ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
315        self.as_str().to_sql_checked(ty, out)
316    }
317}
318
319#[cfg(feature = "rweb-openapi")]
320impl Entity for StackString {
321    fn type_name() -> Cow<'static, str> {
322        <str as Entity>::type_name()
323    }
324
325    #[inline]
326    fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
327        str::describe(comp_d)
328    }
329}
330
331#[cfg(feature = "rweb-openapi")]
332impl ResponseEntity for StackString {
333    #[inline]
334    fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
335        String::describe_responses(comp_d)
336    }
337}
338
339#[cfg(feature = "rweb-openapi")]
340impl From<StackString> for Body {
341    #[inline]
342    fn from(s: StackString) -> Body {
343        let s: String = s.into();
344        Body::from(s)
345    }
346}
347
348#[macro_export]
349macro_rules! format_sstr {
350    ($($arg:tt)*) => {{
351        use std::fmt::Write;
352        let mut buf = $crate::StackString::new();
353        std::write!(buf, "{}", std::format_args!($($arg)*)).unwrap();
354        buf
355    }}
356}
357
358/// Allow StackString to be used as graphql scalar value
359#[cfg(feature = "async_graphql")]
360#[Scalar]
361impl ScalarType for StackString {
362    fn parse(value: Value) -> InputValueResult<Self> {
363        if let Value::String(s) = value {
364            let s: StackString = s.into();
365            Ok(s)
366        } else {
367            Err(InputValueError::expected_type(value))
368        }
369    }
370
371    fn is_valid(value: &Value) -> bool {
372        matches!(value, Value::String(_))
373    }
374
375    fn to_value(&self) -> Value {
376        Value::String(self.to_string())
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use rand::{thread_rng, Rng};
383
384    use crate::StackString;
385
386    #[test]
387    fn test_default() {
388        assert_eq!(StackString::new(), StackString::default());
389    }
390
391    #[test]
392    fn test_split_off() {
393        let mut s0 = "hello there".to_string();
394        let s1 = s0.split_off(3);
395        let mut s2: StackString = "hello there".into();
396        let s3 = s2.split_off(3);
397        assert_eq!(s0.as_str(), s2.as_str());
398        assert_eq!(s1.as_str(), s3.as_str());
399    }
400
401    #[test]
402    fn test_from_utf8() {
403        let mut rng = thread_rng();
404        let v: Vec<_> = (0..20).map(|_| rng.gen::<u8>() & 0x7f).collect();
405        let s0 = String::from_utf8(v.clone()).unwrap();
406        let s1 = StackString::from_utf8(&v).unwrap();
407        assert_eq!(s0.as_str(), s1.as_str());
408
409        let v: Vec<_> = (0..20).map(|_| rng.gen::<u8>()).collect();
410        let s0 = String::from_utf8(v.clone());
411        let s1 = StackString::from_utf8(&v);
412
413        match s0 {
414            Ok(s) => assert_eq!(s.as_str(), s1.unwrap().as_str()),
415            Err(e) => assert_eq!(e.utf8_error(), s1.unwrap_err()),
416        }
417    }
418
419    #[test]
420    fn test_from_utf8_vec() {
421        let mut rng = thread_rng();
422        let v: Vec<_> = (0..20).map(|_| rng.gen::<u8>() & 0x7f).collect();
423        let s0 = String::from_utf8(v.clone()).unwrap();
424        let s1 = StackString::from_utf8_vec(v).unwrap();
425        assert_eq!(s0.as_str(), s1.as_str());
426
427        let v: Vec<_> = (0..20).map(|_| rng.gen::<u8>()).collect();
428        let s0 = String::from_utf8(v.clone());
429        let s1 = StackString::from_utf8_vec(v);
430
431        match s0 {
432            Ok(s) => assert_eq!(s.as_str(), s1.unwrap().as_str()),
433            Err(e) => assert_eq!(e, s1.unwrap_err()),
434        }
435    }
436
437    #[test]
438    fn test_string_from_stackstring() {
439        let s0 = StackString::from("Hello there");
440        let s1: String = s0.clone().into();
441        assert_eq!(s0.as_str(), s1.as_str());
442    }
443
444    #[test]
445    fn test_stackstring_from_string() {
446        let s0 = String::from("Hello there");
447        let s1: StackString = s0.clone().into();
448        assert_eq!(s0.as_str(), s1.as_str());
449        let s1: StackString = (&s0).into();
450        assert_eq!(s0.as_str(), s1.as_str());
451    }
452
453    #[test]
454    fn test_borrow() {
455        use std::borrow::Borrow;
456        let s = StackString::from("Hello");
457        let st: &str = s.borrow();
458        assert_eq!(st, "Hello");
459    }
460
461    #[test]
462    fn test_as_ref() {
463        use std::path::Path;
464
465        let s = StackString::from("Hello");
466        let st: &str = s.as_ref();
467        assert_eq!(st, s.as_str());
468        let bt: &[u8] = s.as_ref();
469        assert_eq!(bt, s.as_bytes());
470        let pt: &Path = s.as_ref();
471        assert_eq!(pt, Path::new("Hello"));
472    }
473
474    #[test]
475    fn test_from_str() {
476        let s = StackString::from("Hello");
477        let st: StackString = "Hello".parse().unwrap();
478        assert_eq!(s, st);
479    }
480
481    #[test]
482    fn test_partialeq_cow() {
483        use std::path::Path;
484        let p = Path::new("Hello");
485        let ps = p.to_string_lossy();
486        let s = StackString::from("Hello");
487        assert_eq!(s, ps);
488    }
489
490    #[test]
491    fn test_partial_eq_string() {
492        assert_eq!(StackString::from("Hello"), String::from("Hello"));
493        assert_eq!(StackString::from("Hello"), "Hello");
494        assert_eq!(&StackString::from("Hello"), "Hello");
495    }
496
497    #[test]
498    fn test_from_iterator_char() {
499        let mut rng = thread_rng();
500        let v: Vec<char> = (0..20).map(|_| rng.gen::<char>()).collect();
501        let s0: StackString = v.iter().map(|x| *x).collect();
502        let s1: String = v.iter().map(|x| *x).collect();
503        assert_eq!(s0, s1);
504    }
505
506    #[test]
507    fn test_contains_stackstring() {
508        let a: StackString = "hey there".into();
509        let b: StackString = "hey".into();
510        assert!(a.contains(b.as_str()));
511    }
512
513    #[test]
514    fn test_contains_char() {
515        let a: StackString = "hey there".into();
516        assert!(a.contains(' '));
517    }
518
519    #[test]
520    fn test_equality() {
521        let s: StackString = "hey".into();
522        assert_eq!(Some(&s).map(Into::into), Some("hey"));
523    }
524
525    #[cfg(feature = "postgres_types")]
526    use bytes::BytesMut;
527    #[cfg(feature = "postgres_types")]
528    use postgres_types::{FromSql, IsNull, ToSql, Type};
529
530    #[cfg(feature = "postgres_types")]
531    #[test]
532    fn test_from_sql() {
533        let raw = b"Hello There";
534        let t = Type::TEXT;
535        let s = StackString::from_sql(&t, raw).unwrap();
536        assert_eq!(s, StackString::from("Hello There"));
537
538        assert!(<StackString as FromSql>::accepts(&t));
539    }
540
541    #[cfg(feature = "postgres_types")]
542    #[test]
543    fn test_to_sql() {
544        let s = StackString::from("Hello There");
545        let t = Type::TEXT;
546        assert!(<StackString as ToSql>::accepts(&t));
547        let mut buf = BytesMut::new();
548        match s.to_sql(&t, &mut buf).unwrap() {
549            IsNull::Yes => assert!(false),
550            IsNull::No => {}
551        }
552        assert_eq!(buf.as_ref(), b"Hello There");
553    }
554
555    #[test]
556    fn test_from_display() {
557        use std::fmt::Display;
558
559        struct Test {}
560
561        impl Display for Test {
562            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563                f.write_str("THIS IS A TEST")
564            }
565        }
566
567        let t = Test {};
568        let s = StackString::from_display(t);
569        assert_eq!(s, StackString::from("THIS IS A TEST"));
570    }
571
572    #[test]
573    fn test_format_sstr() {
574        use crate::format_sstr;
575
576        let s = format_sstr!("This is a test {}", 22);
577        assert_eq!(s, StackString::from("This is a test 22"));
578    }
579
580    #[test]
581    fn test_from_utf8_lossy() {
582        let mut v = Vec::new();
583        v.extend_from_slice("this is a test".as_bytes());
584        v.push(0xff);
585        v.extend_from_slice("yes".as_bytes());
586        let s = StackString::from_utf8_lossy(&v);
587        assert_eq!(s.len(), 20);
588        assert_eq!(s.is_inline(), true);
589    }
590
591    #[test]
592    fn test_serde() {
593        use serde::Deserialize;
594
595        let s = StackString::from("HELLO");
596        let t = "HELLO";
597        let s = serde_json::to_vec(&s).unwrap();
598        let t = serde_json::to_vec(t).unwrap();
599        assert_eq!(s, t);
600
601        let s = r#"{"a": "b"}"#;
602
603        #[derive(Deserialize)]
604        struct A {
605            a: StackString,
606        }
607
608        #[derive(Deserialize)]
609        struct B {
610            a: String,
611        }
612
613        let a: A = serde_json::from_str(s).unwrap();
614        let b: B = serde_json::from_str(s).unwrap();
615        assert_eq!(a.a.as_str(), b.a.as_str());
616    }
617
618    #[cfg(feature = "async_graphql")]
619    #[test]
620    fn test_stackstring_async_graphql() {
621        use async_graphql::{
622            dataloader::{DataLoader, Loader},
623            Context, EmptyMutation, EmptySubscription, Object, Schema,
624        };
625        use async_trait::async_trait;
626        use std::{collections::HashMap, convert::Infallible};
627
628        struct StackStringLoader;
629
630        impl StackStringLoader {
631            fn new() -> Self {
632                Self
633            }
634        }
635
636        #[async_trait]
637        impl Loader<StackString> for StackStringLoader {
638            type Value = StackString;
639            type Error = Infallible;
640
641            async fn load(
642                &self,
643                _: &[StackString],
644            ) -> Result<HashMap<StackString, Self::Value>, Self::Error> {
645                let mut m = HashMap::new();
646                m.insert("HELLO".into(), "WORLD".into());
647                Ok(m)
648            }
649        }
650
651        struct QueryRoot;
652
653        #[Object]
654        impl<'a> QueryRoot {
655            async fn hello(&self, ctx: &Context<'a>) -> Result<Option<StackString>, Infallible> {
656                let hello = ctx
657                    .data::<DataLoader<StackStringLoader>>()
658                    .unwrap()
659                    .load_one("hello".into())
660                    .await
661                    .unwrap();
662                Ok(hello)
663            }
664        }
665
666        let expected_sdl = include_str!("../tests/data/sdl_file_stackstring.txt");
667
668        let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
669            .data(DataLoader::new(
670                StackStringLoader::new(),
671                tokio::task::spawn,
672            ))
673            .finish();
674        let sdl = schema.sdl();
675
676        assert_eq!(&sdl, expected_sdl);
677    }
678}