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