1#![allow(unsafe_code)]
41
42use std::{
43 borrow::Borrow,
44 cmp::Ordering,
45 ffi::{CStr, c_char},
46 fmt::{Debug, Display},
47 hash::{Hash, Hasher},
48 ops::Deref,
49};
50
51use serde::{Deserialize, Deserializer, Serialize, Serializer};
52
53use crate::correctness::FAILED;
54
55pub const STACKSTR_CAPACITY: usize = 36;
57
58const STACKSTR_BUFFER_SIZE: usize = STACKSTR_CAPACITY + 1;
60
61#[derive(Clone, Copy)]
78#[repr(C)]
79pub struct StackStr {
80 value: [u8; 37], len: u8,
84}
85
86impl StackStr {
87 pub const MAX_LEN: usize = STACKSTR_CAPACITY;
89
90 #[must_use]
99 pub fn new(s: &str) -> Self {
100 Self::new_checked(s).expect(FAILED)
101 }
102
103 pub fn new_checked(s: &str) -> anyhow::Result<Self> {
112 if s.is_empty() {
113 anyhow::bail!("String is empty");
114 }
115
116 if s.len() > STACKSTR_CAPACITY {
117 anyhow::bail!(
118 "String exceeds maximum length of {} characters, was {}",
119 STACKSTR_CAPACITY,
120 s.len()
121 );
122 }
123
124 if !s.is_ascii() {
125 anyhow::bail!("String contains non-ASCII character");
126 }
127
128 let bytes = s.as_bytes();
129 if bytes.contains(&0) {
130 anyhow::bail!("String contains interior NUL byte");
131 }
132
133 if bytes.iter().all(|b| b.is_ascii_whitespace()) {
134 anyhow::bail!("String contains only whitespace");
135 }
136
137 let mut value = [0u8; STACKSTR_BUFFER_SIZE];
138 value[..s.len()].copy_from_slice(bytes);
139 Ok(Self {
142 value,
143 len: s.len() as u8,
144 })
145 }
146
147 pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
156 let bytes = if bytes.last() == Some(&0) {
158 &bytes[..bytes.len() - 1]
159 } else {
160 bytes
161 };
162
163 let s = std::str::from_utf8(bytes).map_err(|e| anyhow::anyhow!("Invalid UTF-8: {e}"))?;
164
165 Self::new_checked(s)
166 }
167
168 #[must_use]
182 pub unsafe fn from_c_ptr(ptr: *const c_char) -> Self {
183 let cstr = unsafe { CStr::from_ptr(ptr) };
185 let s = cstr.to_str().expect("Invalid UTF-8 in C string");
186 Self::new(s)
187 }
188
189 #[must_use]
198 pub unsafe fn from_c_ptr_checked(ptr: *const c_char) -> Option<Self> {
199 let cstr = unsafe { CStr::from_ptr(ptr) };
201 let s = cstr.to_str().ok()?;
202 Self::new_checked(s).ok()
203 }
204
205 #[inline]
209 #[must_use]
210 pub fn as_str(&self) -> &str {
211 debug_assert!(
212 self.len as usize <= STACKSTR_CAPACITY,
213 "StackStr len {} exceeds capacity {}",
214 self.len,
215 STACKSTR_CAPACITY
216 );
217 unsafe { std::str::from_utf8_unchecked(&self.value[..self.len as usize]) }
220 }
221
222 #[inline]
226 #[must_use]
227 pub const fn len(&self) -> usize {
228 self.len as usize
229 }
230
231 #[inline]
233 #[must_use]
234 pub const fn is_empty(&self) -> bool {
235 self.len == 0
236 }
237
238 #[inline]
240 #[must_use]
241 pub const fn as_ptr(&self) -> *const c_char {
242 self.value.as_ptr() as *const c_char
243 }
244
245 #[inline]
247 #[must_use]
248 pub fn as_cstr(&self) -> &CStr {
249 debug_assert!(
250 self.len as usize <= STACKSTR_CAPACITY,
251 "StackStr len {} exceeds capacity {}",
252 self.len,
253 STACKSTR_CAPACITY
254 );
255 debug_assert!(
256 self.value[self.len as usize] == 0,
257 "StackStr missing null terminator at position {}",
258 self.len
259 );
260 unsafe { CStr::from_bytes_with_nul_unchecked(&self.value[..=self.len as usize]) }
264 }
265}
266
267impl PartialEq for StackStr {
268 #[inline]
269 fn eq(&self, other: &Self) -> bool {
270 self.len == other.len
271 && self.value[..self.len as usize] == other.value[..other.len as usize]
272 }
273}
274
275impl Eq for StackStr {}
276
277impl Hash for StackStr {
278 #[inline]
279 fn hash<H: Hasher>(&self, state: &mut H) {
280 self.value[..self.len as usize].hash(state);
282 }
283}
284
285impl Ord for StackStr {
286 fn cmp(&self, other: &Self) -> Ordering {
287 self.as_str().cmp(other.as_str())
288 }
289}
290
291impl PartialOrd for StackStr {
292 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
293 Some(self.cmp(other))
294 }
295}
296
297impl Display for StackStr {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 f.write_str(self.as_str())
300 }
301}
302
303impl Debug for StackStr {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 write!(f, "{:?}", self.as_str())
306 }
307}
308
309impl Serialize for StackStr {
310 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
311 serializer.serialize_str(self.as_str())
312 }
313}
314
315impl<'de> Deserialize<'de> for StackStr {
316 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
317 let s = <&str>::deserialize(deserializer)?;
318 Self::new_checked(s).map_err(serde::de::Error::custom)
319 }
320}
321
322impl From<&str> for StackStr {
323 fn from(s: &str) -> Self {
324 Self::new(s)
325 }
326}
327
328impl AsRef<str> for StackStr {
329 fn as_ref(&self) -> &str {
330 self.as_str()
331 }
332}
333
334impl Borrow<str> for StackStr {
335 fn borrow(&self) -> &str {
336 self.as_str()
337 }
338}
339
340impl Default for StackStr {
341 fn default() -> Self {
346 Self {
347 value: [0u8; STACKSTR_BUFFER_SIZE],
348 len: 0,
349 }
350 }
351}
352
353impl Deref for StackStr {
354 type Target = str;
355
356 fn deref(&self) -> &Self::Target {
357 self.as_str()
358 }
359}
360
361impl PartialEq<&str> for StackStr {
362 fn eq(&self, other: &&str) -> bool {
363 self.as_str() == *other
364 }
365}
366
367impl PartialEq<str> for StackStr {
368 fn eq(&self, other: &str) -> bool {
369 self.as_str() == other
370 }
371}
372
373impl TryFrom<&[u8]> for StackStr {
374 type Error = anyhow::Error;
375
376 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
377 Self::from_bytes(bytes)
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use std::hash::{DefaultHasher, Hasher};
384
385 use ahash::AHashMap;
386 use rstest::rstest;
387
388 use super::*;
389
390 #[rstest]
391 fn test_new_valid() {
392 let s = StackStr::new("hello");
393 assert_eq!(s.as_str(), "hello");
394 assert_eq!(s.len(), 5);
395 assert!(!s.is_empty());
396 }
397
398 #[rstest]
399 fn test_max_length() {
400 let input = "x".repeat(36);
401 let s = StackStr::new(&input);
402 assert_eq!(s.len(), 36);
403 assert_eq!(s.as_str(), input);
404 }
405
406 #[rstest]
407 #[should_panic]
408 fn test_exceeds_max_length() {
409 let input = "x".repeat(37);
410 let _ = StackStr::new(&input);
411 }
412
413 #[rstest]
414 #[should_panic]
415 fn test_empty_string() {
416 let _ = StackStr::new("");
417 }
418
419 #[rstest]
420 #[should_panic]
421 fn test_whitespace_only() {
422 let _ = StackStr::new(" ");
423 }
424
425 #[rstest]
426 #[should_panic]
427 fn test_non_ascii() {
428 let _ = StackStr::new("hello\u{1F600}"); }
430
431 #[rstest]
432 #[should_panic]
433 fn test_interior_nul_byte() {
434 let _ = StackStr::new("abc\0def");
435 }
436
437 #[rstest]
438 fn test_interior_nul_byte_checked() {
439 let result = StackStr::new_checked("abc\0def");
440 assert!(result.is_err());
441 assert!(result.unwrap_err().to_string().contains("NUL"));
442 }
443
444 #[rstest]
445 fn test_from_c_ptr_checked_valid() {
446 let cstring = std::ffi::CString::new("hello").unwrap();
447 let s = unsafe { StackStr::from_c_ptr_checked(cstring.as_ptr()) };
448 assert!(s.is_some());
449 assert_eq!(s.unwrap().as_str(), "hello");
450 }
451
452 #[rstest]
453 fn test_from_c_ptr_checked_too_long() {
454 let long = "x".repeat(37);
455 let cstring = std::ffi::CString::new(long).unwrap();
456 let s = unsafe { StackStr::from_c_ptr_checked(cstring.as_ptr()) };
457 assert!(s.is_none());
458 }
459
460 #[rstest]
461 fn test_equality() {
462 let a = StackStr::new("test");
463 let b = StackStr::new("test");
464 let c = StackStr::new("other");
465 assert_eq!(a, b);
466 assert_ne!(a, c);
467 }
468
469 #[rstest]
470 fn test_hash_consistency() {
471 use std::hash::DefaultHasher;
472
473 let a = StackStr::new("test");
474 let b = StackStr::new("test");
475
476 let hash_a = {
477 let mut h = DefaultHasher::new();
478 a.hash(&mut h);
479 h.finish()
480 };
481 let hash_b = {
482 let mut h = DefaultHasher::new();
483 b.hash(&mut h);
484 h.finish()
485 };
486
487 assert_eq!(hash_a, hash_b);
488 }
489
490 #[rstest]
491 fn test_hashmap_usage() {
492 let mut map = AHashMap::new();
493 map.insert(StackStr::new("key1"), 1);
494 map.insert(StackStr::new("key2"), 2);
495
496 assert_eq!(map.get(&StackStr::new("key1")), Some(&1));
497 assert_eq!(map.get(&StackStr::new("key2")), Some(&2));
498 assert_eq!(map.get(&StackStr::new("key3")), None);
499 }
500
501 #[rstest]
502 fn test_ordering() {
503 let a = StackStr::new("aaa");
504 let b = StackStr::new("bbb");
505 assert!(a < b);
506 assert!(b > a);
507 }
508
509 #[rstest]
510 fn test_c_compatibility() {
511 let s = StackStr::new("test");
512 let cstr = s.as_cstr();
513 assert_eq!(cstr.to_str().unwrap(), "test");
514 }
515
516 #[rstest]
517 fn test_as_ptr() {
518 let s = StackStr::new("test");
519 let ptr = s.as_ptr();
520 assert!(!ptr.is_null());
521
522 let cstr = unsafe { CStr::from_ptr(ptr) };
523 assert_eq!(cstr.to_str().unwrap(), "test");
524 }
525
526 #[rstest]
527 fn test_from_bytes() {
528 let s = StackStr::from_bytes(b"hello").unwrap();
529 assert_eq!(s.as_str(), "hello");
530 }
531
532 #[rstest]
533 fn test_from_bytes_with_null() {
534 let s = StackStr::from_bytes(b"hello\0").unwrap();
535 assert_eq!(s.as_str(), "hello");
536 }
537
538 #[rstest]
539 fn test_serde_roundtrip() {
540 let original = StackStr::new("test123");
541 let json = serde_json::to_string(&original).unwrap();
542 assert_eq!(json, "\"test123\"");
543
544 let deserialized: StackStr = serde_json::from_str(&json).unwrap();
545 assert_eq!(original, deserialized);
546 }
547
548 #[rstest]
549 fn test_display() {
550 let s = StackStr::new("hello");
551 assert_eq!(format!("{s}"), "hello");
552 }
553
554 #[rstest]
555 fn test_debug() {
556 let s = StackStr::new("hello");
557 assert_eq!(format!("{s:?}"), "\"hello\"");
558 }
559
560 #[rstest]
561 fn test_from_str() {
562 let s: StackStr = "hello".into();
563 assert_eq!(s.as_str(), "hello");
564 }
565
566 #[rstest]
567 fn test_as_ref() {
568 let s = StackStr::new("hello");
569 let r: &str = s.as_ref();
570 assert_eq!(r, "hello");
571 }
572
573 #[rstest]
574 fn test_borrow() {
575 let s = StackStr::new("hello");
576 let b: &str = s.borrow();
577 assert_eq!(b, "hello");
578 }
579
580 #[rstest]
581 fn test_default() {
582 let s = StackStr::default();
583 assert!(s.is_empty());
584 assert_eq!(s.len(), 0);
585 }
586
587 #[rstest]
588 fn test_copy_semantics() {
589 let a = StackStr::new("test");
590 let b = a; assert_eq!(a, b); }
593
594 #[rstest]
595 #[case("BINANCE")]
596 #[case("ETH-PERP")]
597 #[case("O-20231215-001")]
598 #[case("123456789012345678901234567890123456")] fn test_valid_identifiers(#[case] s: &str) {
600 let stack_str = StackStr::new(s);
601 assert_eq!(stack_str.as_str(), s);
602 }
603
604 #[rstest]
605 fn test_single_char() {
606 let s = StackStr::new("x");
607 assert_eq!(s.len(), 1);
608 assert_eq!(s.as_str(), "x");
609 }
610
611 #[rstest]
612 fn test_length_35() {
613 let input = "x".repeat(35);
614 let s = StackStr::new(&input);
615 assert_eq!(s.len(), 35);
616 }
617
618 #[rstest]
619 fn test_length_36_exact() {
620 let input = "x".repeat(36);
621 let s = StackStr::new(&input);
622 assert_eq!(s.len(), 36);
623 assert_eq!(s.as_str(), input);
624 }
625
626 #[rstest]
627 fn test_length_37_rejected() {
628 let input = "x".repeat(37);
629 let result = StackStr::new_checked(&input);
630 assert!(result.is_err());
631 assert!(result.unwrap_err().to_string().contains("exceeds"));
632 }
633
634 #[rstest]
635 fn test_struct_size() {
636 assert_eq!(std::mem::size_of::<StackStr>(), 38);
637 }
638
639 #[rstest]
640 fn test_value_field_at_offset_zero() {
641 let s = StackStr::new("hello");
642 let struct_ptr = &s as *const StackStr as *const u8;
643 let first_byte = unsafe { *struct_ptr };
644 assert_eq!(first_byte, b'h');
645 }
646
647 #[rstest]
648 fn test_null_terminator_present() {
649 let s = StackStr::new("test");
650 let ptr = s.as_ptr();
651 let null_byte = unsafe { *ptr.offset(4) };
653 assert_eq!(null_byte, 0);
654 }
655
656 #[rstest]
657 fn test_from_bytes_empty() {
658 let result = StackStr::from_bytes(b"");
659 assert!(result.is_err());
660 }
661
662 #[rstest]
663 fn test_from_bytes_interior_nul() {
664 let result = StackStr::from_bytes(b"abc\0def");
665 assert!(result.is_err());
666 assert!(result.unwrap_err().to_string().contains("NUL"));
667 }
668
669 #[rstest]
670 fn test_from_bytes_non_ascii() {
671 let result = StackStr::from_bytes(&[0x80, 0x81]); assert!(result.is_err());
673 }
674
675 #[rstest]
676 fn test_from_bytes_too_long() {
677 let bytes = [b'x'; 55];
678 let result = StackStr::from_bytes(&bytes);
679 assert!(result.is_err());
680 }
681
682 #[rstest]
683 fn test_from_bytes_whitespace_only() {
684 let result = StackStr::from_bytes(b" ");
685 assert!(result.is_err());
686 }
687
688 #[rstest]
689 fn test_hash_differs_for_different_content() {
690 let a = StackStr::new("abc");
691 let b = StackStr::new("xyz");
692
693 let hash_a = {
694 let mut h = DefaultHasher::new();
695 a.hash(&mut h);
696 h.finish()
697 };
698 let hash_b = {
699 let mut h = DefaultHasher::new();
700 b.hash(&mut h);
701 h.finish()
702 };
703
704 assert_ne!(hash_a, hash_b);
705 }
706
707 #[rstest]
708 fn test_hash_ignores_padding() {
709 let a = StackStr::new("test");
710 let b = StackStr::new("test");
711
712 let hash_a = {
713 let mut h = DefaultHasher::new();
714 a.hash(&mut h);
715 h.finish()
716 };
717 let hash_b = {
718 let mut h = DefaultHasher::new();
719 b.hash(&mut h);
720 h.finish()
721 };
722
723 assert_eq!(hash_a, hash_b);
724 }
725
726 #[rstest]
727 fn test_serde_deserialize_too_long() {
728 let long = format!("\"{}\"", "x".repeat(55));
729 let result: Result<StackStr, _> = serde_json::from_str(&long);
730 assert!(result.is_err());
731 }
732
733 #[rstest]
734 fn test_serde_deserialize_empty() {
735 let result: Result<StackStr, _> = serde_json::from_str("\"\"");
736 assert!(result.is_err());
737 }
738
739 #[rstest]
740 fn test_serde_deserialize_non_ascii() {
741 let result: Result<StackStr, _> = serde_json::from_str("\"hello\u{1F600}\"");
742 assert!(result.is_err());
743 }
744
745 #[rstest]
746 #[case("!@#$%^&*()")]
747 #[case("hello-world_123")]
748 #[case("a.b.c.d")]
749 #[case("key=value")]
750 #[case("path/to/file")]
751 #[case("[bracket]")]
752 #[case("{curly}")]
753 fn test_special_ascii_chars(#[case] s: &str) {
754 let stack_str = StackStr::new(s);
755 assert_eq!(stack_str.as_str(), s);
756 }
757
758 #[rstest]
759 fn test_ascii_control_chars_tab() {
760 let result = StackStr::new_checked("a\tb");
762 assert!(result.is_ok());
763 assert_eq!(result.unwrap().as_str(), "a\tb");
764 }
765
766 #[rstest]
767 fn test_ordering_same_prefix_different_length() {
768 let short = StackStr::new("abc");
769 let long = StackStr::new("abcd");
770 assert!(short < long);
771 }
772
773 #[rstest]
774 fn test_ordering_case_sensitive() {
775 let upper = StackStr::new("ABC");
776 let lower = StackStr::new("abc");
777 assert!(upper < lower);
779 }
780
781 #[rstest]
782 fn test_partial_cmp_returns_some() {
783 let a = StackStr::new("test");
784 let b = StackStr::new("test");
785 assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Equal));
786 }
787
788 #[rstest]
789 fn test_new_checked_error_empty() {
790 let err = StackStr::new_checked("").unwrap_err();
791 assert!(err.to_string().contains("empty"));
792 }
793
794 #[rstest]
795 fn test_new_checked_error_whitespace() {
796 let err = StackStr::new_checked(" ").unwrap_err();
797 assert!(err.to_string().contains("whitespace"));
798 }
799
800 #[rstest]
801 fn test_new_checked_error_too_long() {
802 let err = StackStr::new_checked(&"x".repeat(55)).unwrap_err();
803 assert!(err.to_string().contains("exceeds"));
804 }
805
806 #[rstest]
807 fn test_new_checked_error_non_ascii() {
808 let err = StackStr::new_checked("hello\u{1F600}").unwrap_err();
809 assert!(err.to_string().contains("non-ASCII"));
810 }
811
812 #[rstest]
813 fn test_new_checked_error_interior_nul() {
814 let err = StackStr::new_checked("abc\0def").unwrap_err();
815 assert!(err.to_string().contains("NUL"));
816 }
817
818 #[rstest]
819 fn test_clone_equals_original() {
820 let a = StackStr::new("test");
821 #[allow(clippy::clone_on_copy)]
822 let b = a.clone();
823 assert_eq!(a, b);
824 }
825
826 #[rstest]
827 fn test_deref() {
828 let s = StackStr::new("hello");
829 assert!(s.starts_with("hell"));
830 assert_eq!(s.len(), 5);
831 }
832
833 #[rstest]
834 fn test_partial_eq_str_literal() {
835 let s = StackStr::new("hello");
836 assert!(s == "hello");
837 assert!(s != "world");
838 }
839
840 #[rstest]
841 fn test_try_from_bytes() {
842 let s: StackStr = b"hello".as_slice().try_into().unwrap();
843 assert_eq!(s.as_str(), "hello");
844 }
845}