1use compact_str::CompactString as CompactStr;
2pub use compact_str::ToCompactString;
3use derive_more::{Deref, DerefMut, Display, From, Index, IndexMut, Into};
4use serde::{Deserialize, Serialize};
5use std::{
6 borrow::{Borrow, BorrowMut, Cow},
7 convert::Infallible,
8 ffi::OsStr,
9 fmt::{self, Write as FmtWrite},
10 iter::FromIterator,
11 path::Path,
12 str,
13 str::{FromStr, Utf8Error},
14 string::FromUtf8Error,
15};
16
17#[cfg(feature = "postgres_types")]
18use bytes::BytesMut;
19#[cfg(feature = "postgres_types")]
20use postgres_types::{FromSql, IsNull, ToSql, Type};
21
22#[cfg(feature = "utoipa_types")]
23use utoipa::{PartialSchema, ToSchema};
24
25#[cfg(feature = "axum_types")]
26use axum::response::IntoResponse;
27
28#[cfg(feature = "axum_types")]
29use axum::body::Body;
30
31#[cfg(feature = "async_graphql")]
32use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
33
34#[derive(
35 Display,
36 Serialize,
37 Deserialize,
38 Deref,
39 DerefMut,
40 Index,
41 IndexMut,
42 Debug,
43 Clone,
44 Into,
45 From,
46 PartialEq,
47 Eq,
48 Hash,
49 Default,
50 PartialOrd,
51 Ord,
52)]
53pub struct StackString(CompactStr);
54
55impl StackString {
56 #[must_use]
57 pub fn new() -> Self {
58 Self(CompactStr::new(""))
59 }
60
61 #[must_use]
62 pub fn as_str(&self) -> &str {
63 self.0.as_str()
64 }
65
66 #[must_use]
67 pub fn split_off(&mut self, index: usize) -> Self {
68 Self(self.0.split_off(index))
69 }
70
71 pub fn from_utf8(v: &[u8]) -> Result<Self, Utf8Error> {
76 CompactStr::from_utf8(v).map(Self)
77 }
78
79 pub fn from_utf8_vec(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
84 String::from_utf8(vec).map(|s| Self(CompactStr::from_string_buffer(s)))
85 }
86
87 #[must_use]
88 pub fn from_utf8_lossy(v: &[u8]) -> Self {
89 Self(CompactStr::from_utf8_lossy(v))
90 }
91
92 pub fn from_display(buf: impl fmt::Display) -> Self {
97 let mut s = Self::new();
98 write!(s, "{buf}").unwrap();
99 s
100 }
101
102 #[inline]
103 #[must_use]
104 pub fn is_inline(&self) -> bool {
105 !self.is_heap_allocated()
106 }
107}
108
109impl From<StackString> for String {
110 fn from(item: StackString) -> Self {
111 item.0.into()
112 }
113}
114
115impl From<&StackString> for String {
116 fn from(item: &StackString) -> Self {
117 item.as_str().into()
118 }
119}
120
121impl From<&StackString> for StackString {
122 fn from(item: &StackString) -> Self {
123 item.clone()
124 }
125}
126
127impl From<String> for StackString {
128 fn from(item: String) -> Self {
129 Self(item.into())
130 }
131}
132
133impl From<&String> for StackString {
134 fn from(item: &String) -> Self {
135 Self(item.into())
136 }
137}
138
139impl From<&str> for StackString {
140 fn from(item: &str) -> Self {
141 Self(item.into())
142 }
143}
144
145impl<'a> From<&'a StackString> for &'a str {
146 fn from(item: &StackString) -> &str {
147 item.as_str()
148 }
149}
150
151impl<'a> From<Cow<'a, str>> for StackString {
152 fn from(item: Cow<'a, str>) -> Self {
153 match item {
154 Cow::Borrowed(s) => s.into(),
155 Cow::Owned(s) => s.into(),
156 }
157 }
158}
159
160impl From<StackString> for Cow<'_, str> {
161 fn from(item: StackString) -> Self {
162 Cow::Owned(item.into())
163 }
164}
165
166impl Borrow<str> for StackString {
167 fn borrow(&self) -> &str {
168 self.0.borrow()
169 }
170}
171
172impl BorrowMut<str> for StackString {
173 fn borrow_mut(&mut self) -> &mut str {
174 self.0.borrow_mut()
175 }
176}
177
178impl AsRef<str> for StackString {
179 fn as_ref(&self) -> &str {
180 self.0.as_str()
181 }
182}
183
184impl AsRef<[u8]> for StackString {
185 fn as_ref(&self) -> &[u8] {
186 self.0.as_ref()
187 }
188}
189
190impl AsRef<OsStr> for StackString {
191 fn as_ref(&self) -> &OsStr {
192 self.as_str().as_ref()
193 }
194}
195
196impl AsRef<Path> for StackString {
197 fn as_ref(&self) -> &Path {
198 Path::new(self)
199 }
200}
201
202impl FromStr for StackString {
203 type Err = Infallible;
204 fn from_str(s: &str) -> Result<Self, Self::Err> {
205 Ok(s.into())
206 }
207}
208
209impl<'a> PartialEq<Cow<'a, str>> for StackString {
210 #[inline]
211 fn eq(&self, other: &Cow<'a, str>) -> bool {
212 PartialEq::eq(&self[..], &other[..])
213 }
214}
215
216impl<'a> PartialOrd<Cow<'a, str>> for StackString {
217 fn partial_cmp(&self, other: &Cow<'a, str>) -> Option<std::cmp::Ordering> {
218 PartialOrd::partial_cmp(&self[..], &other[..])
219 }
220}
221
222impl PartialEq<String> for StackString {
223 #[inline]
224 fn eq(&self, other: &String) -> bool {
225 PartialEq::eq(&self[..], &other[..])
226 }
227}
228
229impl PartialOrd<String> for StackString {
230 fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
231 PartialOrd::partial_cmp(&self[..], &other[..])
232 }
233}
234
235impl PartialEq<str> for StackString {
236 #[inline]
237 fn eq(&self, other: &str) -> bool {
238 PartialEq::eq(&self.0, other)
239 }
240}
241
242impl PartialOrd<str> for StackString {
243 fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
244 PartialOrd::partial_cmp(&self[..], other)
245 }
246}
247
248impl<'a> PartialEq<&'a str> for StackString {
249 #[inline]
250 fn eq(&self, other: &&'a str) -> bool {
251 PartialEq::eq(&self[..], &other[..])
252 }
253}
254
255impl<'a> PartialOrd<&'a str> for StackString {
256 fn partial_cmp(&self, other: &&'a str) -> Option<std::cmp::Ordering> {
257 PartialOrd::partial_cmp(&self[..], &other[..])
258 }
259}
260
261impl PartialEq<StackString> for str {
262 fn eq(&self, other: &StackString) -> bool {
263 PartialEq::eq(self, &other[..])
264 }
265}
266
267impl PartialOrd<StackString> for str {
268 fn partial_cmp(&self, other: &StackString) -> Option<std::cmp::Ordering> {
269 PartialOrd::partial_cmp(self, &other[..])
270 }
271}
272
273impl PartialEq<StackString> for &str {
274 fn eq(&self, other: &StackString) -> bool {
275 PartialEq::eq(&self[..], &other[..])
276 }
277}
278
279impl PartialOrd<StackString> for &str {
280 fn partial_cmp(&self, other: &StackString) -> Option<std::cmp::Ordering> {
281 PartialOrd::partial_cmp(&self[..], &other[..])
282 }
283}
284
285impl FromIterator<char> for StackString {
286 fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
287 let s = CompactStr::from_iter(iter);
288 Self(s)
289 }
290}
291
292#[cfg(feature = "postgres_types")]
293impl<'a> FromSql<'a> for StackString {
294 fn from_sql(
295 ty: &Type,
296 raw: &'a [u8],
297 ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
298 let s = <&'a str as FromSql>::from_sql(ty, raw)?;
299 Ok(s.into())
300 }
301
302 fn accepts(ty: &Type) -> bool {
303 <&'a str as FromSql>::accepts(ty)
304 }
305}
306
307#[cfg(feature = "postgres_types")]
308impl ToSql for StackString {
309 fn to_sql(
310 &self,
311 ty: &Type,
312 out: &mut BytesMut,
313 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>>
314 where
315 Self: Sized,
316 {
317 ToSql::to_sql(&self.as_str(), ty, out)
318 }
319
320 fn accepts(ty: &Type) -> bool
321 where
322 Self: Sized,
323 {
324 <String as ToSql>::accepts(ty)
325 }
326
327 fn to_sql_checked(
328 &self,
329 ty: &Type,
330 out: &mut BytesMut,
331 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
332 self.as_str().to_sql_checked(ty, out)
333 }
334}
335
336#[cfg(feature = "utoipa_types")]
337impl PartialSchema for StackString {
338 fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
339 str::schema()
340 }
341}
342
343#[cfg(feature = "utoipa_types")]
344impl ToSchema for StackString {
345 fn name() -> Cow<'static, str> {
346 str::name()
347 }
348}
349
350#[cfg(feature = "axum_types")]
351impl IntoResponse for StackString {
352 fn into_response(self) -> axum::response::Response {
353 let s: String = self.into();
354 s.into_response()
355 }
356}
357
358#[cfg(feature = "axum_types")]
359impl From<StackString> for Body {
360 fn from(value: StackString) -> Self {
361 let s: String = value.into();
362 s.into()
363 }
364}
365
366#[macro_export]
367macro_rules! format_sstr {
368 ($($arg:tt)*) => {
369 $crate::StackString::from($crate::stack_string::ToCompactString::to_compact_string(&core::format_args!($($arg)*)))
370 }
371}
372
373#[cfg(feature = "async_graphql")]
375#[Scalar]
376impl ScalarType for StackString {
377 fn parse(value: Value) -> InputValueResult<Self> {
378 if let Value::String(s) = value {
379 let s: StackString = s.into();
380 Ok(s)
381 } else {
382 Err(InputValueError::expected_type(value))
383 }
384 }
385
386 fn is_valid(value: &Value) -> bool {
387 matches!(value, Value::String(_))
388 }
389
390 fn to_value(&self) -> Value {
391 Value::String(self.to_string())
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use rand::{rng as thread_rng, Rng};
398
399 #[cfg(feature = "async_graphql")]
400 use std::future::Future;
401
402 use crate::{StackString, MAX_INLINE};
403
404 #[test]
405 fn test_default() {
406 assert_eq!(StackString::new(), StackString::default());
407 assert_eq!(MAX_INLINE, 24);
408 }
409
410 #[test]
411 fn test_split_off() {
412 let mut s0 = "hello there".to_string();
413 let s1 = s0.split_off(3);
414 let mut s2: StackString = "hello there".into();
415 let s3 = s2.split_off(3);
416 assert_eq!(s0.as_str(), s2.as_str());
417 assert_eq!(s1.as_str(), s3.as_str());
418 }
419
420 #[test]
421 fn test_from_utf8() {
422 let mut rng = thread_rng();
423 let v: Vec<_> = (0..20).map(|_| rng.random::<u8>() & 0x7f).collect();
424 let s0 = String::from_utf8(v.clone()).unwrap();
425 let s1 = StackString::from_utf8(&v).unwrap();
426 assert_eq!(s0.as_str(), s1.as_str());
427
428 let v: Vec<_> = (0..20).map(|_| rng.random::<u8>()).collect();
429 let s0 = String::from_utf8(v.clone());
430 let s1 = StackString::from_utf8(&v);
431
432 match s0 {
433 Ok(s) => assert_eq!(s.as_str(), s1.unwrap().as_str()),
434 Err(e) => assert_eq!(e.utf8_error(), s1.unwrap_err()),
435 }
436 }
437
438 #[test]
439 fn test_from_utf8_vec() {
440 let mut rng = thread_rng();
441 let v: Vec<_> = (0..20).map(|_| rng.random::<u8>() & 0x7f).collect();
442 let s0 = String::from_utf8(v.clone()).unwrap();
443 let s1 = StackString::from_utf8_vec(v).unwrap();
444 assert_eq!(s0.as_str(), s1.as_str());
445
446 let v: Vec<_> = (0..20).map(|_| rng.random::<u8>()).collect();
447 let s0 = String::from_utf8(v.clone());
448 let s1 = StackString::from_utf8_vec(v);
449
450 match s0 {
451 Ok(s) => assert_eq!(s.as_str(), s1.unwrap().as_str()),
452 Err(e) => assert_eq!(e, s1.unwrap_err()),
453 }
454 }
455
456 #[test]
457 fn test_string_from_compact_string() {
458 let s0 = StackString::from("Hello there");
459 let s1: String = s0.clone().into();
460 assert_eq!(s0.as_str(), s1.as_str());
461 }
462
463 #[test]
464 fn test_compact_string_from_string() {
465 let s0 = String::from("Hello there");
466 let s1: StackString = s0.clone().into();
467 assert_eq!(s0.as_str(), s1.as_str());
468 let s1: StackString = (&s0).into();
469 assert_eq!(s0.as_str(), s1.as_str());
470 }
471
472 #[test]
473 fn test_borrow() {
474 use std::borrow::Borrow;
475 let s = StackString::from("Hello");
476 let st: &str = s.borrow();
477 assert_eq!(st, "Hello");
478 }
479
480 #[test]
481 fn test_as_ref() {
482 use std::path::Path;
483
484 let s = StackString::from("Hello");
485 let st: &str = s.as_ref();
486 assert_eq!(st, s.as_str());
487 let bt: &[u8] = s.as_ref();
488 assert_eq!(bt, s.as_bytes());
489 let pt: &Path = s.as_ref();
490 assert_eq!(pt, Path::new("Hello"));
491 }
492
493 #[test]
494 fn test_from_str() {
495 let s = StackString::from("Hello");
496 let st: StackString = "Hello".parse().unwrap();
497 assert_eq!(s, st);
498 }
499
500 #[test]
501 fn test_partialeq_cow() {
502 use std::path::Path;
503 let p = Path::new("Hello");
504 let ps = p.to_string_lossy();
505 let s = StackString::from("Hello");
506 assert_eq!(s, ps);
507 }
508
509 #[test]
510 fn test_partial_eq_string() {
511 assert_eq!(StackString::from("Hello"), String::from("Hello"));
512 assert_eq!(StackString::from("Hello"), "Hello");
513 assert_eq!(&StackString::from("Hello"), "Hello");
514 assert!(StackString::from("alpha") < "beta");
515 assert!("beta" > StackString::from("alpha"));
516 }
517
518 #[test]
519 fn test_from_iterator_char() {
520 let mut rng = thread_rng();
521 let v: Vec<char> = (0..20).map(|_| rng.random::<char>()).collect();
522 let s0: StackString = v.iter().map(|x| *x).collect();
523 let s1: String = v.iter().map(|x| *x).collect();
524 assert_eq!(s0, s1);
525 }
526
527 #[test]
528 fn test_contains_compact_string() {
529 let a: StackString = "hey there".into();
530 let b: StackString = "hey".into();
531 assert!(a.contains(b.as_str()));
532 }
533
534 #[test]
535 fn test_contains_char() {
536 let a: StackString = "hey there".into();
537 assert!(a.contains(' '));
538 }
539
540 #[test]
541 fn test_equality() {
542 let s: StackString = "hey".into();
543 assert_eq!(Some(&s).map(Into::into), Some("hey"));
544 }
545
546 #[cfg(feature = "postgres_types")]
547 use bytes::BytesMut;
548 #[cfg(feature = "postgres_types")]
549 use postgres_types::{FromSql, IsNull, ToSql, Type};
550
551 #[cfg(feature = "postgres_types")]
552 #[test]
553 fn test_from_sql() {
554 let raw = b"Hello There";
555 let t = Type::TEXT;
556 let s = StackString::from_sql(&t, raw).unwrap();
557 assert_eq!(s, StackString::from("Hello There"));
558
559 assert!(<StackString as FromSql>::accepts(&t));
560 }
561
562 #[cfg(feature = "postgres_types")]
563 #[test]
564 fn test_to_sql() {
565 let s = StackString::from("Hello There");
566 let t = Type::TEXT;
567 assert!(<StackString as ToSql>::accepts(&t));
568 let mut buf = BytesMut::new();
569 match s.to_sql(&t, &mut buf).unwrap() {
570 IsNull::Yes => assert!(false),
571 IsNull::No => {}
572 }
573 assert_eq!(buf.as_ref(), b"Hello There");
574 }
575
576 #[test]
577 fn test_from_display() {
578 use std::fmt::Display;
579
580 struct Test {}
581
582 impl Display for Test {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 f.write_str("THIS IS A TEST")
585 }
586 }
587
588 let t = Test {};
589 let s = StackString::from_display(t);
590 assert_eq!(s, StackString::from("THIS IS A TEST"));
591 }
592
593 #[test]
594 fn test_format_sstr() {
595 use crate::format_sstr;
596
597 let s = format_sstr!("This is a test {}", 22);
598 assert_eq!(s, StackString::from("This is a test 22"));
599 }
600
601 #[test]
602 fn test_from_utf8_lossy() {
603 let mut v = Vec::new();
604 v.extend_from_slice("this is a test".as_bytes());
605 v.push(0xff);
606 v.extend_from_slice("yes".as_bytes());
607 let s = StackString::from_utf8_lossy(&v);
608 assert_eq!(s.len(), 20);
609 assert_eq!(s.is_heap_allocated(), false);
610 }
611
612 #[test]
613 fn test_serde() {
614 use serde::Deserialize;
615
616 let s = StackString::from("HELLO");
617 let t = "HELLO";
618 let s = serde_json::to_vec(&s).unwrap();
619 let t = serde_json::to_vec(t).unwrap();
620 assert_eq!(s, t);
621
622 let s = r#"{"a": "b"}"#;
623
624 #[derive(Deserialize)]
625 struct A {
626 a: StackString,
627 }
628
629 #[derive(Deserialize)]
630 struct B {
631 a: String,
632 }
633
634 let a: A = serde_json::from_str(s).unwrap();
635 let b: B = serde_json::from_str(s).unwrap();
636 assert_eq!(a.a.as_str(), b.a.as_str());
637 }
638
639 #[cfg(feature = "async_graphql")]
640 #[test]
641 fn test_compact_string_async_graphql() {
642 use async_graphql::{
643 dataloader::{DataLoader, Loader},
644 Context, EmptyMutation, EmptySubscription, Object, Schema,
645 };
646 use async_trait::async_trait;
647 use std::{collections::HashMap, convert::Infallible};
648
649 struct StackStringLoader;
650
651 impl StackStringLoader {
652 fn new() -> Self {
653 Self
654 }
655 }
656
657 #[async_trait]
658 impl Loader<StackString> for StackStringLoader {
659 type Value = StackString;
660 type Error = Infallible;
661
662 fn load(
663 &self,
664 _: &[StackString],
665 ) -> impl Future<Output = Result<HashMap<StackString, Self::Value>, Self::Error>>
666 {
667 async move {
668 let mut m = HashMap::new();
669 m.insert("HELLO".into(), "WORLD".into());
670 Ok(m)
671 }
672 }
673 }
674
675 struct QueryRoot;
676
677 #[Object]
678 impl QueryRoot {
679 async fn hello<'a>(
680 &self,
681 ctx: &Context<'a>,
682 ) -> Result<Option<StackString>, Infallible> {
683 let hello = ctx
684 .data::<DataLoader<StackStringLoader>>()
685 .unwrap()
686 .load_one("hello".into())
687 .await
688 .unwrap();
689 Ok(hello)
690 }
691 }
692
693 let expected_sdl = include_str!("../tests/data/sdl_file_compactstring.txt");
694
695 let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
696 .data(DataLoader::new(
697 StackStringLoader::new(),
698 tokio::task::spawn,
699 ))
700 .finish();
701 let sdl = schema.sdl();
702
703 assert_eq!(&sdl, expected_sdl);
704 }
705}