1use alloc::borrow::Cow;
2use alloc::string::{ParseError, String, ToString};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5use core::borrow::Borrow;
6use core::cmp::Ordering;
7use core::fmt::{self, Display};
8use core::hash::{Hash, Hasher};
9use core::ops::{Add, AddAssign, Deref};
10use core::str::{self, FromStr, Utf8Error};
11
12mod pattern;
13mod repr;
14
15use crate::inline::InlineStr;
16use pattern::StrPatternImpl;
17pub use pattern::{SplitPattern, SplitStr, SplitWrapper, StrPattern};
18use repr::{InnerString, INLINE_CAPACITY};
19
20#[derive(Clone)]
21#[repr(transparent)]
22pub struct CheetahString {
23 inner: InnerString,
24}
25
26impl Default for CheetahString {
27 fn default() -> Self {
28 CheetahString {
29 inner: InnerString::Inline(InlineStr::empty()),
30 }
31 }
32}
33
34impl From<String> for CheetahString {
35 #[inline]
36 fn from(s: String) -> Self {
37 CheetahString::from_string(s)
38 }
39}
40
41impl From<Arc<String>> for CheetahString {
42 #[inline]
43 fn from(s: Arc<String>) -> Self {
44 CheetahString::from_arc_string(s)
45 }
46}
47
48impl<'a> From<&'a str> for CheetahString {
49 #[inline]
50 fn from(s: &'a str) -> Self {
51 CheetahString::from_slice(s)
52 }
53}
54
55impl<'a> TryFrom<&'a [u8]> for CheetahString {
56 type Error = Utf8Error;
57
58 #[inline]
59 fn try_from(b: &'a [u8]) -> Result<Self, Self::Error> {
60 CheetahString::try_from_bytes(b)
61 }
62}
63
64impl FromStr for CheetahString {
65 type Err = ParseError;
66 #[inline]
67 fn from_str(s: &str) -> Result<Self, Self::Err> {
68 Ok(CheetahString::from_slice(s))
69 }
70}
71
72impl TryFrom<Vec<u8>> for CheetahString {
73 type Error = Utf8Error;
74
75 #[inline]
76 fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
77 CheetahString::try_from_vec(v)
78 }
79}
80
81impl From<Cow<'static, str>> for CheetahString {
82 #[inline]
83 fn from(cow: Cow<'static, str>) -> Self {
84 match cow {
85 Cow::Borrowed(s) => CheetahString::from_static_str(s),
86 Cow::Owned(s) => CheetahString::from_string(s),
87 }
88 }
89}
90
91impl From<Cow<'_, String>> for CheetahString {
92 #[inline]
93 fn from(cow: Cow<'_, String>) -> Self {
94 match cow {
95 Cow::Borrowed(s) => CheetahString::from_slice(s),
96 Cow::Owned(s) => CheetahString::from_string(s),
97 }
98 }
99}
100
101impl From<char> for CheetahString {
102 #[inline]
112 fn from(c: char) -> Self {
113 CheetahString::from_string(c.to_string())
114 }
115}
116
117impl<'a> FromIterator<&'a char> for CheetahString {
118 #[inline]
119 fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> CheetahString {
120 let mut buf = String::new();
121 buf.extend(iter);
122 CheetahString::from_string(buf)
123 }
124}
125
126impl<'a> FromIterator<&'a str> for CheetahString {
127 fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> CheetahString {
128 let mut buf = String::new();
129 buf.extend(iter);
130 CheetahString::from_string(buf)
131 }
132}
133
134impl FromIterator<String> for CheetahString {
135 #[inline]
136 fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
137 let mut buf = String::new();
138 buf.extend(iter);
139 CheetahString::from_string(buf)
140 }
141}
142
143impl<'a> FromIterator<&'a String> for CheetahString {
144 #[inline]
145 fn from_iter<T: IntoIterator<Item = &'a String>>(iter: T) -> Self {
146 let mut buf = String::new();
147 buf.extend(iter.into_iter().map(|s| s.as_str()));
148 CheetahString::from_string(buf)
149 }
150}
151
152#[cfg(feature = "bytes")]
153impl TryFrom<bytes::Bytes> for CheetahString {
154 type Error = Utf8Error;
155
156 #[inline]
157 fn try_from(b: bytes::Bytes) -> Result<Self, Self::Error> {
158 CheetahString::try_from_bytes_buf(b)
159 }
160}
161
162impl From<&CheetahString> for CheetahString {
163 #[inline]
164 fn from(s: &CheetahString) -> Self {
165 s.clone()
166 }
167}
168
169impl From<CheetahString> for String {
170 #[inline]
171 fn from(s: CheetahString) -> Self {
172 match s {
173 CheetahString {
174 inner: InnerString::Inline(inline),
175 } => inline.into_string(),
176 CheetahString {
177 inner: InnerString::Static(s),
178 } => s.to_string(),
179 CheetahString {
180 inner: InnerString::Shared(s),
181 } => s.to_string(),
182 CheetahString {
183 inner: InnerString::Owned(s),
184 } => s,
185 }
186 }
187}
188
189impl Deref for CheetahString {
190 type Target = str;
191
192 #[inline]
193 fn deref(&self) -> &Self::Target {
194 self.as_str()
195 }
196}
197
198impl AsRef<str> for CheetahString {
199 #[inline]
200 fn as_ref(&self) -> &str {
201 self.as_str()
202 }
203}
204
205impl AsRef<[u8]> for CheetahString {
206 #[inline]
207 fn as_ref(&self) -> &[u8] {
208 self.as_bytes()
209 }
210}
211
212impl AsRef<CheetahString> for CheetahString {
213 #[inline]
214 fn as_ref(&self) -> &CheetahString {
215 self
216 }
217}
218
219impl From<&String> for CheetahString {
220 #[inline]
221 fn from(s: &String) -> Self {
222 CheetahString::from_slice(s)
223 }
224}
225
226impl CheetahString {
227 #[inline]
228 pub const fn empty() -> Self {
229 CheetahString {
230 inner: InnerString::Inline(InlineStr::empty()),
231 }
232 }
233
234 #[inline]
235 pub fn new() -> Self {
236 CheetahString::default()
237 }
238
239 #[inline]
240 pub const fn from_static_str(s: &'static str) -> Self {
241 CheetahString {
242 inner: InnerString::Static(s),
243 }
244 }
245
246 #[inline]
253 pub unsafe fn from_utf8_unchecked_vec(s: Vec<u8>) -> Self {
254 CheetahString::from_validated_vec_unchecked(s)
255 }
256
257 #[inline]
258 fn from_validated_vec_unchecked(s: Vec<u8>) -> Self {
259 if s.len() <= INLINE_CAPACITY {
260 let value = unsafe { str::from_utf8_unchecked(&s) };
262 let inline = InlineStr::from_str(value).expect("short str must fit inline storage");
263 return CheetahString {
264 inner: InnerString::Inline(inline),
265 };
266 }
267
268 CheetahString::from_builder_string(unsafe { String::from_utf8_unchecked(s) })
270 }
271
272 pub fn try_from_vec(v: Vec<u8>) -> Result<Self, Utf8Error> {
291 str::from_utf8(&v)?;
292 Ok(CheetahString::from_validated_vec_unchecked(v))
293 }
294
295 pub fn try_from_bytes(b: &[u8]) -> Result<Self, Utf8Error> {
314 let s = str::from_utf8(b)?;
315 Ok(CheetahString::from_slice(s))
316 }
317
318 #[inline]
324 pub unsafe fn from_utf8_unchecked_bytes(b: &[u8]) -> Self {
325 CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
327 }
328
329 #[inline]
335 pub fn try_from_arc_vec(s: Arc<Vec<u8>>) -> Result<Self, Utf8Error> {
336 match Arc::try_unwrap(s) {
337 Ok(v) => CheetahString::try_from_vec(v),
338 Err(s) => {
339 let s = str::from_utf8(s.as_slice())?;
340 Ok(CheetahString::from_slice(s))
341 }
342 }
343 }
344
345 #[inline]
351 pub unsafe fn from_utf8_unchecked_arc_vec(s: Arc<Vec<u8>>) -> Self {
352 CheetahString::from_validated_arc_vec_unchecked(s)
353 }
354
355 #[inline]
356 fn from_validated_arc_vec_unchecked(s: Arc<Vec<u8>>) -> Self {
357 match Arc::try_unwrap(s) {
358 Ok(v) => CheetahString::from_validated_vec_unchecked(v),
359 Err(s) => {
360 unsafe { CheetahString::from_utf8_unchecked_bytes(s.as_slice()) }
362 }
363 }
364 }
365
366 #[inline]
367 pub fn from_slice(s: &str) -> Self {
368 if let Some(inline) = InlineStr::from_str(s) {
369 CheetahString {
370 inner: InnerString::Inline(inline),
371 }
372 } else {
373 let arc_str: Arc<str> = Arc::from(s);
375 CheetahString {
376 inner: InnerString::Shared(arc_str),
377 }
378 }
379 }
380
381 #[inline]
382 pub fn from_string(s: String) -> Self {
383 CheetahString::from_string_owned(s)
384 }
385
386 #[inline]
393 pub fn from_string_owned(s: String) -> Self {
394 CheetahString::from_builder_string(s)
395 }
396
397 #[inline]
403 pub fn from_string_shared(s: String) -> Self {
404 if let Some(inline) = InlineStr::from_str(&s) {
405 CheetahString {
406 inner: InnerString::Inline(inline),
407 }
408 } else {
409 let arc_str: Arc<str> = s.into_boxed_str().into();
411 CheetahString {
412 inner: InnerString::Shared(arc_str),
413 }
414 }
415 }
416
417 #[inline]
418 fn from_builder_string(s: String) -> Self {
419 if s.len() <= INLINE_CAPACITY && s.capacity() <= INLINE_CAPACITY {
420 let inline = InlineStr::from_str(&s).expect("short String must fit inline storage");
421 CheetahString {
422 inner: InnerString::Inline(inline),
423 }
424 } else {
425 CheetahString {
426 inner: InnerString::Owned(s),
427 }
428 }
429 }
430
431 #[inline]
432 pub fn from_arc_string(s: Arc<String>) -> Self {
433 match Arc::try_unwrap(s) {
434 Ok(s) => CheetahString::from_builder_string(s),
435 Err(s) => CheetahString::from_slice(s.as_str()),
436 }
437 }
438
439 #[inline]
440 #[cfg(feature = "bytes")]
441 pub fn try_from_bytes_buf(b: bytes::Bytes) -> Result<Self, Utf8Error> {
442 str::from_utf8(b.as_ref())?;
443 Ok(CheetahString::from_validated_bytes_unchecked(b))
444 }
445
446 #[inline]
452 #[cfg(feature = "bytes")]
453 pub unsafe fn from_utf8_unchecked_bytes_buf(b: bytes::Bytes) -> Self {
454 CheetahString::from_validated_bytes_unchecked(b)
455 }
456
457 #[inline]
458 #[cfg(feature = "bytes")]
459 fn from_validated_bytes_unchecked(b: bytes::Bytes) -> Self {
460 unsafe { CheetahString::from_utf8_unchecked_bytes(b.as_ref()) }
462 }
463
464 #[inline]
465 pub fn as_str(&self) -> &str {
466 match &self.inner {
467 InnerString::Inline(inline) => inline.as_str(),
468 InnerString::Static(s) => s,
469 InnerString::Shared(s) => s.as_ref(),
470 InnerString::Owned(s) => s.as_str(),
471 }
472 }
473
474 #[inline]
475 pub fn as_bytes(&self) -> &[u8] {
476 match &self.inner {
477 InnerString::Inline(inline) => inline.as_bytes(),
478 InnerString::Static(s) => s.as_bytes(),
479 InnerString::Shared(s) => s.as_bytes(),
480 InnerString::Owned(s) => s.as_bytes(),
481 }
482 }
483
484 #[inline]
485 pub fn len(&self) -> usize {
486 match &self.inner {
487 InnerString::Inline(inline) => inline.len(),
488 InnerString::Static(s) => s.len(),
489 InnerString::Shared(s) => s.len(),
490 InnerString::Owned(s) => s.len(),
491 }
492 }
493
494 #[inline]
495 pub fn is_empty(&self) -> bool {
496 match &self.inner {
497 InnerString::Inline(inline) => inline.is_empty(),
498 InnerString::Static(s) => s.is_empty(),
499 InnerString::Shared(s) => s.is_empty(),
500 InnerString::Owned(s) => s.is_empty(),
501 }
502 }
503
504 #[inline]
522 pub fn starts_with<P: StrPattern>(&self, pat: P) -> bool {
523 match pat.as_str_pattern() {
524 StrPatternImpl::Char(c) => self.as_str().starts_with(c),
525 StrPatternImpl::Str(s) => {
526 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
527 {
528 if s.len() >= crate::simd::SIMD_THRESHOLD {
529 return crate::simd::starts_with_bytes(self.as_bytes(), s.as_bytes());
530 }
531 }
532
533 self.as_str().starts_with(s)
534 }
535 }
536 }
537
538 #[inline]
550 pub fn starts_with_char(&self, pat: char) -> bool {
551 self.as_str().starts_with(pat)
552 }
553
554 #[inline]
570 pub fn ends_with<P: StrPattern>(&self, pat: P) -> bool {
571 match pat.as_str_pattern() {
572 StrPatternImpl::Char(c) => self.as_str().ends_with(c),
573 StrPatternImpl::Str(s) => {
574 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
575 {
576 if s.len() >= crate::simd::SIMD_THRESHOLD {
577 return crate::simd::ends_with_bytes(self.as_bytes(), s.as_bytes());
578 }
579 }
580
581 self.as_str().ends_with(s)
582 }
583 }
584 }
585
586 #[inline]
598 pub fn ends_with_char(&self, pat: char) -> bool {
599 self.as_str().ends_with(pat)
600 }
601
602 #[inline]
619 pub fn contains<P: StrPattern>(&self, pat: P) -> bool {
620 match pat.as_str_pattern() {
621 StrPatternImpl::Char(c) => self.as_str().contains(c),
622 StrPatternImpl::Str(s) => {
623 crate::search::find_bytes(self.as_bytes(), s.as_bytes()).is_some()
624 }
625 }
626 }
627
628 #[inline]
640 pub fn contains_char(&self, pat: char) -> bool {
641 self.as_str().contains(pat)
642 }
643
644 #[inline]
660 pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
661 let pat = pat.as_ref();
662 crate::search::find_bytes(self.as_bytes(), pat.as_bytes())
663 }
664
665 #[inline]
676 pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
677 crate::search::rfind_bytes(self.as_bytes(), pat.as_ref().as_bytes())
678 }
679
680 #[inline]
691 pub fn trim(&self) -> &str {
692 self.as_str().trim()
693 }
694
695 #[inline]
706 pub fn trim_start(&self) -> &str {
707 self.as_str().trim_start()
708 }
709
710 #[inline]
721 pub fn trim_end(&self) -> &str {
722 self.as_str().trim_end()
723 }
724
725 #[inline]
739 pub fn split<'a, P>(&'a self, pat: P) -> SplitWrapper<'a>
740 where
741 P: SplitPattern<'a>,
742 {
743 pat.split_str(self.as_str())
744 }
745
746 #[inline]
758 pub fn lines(&self) -> impl Iterator<Item = &str> {
759 self.as_str().lines()
760 }
761
762 #[inline]
776 pub fn chars(&self) -> str::Chars<'_> {
777 self.as_str().chars()
778 }
779
780 #[inline]
793 pub fn to_uppercase(&self) -> CheetahString {
794 CheetahString::from_string(self.as_str().to_uppercase())
795 }
796
797 #[inline]
808 pub fn to_lowercase(&self) -> CheetahString {
809 CheetahString::from_string(self.as_str().to_lowercase())
810 }
811
812 #[inline]
823 pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
824 CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
825 }
826
827 #[inline]
838 pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
839 CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
840 }
841
842 #[inline]
860 pub fn substring(&self, start: usize, end: usize) -> CheetahString {
861 self.try_substring(start, end)
862 .expect("substring range must be in bounds and on UTF-8 character boundaries")
863 }
864
865 #[inline]
878 pub fn try_substring(&self, start: usize, end: usize) -> crate::Result<CheetahString> {
879 let value = self.as_str();
880 let len = value.len();
881
882 if start > end {
883 return Err(crate::Error::InvalidRange { start, end });
884 }
885
886 if start > len {
887 return Err(crate::Error::IndexOutOfBounds { index: start, len });
888 }
889
890 if end > len {
891 return Err(crate::Error::IndexOutOfBounds { index: end, len });
892 }
893
894 if !value.is_char_boundary(start) {
895 return Err(crate::Error::InvalidCharBoundary { index: start });
896 }
897
898 if !value.is_char_boundary(end) {
899 return Err(crate::Error::InvalidCharBoundary { index: end });
900 }
901
902 Ok(CheetahString::from_slice(&value[start..end]))
903 }
904
905 #[inline]
916 pub fn repeat(&self, n: usize) -> CheetahString {
917 CheetahString::from_string(self.as_str().repeat(n))
918 }
919
920 #[inline]
938 pub fn with_capacity(capacity: usize) -> Self {
939 if capacity <= INLINE_CAPACITY {
940 CheetahString::empty()
941 } else {
942 CheetahString::from_builder_string(String::with_capacity(capacity))
943 }
944 }
945
946 #[inline]
947 fn push_str_internal(&mut self, string: &str) {
948 if string.is_empty() {
949 return;
950 }
951
952 match &mut self.inner {
953 InnerString::Inline(inline) => {
954 if inline.push_str(string) {
955 return;
956 }
957 }
958 InnerString::Owned(s) => {
959 s.push_str(string);
960 return;
961 }
962 _ => {}
963 }
964
965 let total_len = self.len() + string.len();
966 let mut result = String::with_capacity(total_len);
967 result.push_str(self.as_str());
968 result.push_str(string);
969 *self = CheetahString::from_builder_string(result);
970 }
971
972 #[inline]
990 pub fn push_str(&mut self, string: &str) {
991 self.push_str_internal(string);
992 }
993
994 #[inline]
1009 pub fn reserve(&mut self, additional: usize) {
1010 if additional == 0 {
1011 return;
1012 }
1013
1014 match &mut self.inner {
1015 InnerString::Inline(inline) if inline.len() + additional <= INLINE_CAPACITY => {
1016 return;
1017 }
1018 InnerString::Inline(_) => {}
1019 InnerString::Owned(s) => {
1020 s.reserve(additional);
1021 return;
1022 }
1023 _ => {}
1024 }
1025
1026 let new_len = self.len() + additional;
1027 let mut s = String::with_capacity(new_len);
1028 s.push_str(self.as_str());
1029 *self = CheetahString::from_builder_string(s);
1030 }
1031}
1032
1033impl PartialEq for CheetahString {
1034 #[inline]
1035 fn eq(&self, other: &Self) -> bool {
1036 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1037 {
1038 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1039 }
1040 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1041 {
1042 self.as_str() == other.as_str()
1043 }
1044 }
1045}
1046
1047impl PartialEq<str> for CheetahString {
1048 #[inline]
1049 fn eq(&self, other: &str) -> bool {
1050 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1051 {
1052 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1053 }
1054 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1055 {
1056 self.as_str() == other
1057 }
1058 }
1059}
1060
1061impl PartialEq<String> for CheetahString {
1062 #[inline]
1063 fn eq(&self, other: &String) -> bool {
1064 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1065 {
1066 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1067 }
1068 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1069 {
1070 self.as_str() == other.as_str()
1071 }
1072 }
1073}
1074
1075impl PartialEq<Vec<u8>> for CheetahString {
1076 #[inline]
1077 fn eq(&self, other: &Vec<u8>) -> bool {
1078 self.as_bytes() == other.as_slice()
1079 }
1080}
1081
1082impl<'a> PartialEq<&'a str> for CheetahString {
1083 #[inline]
1084 fn eq(&self, other: &&'a str) -> bool {
1085 self.as_str() == *other
1086 }
1087}
1088
1089impl PartialEq<CheetahString> for str {
1090 #[inline]
1091 fn eq(&self, other: &CheetahString) -> bool {
1092 self == other.as_str()
1093 }
1094}
1095
1096impl PartialEq<CheetahString> for String {
1097 #[inline]
1098 fn eq(&self, other: &CheetahString) -> bool {
1099 self.as_str() == other.as_str()
1100 }
1101}
1102
1103impl PartialEq<CheetahString> for &str {
1104 #[inline]
1105 fn eq(&self, other: &CheetahString) -> bool {
1106 *self == other.as_str()
1107 }
1108}
1109
1110impl Eq for CheetahString {}
1111
1112impl PartialOrd for CheetahString {
1113 #[inline]
1114 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1115 Some(self.cmp(other))
1116 }
1117}
1118
1119impl Ord for CheetahString {
1120 #[inline]
1121 fn cmp(&self, other: &Self) -> Ordering {
1122 self.as_str().cmp(other.as_str())
1123 }
1124}
1125
1126impl Hash for CheetahString {
1127 #[inline]
1128 fn hash<H: Hasher>(&self, state: &mut H) {
1129 self.as_str().hash(state);
1130 }
1131}
1132
1133impl Display for CheetahString {
1134 #[inline]
1135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1136 self.as_str().fmt(f)
1137 }
1138}
1139
1140impl fmt::Debug for CheetahString {
1141 #[inline]
1142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143 fmt::Debug::fmt(self.as_str(), f)
1144 }
1145}
1146
1147impl Borrow<str> for CheetahString {
1148 #[inline]
1149 fn borrow(&self) -> &str {
1150 self.as_str()
1151 }
1152}
1153
1154impl Add<&str> for CheetahString {
1157 type Output = CheetahString;
1158
1159 #[inline]
1171 fn add(mut self, rhs: &str) -> Self::Output {
1172 self.push_str_internal(rhs);
1173 self
1174 }
1175}
1176
1177impl Add<&CheetahString> for CheetahString {
1178 type Output = CheetahString;
1179
1180 #[inline]
1193 fn add(mut self, rhs: &CheetahString) -> Self::Output {
1194 self.push_str_internal(rhs.as_str());
1195 self
1196 }
1197}
1198
1199impl Add<String> for CheetahString {
1200 type Output = CheetahString;
1201
1202 #[inline]
1214 fn add(mut self, rhs: String) -> Self::Output {
1215 if self.is_empty() {
1216 return CheetahString::from_string_owned(rhs);
1217 }
1218
1219 self.push_str_internal(&rhs);
1220 self
1221 }
1222}
1223
1224impl AddAssign<&str> for CheetahString {
1225 #[inline]
1237 fn add_assign(&mut self, rhs: &str) {
1238 self.push_str_internal(rhs);
1239 }
1240}
1241
1242impl AddAssign<&CheetahString> for CheetahString {
1243 #[inline]
1256 fn add_assign(&mut self, rhs: &CheetahString) {
1257 self.push_str_internal(rhs.as_str());
1258 }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263 use super::*;
1264 use alloc::{format, vec};
1265
1266 #[test]
1267 fn with_capacity_above_inline_uses_heap_storage() {
1268 let s = CheetahString::with_capacity(INLINE_CAPACITY + 8);
1269
1270 match &s.inner {
1271 InnerString::Owned(inner) => {
1272 assert!(inner.capacity() >= INLINE_CAPACITY + 8);
1273 }
1274 other => panic!(
1275 "expected heap-backed storage from with_capacity, got {:?}",
1276 core::mem::discriminant(other)
1277 ),
1278 }
1279 }
1280
1281 #[test]
1282 fn push_str_promotes_builder_growth_to_owned_storage() {
1283 let suffix = "a".repeat(INLINE_CAPACITY);
1284 let expected = format!("hello{suffix}");
1285 let mut s = CheetahString::from("hello");
1286
1287 s.push_str(&suffix);
1288
1289 match &s.inner {
1290 InnerString::Owned(inner) => {
1291 assert_eq!(inner.as_str(), expected.as_str());
1292 assert!(inner.capacity() >= expected.len());
1293 }
1294 other => panic!(
1295 "expected owned heap storage after builder growth, got {:?}",
1296 core::mem::discriminant(other)
1297 ),
1298 }
1299 }
1300
1301 #[test]
1302 fn long_borrowed_str_uses_shared_storage() {
1303 let value = "a".repeat(INLINE_CAPACITY + 1);
1304 let s = CheetahString::from_slice(&value);
1305
1306 match &s.inner {
1307 InnerString::Shared(inner) => assert_eq!(inner.as_ref(), value.as_str()),
1308 other => panic!(
1309 "expected Shared for long borrowed input, got {:?}",
1310 core::mem::discriminant(other)
1311 ),
1312 }
1313 }
1314
1315 #[test]
1316 fn try_from_vec_short_input_uses_inline_storage() {
1317 let s = CheetahString::try_from_vec(b"hello".to_vec()).expect("valid utf-8");
1318
1319 match &s.inner {
1320 InnerString::Inline(inline) => {
1321 assert_eq!(inline.len(), 5);
1322 assert_eq!(inline.as_bytes(), b"hello");
1323 }
1324 other => panic!(
1325 "expected inline storage for short validated Vec<u8>, got {:?}",
1326 core::mem::discriminant(other)
1327 ),
1328 }
1329 }
1330
1331 #[test]
1332 fn long_vec_conversion_uses_owned_storage() {
1333 let value = "a".repeat(INLINE_CAPACITY + 1).into_bytes();
1334 let s = CheetahString::try_from_vec(value).expect("valid utf-8");
1335
1336 match &s.inner {
1337 InnerString::Owned(inner) => {
1338 assert_eq!(inner.len(), INLINE_CAPACITY + 1);
1339 assert_eq!(inner.as_bytes(), vec![b'a'; INLINE_CAPACITY + 1].as_slice());
1340 }
1341 other => panic!(
1342 "expected Owned for long Vec<u8> conversion, got {:?}",
1343 core::mem::discriminant(other)
1344 ),
1345 }
1346 }
1347}