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 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 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 #[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 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#[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}