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