stack_string/
small_string.rs

1use arrayvec::ArrayString;
2use core::marker::PhantomData;
3use serde::{
4    de::{Error, Unexpected, Visitor},
5    Deserialize, Deserializer, Serialize, Serializer,
6};
7use std::{
8    borrow::{Borrow, BorrowMut, Cow},
9    convert::Infallible,
10    ffi::OsStr,
11    fmt,
12    fmt::Write as FmtWrite,
13    iter::FromIterator,
14    mem,
15    ops::{Deref, DerefMut},
16    path::Path,
17    str,
18    str::{FromStr, Utf8Error},
19    string::FromUtf8Error,
20};
21
22#[cfg(feature = "postgres_types")]
23use bytes::BytesMut;
24#[cfg(feature = "postgres_types")]
25use postgres_types::{FromSql, IsNull, ToSql, Type};
26
27#[cfg(feature = "rweb-openapi")]
28use rweb::openapi::{
29    ComponentDescriptor, ComponentOrInlineSchema, Entity, ResponseEntity, Responses,
30};
31
32#[cfg(feature = "rweb-openapi")]
33use hyper::Body;
34
35#[cfg(feature = "async_graphql")]
36use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
37
38use crate::{StackString, MAX_INLINE};
39
40#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
41pub enum SmallString<const CAP: usize> {
42    Inline(ArrayString<CAP>),
43    Boxed(String),
44}
45
46impl<const CAP: usize> Default for SmallString<CAP> {
47    fn default() -> Self {
48        Self::Inline(ArrayString::new())
49    }
50}
51
52impl<const CAP: usize> SmallString<CAP> {
53    #[inline]
54    #[must_use]
55    pub fn new() -> Self {
56        Self::Inline(ArrayString::new())
57    }
58
59    #[inline]
60    #[must_use]
61    pub fn is_inline(&self) -> bool {
62        match self {
63            Self::Inline(_) => true,
64            Self::Boxed(_) => false,
65        }
66    }
67
68    #[inline]
69    #[must_use]
70    pub fn is_boxed(&self) -> bool {
71        !self.is_inline()
72    }
73
74    #[inline]
75    #[must_use]
76    pub fn as_str(&self) -> &str {
77        match self {
78            Self::Inline(s) => s.as_str(),
79            Self::Boxed(s) => s.as_str(),
80        }
81    }
82
83    #[inline]
84    pub fn as_mut_str(&mut self) -> &mut str {
85        match self {
86            Self::Inline(s) => s.as_mut_str(),
87            Self::Boxed(s) => s.as_mut_str(),
88        }
89    }
90
91    /// Construct a `SmallString` from a `&[u8]`
92    /// # Errors
93    ///
94    /// Will return an Error if the byte slice is not utf8 compliant
95    pub fn from_utf8(v: &[u8]) -> Result<Self, Utf8Error> {
96        str::from_utf8(v)
97            .map(|s| ArrayString::from(s).map_or_else(|_| Self::Boxed(s.into()), Self::Inline))
98    }
99
100    /// Construct a `SmallString` from a `Vec<u8>`
101    /// # Errors
102    ///
103    /// Will return an Error if the `Vec<u8>` not utf8 compliant
104    pub fn from_utf8_vec(v: Vec<u8>) -> Result<Self, FromUtf8Error> {
105        String::from_utf8(v).map(|s| {
106            if s.len() > CAP {
107                Self::Boxed(s)
108            } else {
109                let mut astr = ArrayString::new();
110                astr.push_str(s.as_str());
111                Self::Inline(astr)
112            }
113        })
114    }
115
116    #[must_use]
117    pub fn from_utf8_lossy(v: &[u8]) -> Self {
118        if v.len() > CAP {
119            match String::from_utf8_lossy(v) {
120                Cow::Borrowed(s) => s.into(),
121                Cow::Owned(s) => s.into(),
122            }
123        } else {
124            let (v, up_to, error_len) = match str::from_utf8(v) {
125                Ok(s) => return s.into(),
126                Err(error) => (v, error.valid_up_to(), error.error_len()),
127            };
128            let mut buf = ArrayString::new();
129            let (valid, after_valid) = v.split_at(up_to);
130            buf.push_str(unsafe { str::from_utf8_unchecked(valid) });
131            buf.push('\u{FFFD}');
132            let mut input = after_valid;
133            if let Some(invalid_sequence_length) = error_len {
134                input = &after_valid[invalid_sequence_length..];
135            }
136            loop {
137                match str::from_utf8(input) {
138                    Ok(s) => {
139                        buf.push_str(s);
140                        break;
141                    }
142                    Err(error) => {
143                        let (valid, after_valid) = input.split_at(error.valid_up_to());
144                        buf.push_str(unsafe { str::from_utf8_unchecked(valid) });
145                        buf.push('\u{FFFD}');
146                        if let Some(invalid_sequence_length) = error.error_len() {
147                            input = &after_valid[invalid_sequence_length..];
148                        } else {
149                            break;
150                        }
151                    }
152                }
153            }
154            buf.into()
155        }
156    }
157
158    pub fn push_str(&mut self, s: &str) {
159        match self {
160            Self::Inline(a) => {
161                if a.try_push_str(s).is_err() {
162                    let mut buf: String = a.as_str().into();
163                    buf.push_str(s);
164                    let mut new_a = Self::Boxed(buf);
165                    mem::swap(self, &mut new_a);
166                }
167            }
168            Self::Boxed(a) => a.push_str(s),
169        }
170    }
171
172    /// Split the string into two at the given index.
173    ///
174    /// Returns the content to the right of the index as a new string, and
175    /// removes it from the original.
176    ///
177    /// If the index doesn't fall on a UTF-8 character boundary, this method
178    /// panics.
179    #[allow(clippy::missing_panics_doc)]
180    #[must_use]
181    pub fn split_off(&mut self, index: usize) -> Self {
182        match self {
183            Self::Boxed(s) => s.split_off(index).into(),
184            Self::Inline(s) => {
185                let st = s.as_str();
186                assert!(st.is_char_boundary(index));
187                let result = st[index..].into();
188                s.truncate(index);
189                result
190            }
191        }
192    }
193
194    /// # Panics
195    /// `from_display` panics if a formatting trait implementation returns an
196    /// error. This indicates an incorrect implementation
197    /// since `fmt::Write for String` never returns an error itself.
198    pub fn from_display(buf: impl fmt::Display) -> Self {
199        let mut s = Self::new();
200        write!(s, "{buf}").unwrap();
201        s
202    }
203
204    #[must_use]
205    pub fn into_smallstring<const CAP1: usize>(self) -> SmallString<CAP1> {
206        if self.len() > CAP1 {
207            match self {
208                SmallString::Boxed(s) => SmallString::Boxed(s),
209                SmallString::Inline(s) => s.as_str().into(),
210            }
211        } else {
212            self.as_str().into()
213        }
214    }
215}
216
217impl<const CAP: usize> From<&str> for SmallString<CAP> {
218    fn from(item: &str) -> Self {
219        ArrayString::from(item).map_or_else(|e| Self::Boxed(e.element().into()), Self::Inline)
220    }
221}
222
223impl<const CAP: usize> Serialize for SmallString<CAP> {
224    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
225    where
226        S: Serializer,
227    {
228        serializer.serialize_str(self.as_str())
229    }
230}
231
232impl<'de, const CAP: usize> Deserialize<'de> for SmallString<CAP> {
233    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
234    where
235        D: Deserializer<'de>,
236    {
237        deserializer
238            .deserialize_string(SmartStringVisitor(PhantomData))
239            .map(SmallString::from)
240    }
241}
242
243struct SmartStringVisitor<const CAP: usize>(PhantomData<*const SmallString<CAP>>);
244
245impl<'de, const CAP: usize> Visitor<'de> for SmartStringVisitor<CAP> {
246    type Value = SmallString<CAP>;
247
248    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
249        formatter.write_str("a string")
250    }
251
252    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
253    where
254        E: Error,
255    {
256        Ok(SmallString::from(v))
257    }
258
259    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
260    where
261        E: Error,
262    {
263        Ok(SmallString::from(v))
264    }
265
266    fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
267    where
268        E: Error,
269    {
270        match str::from_utf8(v) {
271            Ok(s) => Ok(s.into()),
272            Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
273        }
274    }
275
276    fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
277    where
278        E: Error,
279    {
280        match String::from_utf8(v) {
281            Ok(s) => Ok(s.into()),
282            Err(e) => Err(Error::invalid_value(
283                Unexpected::Bytes(&e.into_bytes()),
284                &self,
285            )),
286        }
287    }
288}
289
290impl<const CAP: usize> From<String> for SmallString<CAP> {
291    fn from(item: String) -> Self {
292        if item.len() > CAP {
293            Self::Boxed(item)
294        } else {
295            SmallString::from(item.as_str())
296        }
297    }
298}
299
300impl<const CAP: usize> From<&String> for SmallString<CAP> {
301    fn from(item: &String) -> Self {
302        item.as_str().into()
303    }
304}
305
306impl<const CAP: usize> From<ArrayString<CAP>> for SmallString<CAP> {
307    fn from(item: ArrayString<CAP>) -> Self {
308        Self::Inline(item)
309    }
310}
311
312impl<const CAP: usize> From<SmallString<CAP>> for String {
313    fn from(item: SmallString<CAP>) -> Self {
314        match item {
315            SmallString::Inline(s) => s.to_string(),
316            SmallString::Boxed(s) => s,
317        }
318    }
319}
320
321impl<const CAP: usize> From<&SmallString<CAP>> for String {
322    fn from(item: &SmallString<CAP>) -> Self {
323        item.to_string()
324    }
325}
326
327impl<const CAP: usize> From<&SmallString<CAP>> for SmallString<CAP> {
328    fn from(item: &SmallString<CAP>) -> Self {
329        item.clone()
330    }
331}
332
333impl<'a, const CAP: usize> From<&'a SmallString<CAP>> for &'a str {
334    fn from(item: &SmallString<CAP>) -> &str {
335        item.as_str()
336    }
337}
338
339impl<'a, const CAP: usize> From<Cow<'a, str>> for SmallString<CAP> {
340    fn from(item: Cow<'a, str>) -> Self {
341        match item {
342            Cow::Borrowed(s) => s.into(),
343            Cow::Owned(s) => s.into(),
344        }
345    }
346}
347
348impl<const CAP: usize> From<SmallString<CAP>> for Cow<'_, str> {
349    fn from(item: SmallString<CAP>) -> Self {
350        Cow::Owned(item.into())
351    }
352}
353
354impl<const CAP: usize> From<StackString> for SmallString<CAP> {
355    fn from(item: StackString) -> Self {
356        if item.len() > CAP {
357            let s: String = item.into();
358            Self::Boxed(s)
359        } else {
360            Self::Inline(ArrayString::from(item.as_str()).unwrap())
361        }
362    }
363}
364
365impl<const CAP: usize> From<&StackString> for SmallString<CAP> {
366    fn from(item: &StackString) -> Self {
367        SmallString::from(item.as_str())
368    }
369}
370
371impl<const CAP: usize> From<SmallString<CAP>> for StackString {
372    fn from(item: SmallString<CAP>) -> Self {
373        if item.len() > MAX_INLINE {
374            let s: String = item.into();
375            s.into()
376        } else {
377            StackString::from(item.as_str())
378        }
379    }
380}
381
382impl<const CAP: usize> Borrow<str> for SmallString<CAP> {
383    fn borrow(&self) -> &str {
384        self.as_str()
385    }
386}
387
388impl<const CAP: usize> BorrowMut<str> for SmallString<CAP> {
389    fn borrow_mut(&mut self) -> &mut str {
390        self.as_mut_str()
391    }
392}
393
394impl<const CAP: usize> fmt::Display for SmallString<CAP> {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
396        fmt::Display::fmt(self.as_str(), f)
397    }
398}
399
400impl<const CAP: usize> fmt::Write for SmallString<CAP> {
401    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
402        self.push_str(s);
403        Ok(())
404    }
405}
406
407impl<const CAP: usize> AsRef<str> for SmallString<CAP> {
408    fn as_ref(&self) -> &str {
409        self.as_str()
410    }
411}
412
413impl<const CAP: usize> AsRef<[u8]> for SmallString<CAP> {
414    fn as_ref(&self) -> &[u8] {
415        self.as_str().as_ref()
416    }
417}
418
419impl<const CAP: usize> AsRef<OsStr> for SmallString<CAP> {
420    fn as_ref(&self) -> &OsStr {
421        self.as_str().as_ref()
422    }
423}
424
425impl<const CAP: usize> AsRef<Path> for SmallString<CAP> {
426    fn as_ref(&self) -> &Path {
427        Path::new(self)
428    }
429}
430
431impl<const CAP: usize> FromStr for SmallString<CAP> {
432    type Err = Infallible;
433    fn from_str(s: &str) -> Result<Self, Self::Err> {
434        Ok(s.into())
435    }
436}
437
438impl<const CAP: usize> Deref for SmallString<CAP> {
439    type Target = str;
440    fn deref(&self) -> &Self::Target {
441        self.as_str()
442    }
443}
444
445impl<const CAP: usize> DerefMut for SmallString<CAP> {
446    fn deref_mut(&mut self) -> &mut Self::Target {
447        self.as_mut_str()
448    }
449}
450
451impl<'a, const CAP: usize> PartialEq<Cow<'a, str>> for SmallString<CAP> {
452    #[inline]
453    fn eq(&self, other: &Cow<'a, str>) -> bool {
454        PartialEq::eq(&self[..], &other[..])
455    }
456}
457
458impl<const CAP: usize> PartialEq<String> for SmallString<CAP> {
459    #[inline]
460    fn eq(&self, other: &String) -> bool {
461        PartialEq::eq(&self[..], &other[..])
462    }
463}
464
465impl<const CAP: usize> PartialEq<str> for SmallString<CAP> {
466    #[inline]
467    fn eq(&self, other: &str) -> bool {
468        PartialEq::eq(self.as_str(), other)
469    }
470}
471
472impl<const CAP: usize> PartialEq<&str> for SmallString<CAP> {
473    #[inline]
474    fn eq(&self, other: &&str) -> bool {
475        PartialEq::eq(&self.as_str(), other)
476    }
477}
478
479#[cfg(feature = "postgres_types")]
480impl<'a, const CAP: usize> FromSql<'a> for SmallString<CAP> {
481    fn from_sql(
482        ty: &Type,
483        raw: &'a [u8],
484    ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
485        let s = <&'a str as FromSql>::from_sql(ty, raw)?;
486        Ok(s.into())
487    }
488
489    fn accepts(ty: &Type) -> bool {
490        <&'a str as FromSql>::accepts(ty)
491    }
492}
493
494#[cfg(feature = "postgres_types")]
495impl<const CAP: usize> ToSql for SmallString<CAP> {
496    fn to_sql(
497        &self,
498        ty: &Type,
499        out: &mut BytesMut,
500    ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>>
501    where
502        Self: Sized,
503    {
504        ToSql::to_sql(&self.as_str(), ty, out)
505    }
506
507    fn accepts(ty: &Type) -> bool
508    where
509        Self: Sized,
510    {
511        <String as ToSql>::accepts(ty)
512    }
513
514    fn to_sql_checked(
515        &self,
516        ty: &Type,
517        out: &mut BytesMut,
518    ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
519        self.as_str().to_sql_checked(ty, out)
520    }
521}
522
523#[cfg(feature = "rweb-openapi")]
524impl<const CAP: usize> Entity for SmallString<CAP> {
525    fn type_name() -> Cow<'static, str> {
526        str::type_name()
527    }
528
529    #[inline]
530    fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
531        str::describe(comp_d)
532    }
533}
534
535#[cfg(feature = "rweb-openapi")]
536impl<const CAP: usize> ResponseEntity for SmallString<CAP> {
537    #[inline]
538    fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
539        String::describe_responses(comp_d)
540    }
541}
542
543#[cfg(feature = "rweb-openapi")]
544impl<const CAP: usize> From<SmallString<CAP>> for Body {
545    #[inline]
546    fn from(s: SmallString<CAP>) -> Body {
547        let s: String = s.into();
548        Body::from(s)
549    }
550}
551
552impl<const CAP: usize> FromIterator<char> for SmallString<CAP> {
553    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
554        let iter = iter.into_iter();
555        let (min, max) = iter.size_hint();
556        let size = if let Some(x) = max { x } else { min };
557        let mut s = if size > CAP {
558            Self::Boxed(String::with_capacity(size))
559        } else {
560            Self::Inline(ArrayString::<CAP>::new())
561        };
562        for c in iter {
563            s.write_char(c).unwrap();
564        }
565        s
566    }
567}
568
569/// Allow SmallString to be used as graphql scalar value
570#[cfg(feature = "async_graphql")]
571#[Scalar]
572impl<const CAP: usize> ScalarType for SmallString<CAP> {
573    fn parse(value: Value) -> InputValueResult<Self> {
574        if let Value::String(s) = value {
575            let s: Self = s.into();
576            Ok(s)
577        } else {
578            Err(InputValueError::expected_type(value))
579        }
580    }
581
582    fn is_valid(value: &Value) -> bool {
583        matches!(value, Value::String(_))
584    }
585
586    fn to_value(&self) -> Value {
587        Value::String(self.to_string())
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use arrayvec::ArrayString;
594    use rand::{thread_rng, Rng};
595    use std::fmt::Write;
596
597    use crate::{small_string::SmallString, stack_string::StackString};
598
599    #[test]
600    fn test_default() {
601        assert_eq!(SmallString::<1>::new(), SmallString::<1>::default());
602    }
603
604    #[test]
605    fn test_sizeof() {
606        if std::mem::size_of::<String>() == 24 {
607            assert_eq!(std::mem::size_of::<StackString>(), 24);
608            assert_eq!(std::mem::size_of::<SmallString<32>>(), 40);
609            assert_eq!(std::mem::size_of::<SmallString<30>>(), 40);
610            assert_eq!(std::mem::size_of::<ArrayString<32>>(), 36);
611            assert_eq!(std::mem::size_of::<[u8; 32]>(), 32);
612        } else {
613            assert!(false);
614        }
615    }
616
617    #[test]
618    fn test_small_string_split_off() {
619        let mut s0 = "hello there".to_string();
620        let s1 = s0.split_off(3);
621        let mut s2: SmallString<20> = "hello there".into();
622        let s3 = s2.split_off(3);
623        assert_eq!(s0.as_str(), s2.as_str());
624        assert_eq!(s1.as_str(), s3.as_str());
625        assert!(s2.is_inline());
626        assert!(s3.is_inline());
627    }
628
629    #[test]
630    fn test_from_utf8() {
631        let mut rng = thread_rng();
632        let v: Vec<_> = (0..20).map(|_| rng.gen::<u8>() & 0x7f).collect();
633        let s0 = std::str::from_utf8(&v).unwrap();
634        let s1 = SmallString::<20>::from_utf8(&v).unwrap();
635        assert_eq!(s0, s1.as_str());
636        assert!(s1.is_inline());
637
638        let v: Vec<_> = (0..20).map(|_| rng.gen::<u8>()).collect();
639        let s0 = std::str::from_utf8(&v);
640        let s1 = SmallString::<20>::from_utf8(&v);
641
642        match s0 {
643            Ok(s) => assert_eq!(s, s1.unwrap().as_str()),
644            Err(e) => assert_eq!(e, s1.unwrap_err()),
645        }
646    }
647
648    #[test]
649    fn test_string_from_smallstring() {
650        let s0 = SmallString::<20>::from("Hello there");
651        let s1: String = s0.clone().into();
652        assert_eq!(s0.as_str(), s1.as_str());
653    }
654
655    #[test]
656    fn test_smallstring_from_string() {
657        let s0 = String::from("Hello there");
658        let s1: SmallString<20> = s0.clone().into();
659        assert_eq!(s0.as_str(), s1.as_str());
660        let s1: SmallString<20> = (&s0).into();
661        assert_eq!(s0.as_str(), s1.as_str());
662    }
663
664    #[test]
665    fn test_borrow() {
666        use std::borrow::Borrow;
667        let s = SmallString::<20>::from("Hello");
668        let st: &str = s.borrow();
669        assert_eq!(st, "Hello");
670    }
671
672    #[test]
673    fn test_as_ref() {
674        use std::path::Path;
675        let s = SmallString::<20>::from("Hello");
676        let st: &str = s.as_ref();
677        assert_eq!(st, s.as_str());
678        let bt: &[u8] = s.as_ref();
679        assert_eq!(bt, s.as_bytes());
680        let pt: &Path = s.as_ref();
681        assert_eq!(pt, Path::new("Hello"));
682    }
683
684    #[test]
685    fn test_from_str() {
686        let s = SmallString::<20>::from("Hello");
687        let st: SmallString<20> = "Hello".parse().unwrap();
688        assert_eq!(s, st);
689    }
690
691    #[test]
692    fn test_partialeq_cow() {
693        use std::path::Path;
694        let p = Path::new("Hello");
695        let ps = p.to_string_lossy();
696        let s = SmallString::<20>::from("Hello");
697        assert_eq!(s, ps);
698    }
699
700    #[test]
701    fn test_partial_eq_string() {
702        assert_eq!(SmallString::<20>::from("Hello"), String::from("Hello"));
703        assert_eq!(SmallString::<20>::from("Hello"), "Hello");
704        assert_eq!(&SmallString::<20>::from("Hello"), "Hello");
705    }
706
707    #[test]
708    fn test_from_iterator_char() {
709        let mut rng = thread_rng();
710        let v: Vec<char> = (0..20).map(|_| rng.gen::<char>()).collect();
711        let s0: SmallString<20> = v.iter().map(|x| *x).collect();
712        let s1: String = v.iter().map(|x| *x).collect();
713        assert_eq!(s0, s1);
714    }
715
716    #[test]
717    fn test_contains_smallstring() {
718        let a: SmallString<20> = "hey there".into();
719        let b: SmallString<20> = "hey".into();
720        assert!(a.contains(b.as_str()));
721    }
722
723    #[test]
724    fn test_contains_char() {
725        let a: SmallString<20> = "hey there".into();
726        assert!(a.contains(' '));
727    }
728
729    #[test]
730    fn test_equality() {
731        let s: SmallString<20> = "hey".into();
732        assert_eq!(Some(&s).map(Into::into), Some("hey"));
733    }
734
735    #[cfg(feature = "postgres_types")]
736    use bytes::BytesMut;
737    #[cfg(feature = "postgres_types")]
738    use postgres_types::{FromSql, IsNull, ToSql, Type};
739
740    #[cfg(feature = "postgres_types")]
741    #[test]
742    fn test_from_sql() {
743        let raw = b"Hello There";
744        let t = Type::TEXT;
745        let s = SmallString::<20>::from_sql(&t, raw).unwrap();
746        assert_eq!(s, SmallString::<20>::from("Hello There"));
747
748        assert!(<SmallString<20> as FromSql>::accepts(&t));
749    }
750
751    #[cfg(feature = "postgres_types")]
752    #[test]
753    fn test_to_sql() {
754        let s = SmallString::<20>::from("Hello There");
755        let t = Type::TEXT;
756        assert!(<SmallString<20> as ToSql>::accepts(&t));
757        let mut buf = BytesMut::new();
758        match s.to_sql(&t, &mut buf).unwrap() {
759            IsNull::Yes => assert!(false),
760            IsNull::No => {}
761        }
762        assert_eq!(buf.as_ref(), b"Hello There");
763    }
764
765    #[test]
766    fn test_from_display() {
767        use std::fmt::Display;
768
769        struct Test {}
770
771        impl Display for Test {
772            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
773                f.write_str("THIS IS A TEST")
774            }
775        }
776
777        let t = Test {};
778        let s = SmallString::<20>::from_display(t);
779        assert_eq!(s, SmallString::<20>::from("THIS IS A TEST"));
780    }
781
782    #[test]
783    fn test_write_smallstring() {
784        let mut s = SmallString::<5>::new();
785        write!(&mut s, "12345").unwrap();
786        assert_eq!(s.as_str(), "12345");
787        assert!(s.is_inline());
788
789        let mut s = SmallString::<5>::new();
790        write!(&mut s, "123456789").unwrap();
791        assert_eq!(s.as_str(), "123456789");
792        assert!(s.is_boxed());
793    }
794
795    #[test]
796    fn test_into_smallstring() {
797        let mut s = SmallString::<10>::new();
798        write!(&mut s, "123456789").unwrap();
799        assert!(s.is_inline());
800        let s = s.into_smallstring::<20>();
801        assert!(s.is_inline());
802        let s = s.into_smallstring::<5>();
803        assert!(s.is_boxed());
804    }
805
806    #[test]
807    fn test_serde() {
808        use serde::Deserialize;
809
810        let s = SmallString::<30>::from("HELLO");
811        let t = "HELLO";
812        let s = serde_json::to_vec(&s).unwrap();
813        let t = serde_json::to_vec(t).unwrap();
814        assert_eq!(s, t);
815
816        let s = r#"{"a": "b"}"#;
817
818        #[derive(Deserialize)]
819        struct A {
820            a: SmallString<30>,
821        }
822
823        #[derive(Deserialize)]
824        struct B {
825            a: String,
826        }
827
828        let a: A = serde_json::from_str(s).unwrap();
829        let b: B = serde_json::from_str(s).unwrap();
830        assert_eq!(a.a.as_str(), b.a.as_str());
831    }
832
833    #[cfg(feature = "async_graphql")]
834    #[test]
835    fn test_smallstring_async_graphql() {
836        use async_graphql::{
837            dataloader::{DataLoader, Loader},
838            Context, EmptyMutation, EmptySubscription, Object, Schema,
839        };
840        use async_trait::async_trait;
841        use std::{collections::HashMap, convert::Infallible};
842
843        struct SmallStringLoader;
844
845        impl SmallStringLoader {
846            fn new() -> Self {
847                Self
848            }
849        }
850
851        #[async_trait]
852        impl<const CAP: usize> Loader<SmallString<CAP>> for SmallStringLoader {
853            type Value = SmallString<CAP>;
854            type Error = Infallible;
855
856            async fn load(
857                &self,
858                _: &[SmallString<CAP>],
859            ) -> Result<HashMap<SmallString<CAP>, Self::Value>, Self::Error> {
860                let mut m = HashMap::new();
861                m.insert("HELLO".into(), "WORLD".into());
862                Ok(m)
863            }
864        }
865
866        struct QueryRoot<const CAP: usize>;
867
868        #[Object]
869        impl<const CAP: usize, 'a> QueryRoot<CAP> {
870            async fn hello(
871                &self,
872                ctx: &Context<'a>,
873            ) -> Result<Option<SmallString<CAP>>, Infallible> {
874                let hello = ctx
875                    .data::<DataLoader<SmallStringLoader>>()
876                    .unwrap()
877                    .load_one("hello".into())
878                    .await
879                    .unwrap();
880                Ok(hello)
881            }
882        }
883
884        let expected_sdl = include_str!("../tests/data/sdl_file_smallstring.txt");
885
886        let schema = Schema::build(QueryRoot::<5>, EmptyMutation, EmptySubscription)
887            .data(DataLoader::new(
888                SmallStringLoader::new(),
889                tokio::task::spawn,
890            ))
891            .finish();
892        let sdl = schema.sdl();
893        assert_eq!(&sdl, expected_sdl);
894    }
895}