static_or_heap_string/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5use alloc::string::{String,ToString};
6use serde::*;
7use core::fmt;
8use core::cmp::Ordering;
9use core::hash::{Hash, Hasher};
10
11/// A string type that can either be a static string slice or an owned heap-allocated string.
12#[derive(Clone, Eq)]
13pub enum StaticOrHeapString {
14    Static(&'static str),
15    Heap(String),
16}
17
18impl PartialEq for StaticOrHeapString {
19    fn eq(&self, other: &Self) -> bool {
20        self.as_str() == other.as_str()
21    }
22}
23
24impl PartialOrd for StaticOrHeapString {
25    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
26        Some(self.as_str().cmp(other.as_str()))
27    }
28}
29
30impl Ord for StaticOrHeapString {
31    fn cmp(&self, other: &Self) -> Ordering {
32        self.as_str().cmp(other.as_str())
33    }
34}
35
36impl fmt::Debug for StaticOrHeapString {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(f, "{}", self.as_str())
39    }
40}
41
42impl<'de> Deserialize<'de> for StaticOrHeapString {
43    fn deserialize<D>(deserializer: D) -> Result<StaticOrHeapString, D::Error>
44    where
45        D: Deserializer<'de>,
46    {
47        let heap_key: String = Deserialize::deserialize(deserializer)?;
48        Ok(StaticOrHeapString::Heap(heap_key))
49    }
50}
51
52impl Serialize for StaticOrHeapString {
53    /// Serializes the string, ignoring whether it is static or heap-allocated.
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: serde::ser::Serializer,
57    {
58        serializer.serialize_str(self.as_str())
59    }
60}
61
62impl StaticOrHeapString {
63    /// Returns the string slice representation of the enum variant.
64    pub fn as_str(&self) -> &str {
65        match self {
66            StaticOrHeapString::Static(s) => s,
67            StaticOrHeapString::Heap(s) => s.as_str(),
68        }
69    }
70
71    /// Returns the mutable string slice representation of the enum variant.
72    pub fn as_mut_str(&mut self) -> &mut str {
73        match self {
74            StaticOrHeapString::Static(s) => {
75                // Convert the static str to a heap-allocated String for mutation
76                let heap_string = s.to_string();
77                *self = StaticOrHeapString::Heap(heap_string);
78                match self {
79                    StaticOrHeapString::Heap(s) => s.as_mut_str(),
80                    _ => unreachable!(),
81                }
82            }
83            StaticOrHeapString::Heap(s) => s.as_mut_str(),
84        }
85    }
86
87    /// Returns true if the string is empty.
88    pub fn is_empty(&self) -> bool {
89        self.as_str().is_empty()
90    }
91
92    /// Returns the length of the string.
93    pub fn len(&self) -> usize {
94        self.as_str().len()
95    }
96}
97
98impl Hash for StaticOrHeapString {
99    fn hash<H: Hasher>(&self, state: &mut H) {
100        self.as_str().hash(state);
101    }
102}
103
104impl From<&'static str> for StaticOrHeapString {
105    fn from(s: &'static str) -> Self {
106        StaticOrHeapString::Static(s)
107    }
108}
109
110impl From<String> for StaticOrHeapString {
111    fn from(s: String) -> Self {
112        StaticOrHeapString::Heap(s)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use serde_json;
120    use core::hash::Hash;
121    use core::hash::{BuildHasherDefault,BuildHasher};
122    use twox_hash::XxHash64;
123    use alloc::format;
124
125    #[test]
126    fn test_static_or_heap_string_can_serde_roundtrip() {
127        // Testing static string round-trip
128        let static_str = StaticOrHeapString::Static("hello");
129        let serialized = serde_json::to_string(&static_str).unwrap();
130        let deserialized: StaticOrHeapString = serde_json::from_str(&serialized).unwrap();
131        assert_eq!(static_str, deserialized);
132
133        // Testing heap string round-trip
134        let heap_str = StaticOrHeapString::Heap(String::from("world"));
135        let serialized = serde_json::to_string(&heap_str).unwrap();
136        let deserialized: StaticOrHeapString = serde_json::from_str(&serialized).unwrap();
137        assert_eq!(heap_str, deserialized);
138    }
139
140    #[test]
141    fn test_static_or_heap_string_partial_eq() {
142        let static_str1 = StaticOrHeapString::Static("hello");
143        let static_str2 = StaticOrHeapString::Static("hello");
144        let heap_str1 = StaticOrHeapString::Heap(String::from("hello"));
145        let heap_str2 = StaticOrHeapString::Heap(String::from("world"));
146
147        assert_eq!(static_str1, static_str2);
148        assert_eq!(static_str1, heap_str1);
149        assert_ne!(static_str1, heap_str2);
150    }
151
152    #[test]
153    fn test_static_or_heap_string_partial_ord() {
154        let static_str1 = StaticOrHeapString::Static("apple");
155        let static_str2 = StaticOrHeapString::Static("banana");
156        let heap_str1 = StaticOrHeapString::Heap(String::from("apple"));
157        let heap_str2 = StaticOrHeapString::Heap(String::from("banana"));
158
159        assert!(static_str1 < static_str2);
160        assert!(heap_str1 < heap_str2);
161        assert!(static_str1 <= heap_str1);
162        assert!(static_str2 > heap_str1);
163    }
164
165    #[test]
166    fn test_static_or_heap_string_ord() {
167        let static_str1 = StaticOrHeapString::Static("apple");
168        let heap_str1 = StaticOrHeapString::Heap(String::from("apple"));
169        let static_str2 = StaticOrHeapString::Static("banana");
170        let heap_str2 = StaticOrHeapString::Heap(String::from("banana"));
171
172        assert_eq!(static_str1.cmp(&heap_str1), Ordering::Equal);
173        assert_eq!(static_str1.cmp(&static_str2), Ordering::Less);
174        assert_eq!(heap_str2.cmp(&static_str1), Ordering::Greater);
175    }
176
177    #[test]
178    fn test_static_or_heap_string_debug() {
179        let static_str = StaticOrHeapString::Static("hello");
180        let heap_str = StaticOrHeapString::Heap(String::from("world"));
181
182        assert_eq!(format!("{:?}", static_str), "hello");
183        assert_eq!(format!("{:?}", heap_str), "world");
184    }
185
186    #[test]
187    fn test_static_or_heap_string_as_str() {
188        let static_str = StaticOrHeapString::Static("hello");
189        let heap_str = StaticOrHeapString::Heap(String::from("world"));
190
191        assert_eq!(static_str.as_str(), "hello");
192        assert_eq!(heap_str.as_str(), "world");
193    }
194
195    #[test]
196    fn test_static_or_heap_string_clone() {
197        let static_str = StaticOrHeapString::Static("hello");
198        let heap_str = StaticOrHeapString::Heap(String::from("world"));
199
200        let cloned_static = static_str.clone();
201        let cloned_heap = heap_str.clone();
202
203        assert_eq!(static_str, cloned_static);
204        assert_eq!(heap_str, cloned_heap);
205    }
206
207    #[test]
208    fn test_static_or_heap_string_hash() {
209        type MyHasher = BuildHasherDefault<XxHash64>;
210
211        let static_str = StaticOrHeapString::Static("hello");
212        let heap_str = StaticOrHeapString::Heap(String::from("hello"));
213
214        let mut hasher1 = MyHasher::default().build_hasher();
215        static_str.hash(&mut hasher1);
216        let hash1 = hasher1.finish();
217
218        let mut hasher2 = MyHasher::default().build_hasher();
219        heap_str.hash(&mut hasher2);
220        let hash2 = hasher2.finish();
221
222        assert_eq!(hash1, hash2);
223    }
224}