havok_types/
cstring.rs

1use crate::{NULL_STR, StringPtr, lib::*};
2
3/// # CString
4/// - binary data(.hkx): null-terminated string
5/// - XML: `&str`
6///
7/// # C++ Info
8/// - name: `char*`
9/// - type_size: ` 4`(x86)/` 8`(x86_64)
10/// - align: ` 4`(x86)/` 8`(x86_64)
11///
12/// # Null representation
13/// - hkx: It will not be written to the binary data.
14/// - XML: `\u{2400}`.
15/// - Rust: If it is null then [`Option::None`].(To eliminate the risk of always being null ptr by type)
16///
17/// # Deserialization patterns
18/// - hkx(`Vec<u8>`)   -> Struct([`str`] in `Cow<'_, str>`) => non copy
19/// - xml([`String`])  -> Struct([`str`] in `Cow<'_, str>`) => non copy
20/// - json: [`String`] -> Struct([`str`] in `Cow<'_, str>`) => non copy
21///
22/// # Serialization is alloc
23/// Struct([`str`]) -> (alloc [`String`])
24///
25/// [`str`]: https://doc.rust-lang.org/std/primitive.str.html
26#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
27#[cfg_attr(feature = "json_schema", schemars(transparent))]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(feature = "serde", serde(transparent))]
30#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub struct CString<'a> {
32    #[cfg_attr(feature = "serde", serde(borrow))]
33    inner: Option<Cow<'a, str>>,
34}
35
36impl<'a> CString<'a> {
37    /// Create a new `CString`
38    #[inline]
39    pub const fn new(inner: Option<Cow<'a, str>>) -> Self {
40        Self { inner }
41    }
42
43    /// Get inner value.
44    #[inline]
45    pub fn into_inner(self) -> Option<Cow<'a, str>> {
46        self.inner
47    }
48
49    /// Get inner ref.
50    #[inline]
51    pub const fn get_ref(&self) -> &Option<Cow<'a, str>> {
52        &self.inner
53    }
54
55    /// Cast [`str`] with non copying.
56    #[allow(clippy::should_implement_trait)]
57    #[inline]
58    pub const fn from_str(s: &'a str) -> Self {
59        Self {
60            inner: Some(Cow::Borrowed(s)),
61        }
62    }
63
64    /// Inner to [`Self`]
65    #[inline]
66    pub const fn from_option(s: Option<Cow<'a, str>>) -> Self {
67        Self { inner: s }
68    }
69
70    /// Null pointer or not?
71    ///
72    /// This indicates that no binary data was present.
73    #[inline]
74    pub const fn is_null(&self) -> bool {
75        self.get_ref().is_none()
76    }
77
78    /// Should the data pointed to by the pointer be written to the binary data or not?
79    ///
80    /// This is an invalid value or not.
81    #[inline]
82    pub fn should_write_binary(&self) -> bool {
83        match self.get_ref() {
84            Some(s) => {
85                if s.is_empty() || s == NULL_STR {
86                    return false;
87                };
88                true
89            }
90            _ => false,
91        }
92    }
93
94    /// Gets a reference as [`&str`].
95    #[inline]
96    pub fn as_str(&self) -> &str {
97        self.inner.as_ref().map_or(NULL_STR, |s| s.as_ref())
98    }
99}
100
101impl fmt::Display for CString<'_> {
102    #[inline]
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "{}", self.as_str())
105    }
106}
107
108impl PartialEq<str> for CString<'_> {
109    #[inline]
110    fn eq(&self, other: &str) -> bool {
111        self.as_str() == other
112    }
113}
114
115impl PartialEq<&str> for CString<'_> {
116    #[inline]
117    fn eq(&self, other: &&str) -> bool {
118        self.as_str() == *other
119    }
120}
121
122impl PartialEq<String> for CString<'_> {
123    #[inline]
124    fn eq(&self, other: &String) -> bool {
125        self.as_str() == other.as_str()
126    }
127}
128
129impl<'a> From<&'a str> for CString<'a> {
130    #[inline]
131    fn from(value: &'a str) -> Self {
132        Self::from_str(value)
133    }
134}
135
136impl<'a> From<CString<'a>> for StringPtr<'a> {
137    #[inline]
138    fn from(value: CString<'a>) -> Self {
139        Self::from_option(value.into_inner())
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::borrow::Cow;
147
148    #[test]
149    fn test_is_null() {
150        let cstring = CString::new(None);
151        assert!(cstring.is_null());
152
153        let cstring_with_str = CString::from_str("test");
154        assert!(!cstring_with_str.is_null());
155    }
156
157    #[test]
158    fn test_should_write_binary() {
159        let cstring = CString::new(None);
160        assert!(!cstring.should_write_binary());
161
162        let empty_cstring = CString::from_str("");
163        assert!(!empty_cstring.should_write_binary());
164
165        let null_str_cstring = CString::from_str(NULL_STR);
166        assert!(!null_str_cstring.should_write_binary());
167
168        let valid_cstring = CString::from_str("valid");
169        assert!(valid_cstring.should_write_binary());
170    }
171
172    #[test]
173    fn test_display() {
174        let null_cstring = CString::new(None);
175        assert_eq!(null_cstring, NULL_STR);
176
177        let cstring = CString::from_str("display test");
178        assert_eq!(cstring, "display test");
179    }
180
181    #[test]
182    fn test_from_str_conversion() {
183        let cstring: CString = "test string".into();
184        assert_eq!(cstring.get_ref(), &Some(Cow::Borrowed("test string")));
185    }
186
187    #[test]
188    fn test_to_string_ptr() {
189        let cstring = CString::from_str("test");
190        let string_ptr: StringPtr = cstring.clone().into();
191        assert_eq!(string_ptr.get_ref(), cstring.get_ref());
192    }
193
194    #[cfg(feature = "serde")]
195    #[test]
196    fn test_serialization() {
197        let cstring = CString::from_str("serialize me");
198        let serialized = serde_json::to_string(&cstring).unwrap();
199        assert_eq!(serialized, "\"serialize me\"");
200
201        let null_cstring = CString::new(None);
202        let serialized_null = serde_json::to_string(&null_cstring).unwrap();
203        assert_eq!(serialized_null, "null");
204    }
205
206    #[cfg(feature = "serde")]
207    #[test]
208    fn test_deserialization() {
209        let json_data = "\"deserialize me\"";
210        let deserialized: CString = serde_json::from_str(json_data).unwrap();
211        assert_eq!(
212            deserialized.get_ref(),
213            &Some(Cow::Borrowed("deserialize me"))
214        );
215
216        let json_null = "null";
217        let deserialized_null: CString = serde_json::from_str(json_null).unwrap();
218        assert!(deserialized_null.is_null());
219    }
220}