tempest_core/
tempest_str.rs1use std::borrow::Cow;
2
3use serde::{Deserialize, Serialize};
4
5use crate::utils::contains_null;
6
7#[derive(Debug, Display, Error)]
9pub enum TempestStrError {
10 InputContainsNullByte,
13}
14
15#[derive(
28 Debug, Display, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
29)]
30pub struct TempestStr<'a>(Cow<'a, str>);
31
32impl<'a> TempestStr<'a> {
33 pub fn from_borrowed(s: &'a str) -> Result<TempestStr<'a>, TempestStrError> {
39 if contains_null(s) {
40 return Err(TempestStrError::InputContainsNullByte);
41 }
42 Ok(TempestStr(s.into()))
43 }
44
45 pub const unsafe fn from_borrowed_unchecked(s: &'a str) -> TempestStr<'a> {
51 Self(Cow::Borrowed(s))
52 }
53
54 pub fn from_owned(s: String) -> Result<TempestStr<'static>, TempestStrError> {
60 if contains_null(&s) {
61 return Err(TempestStrError::InputContainsNullByte);
62 }
63 Ok(TempestStr(s.into()))
64 }
65
66 pub fn into_owned(self) -> TempestStr<'static> {
69 TempestStr(self.0.into_owned().into())
70 }
71}
72
73impl TryFrom<String> for TempestStr<'static> {
74 type Error = TempestStrError;
75
76 fn try_from(s: String) -> Result<Self, Self::Error> {
77 TempestStr::from_owned(s)
78 }
79}
80
81impl From<&'static str> for TempestStr<'static> {
85 fn from(s: &'static str) -> Self {
86 TempestStr::from_borrowed(s).expect("static str must not contain null bytes")
87 }
88}
89
90impl From<u32> for TempestStr<'static> {
91 fn from(value: u32) -> Self {
92 TempestStr::from_owned(value.to_string())
93 .expect("stringified number does not contain null bytes")
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn assert_valid(s: &str) {
102 let ts = match TempestStr::from_borrowed(s) {
103 Ok(ts) => ts,
104 Err(e) => panic!("{} should be valid, but got error: {}", s, e),
105 };
106 assert_eq!(s, *ts);
107 }
108
109 fn assert_invalid(s: &str) {
110 assert!(
111 TempestStr::from_borrowed(s).is_err(),
112 "{} should not be valid",
113 s
114 );
115 assert!(
116 TempestStr::from_owned(s.to_owned()).is_err(),
117 "{} should not be valid, when converting into owned",
118 s
119 );
120 }
121
122 #[test]
123 fn test_tempest_str_valid() {
124 [
125 "sup",
126 "world",
127 "", "tempest",
129 "juice",
130 "eventual consistency",
131 ]
132 .into_iter()
133 .for_each(assert_valid);
134 }
135
136 #[test]
137 fn test_tempest_str_invalid() {
138 [
139 "\x00",
140 "\x00hello",
141 "hel\x00lo",
142 "hello\x00",
143 "\x00\x00\x00",
144 ]
145 .into_iter()
146 .for_each(assert_invalid);
147 }
148
149 #[test]
150 fn test_into_owned_produces_static_lifetime() {
151 let s = String::from("hello");
152 let ts: TempestStr<'static> = TempestStr::from_owned(s).unwrap().into_owned();
153 assert_eq!(*ts, "hello");
154 }
155}