Skip to main content

hapi_rs/
stringhandle.rs

1//! String handling utilities and iterators
2use std::ffi::{CStr, CString};
3use std::fmt::Formatter;
4
5use crate::errors::Result;
6use crate::session::Session;
7
8// StringArray iterators SAFETY: Are Houdini strings expected to be valid utf? Maybe revisit.
9
10/// A handle to a string returned by some api.
11/// Then the String can be retrieved with [`Session::get_string`]
12#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13#[repr(transparent)]
14pub struct StringHandle(pub(crate) i32);
15
16/// Holds a contiguous array of bytes where each individual string value is null-separated.
17/// You can choose how to iterate over it by calling a corresponding iter_* function.
18/// The `Debug` impl has an alternative `{:#?}` representation, which prints as a vec of strings.
19#[derive(Clone, PartialEq)]
20pub struct StringArray(pub Vec<u8>);
21
22impl std::fmt::Debug for StringArray {
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        if f.alternate() {
25            let strings = self.iter_str().collect::<Vec<_>>();
26            strings.fmt(f)
27        } else {
28            let count = bytecount::count(self.0.as_slice(), b'\0');
29            write!(f, "StringArray[num_strings = {count}]")
30        }
31    }
32}
33
34/// Iterator over &str, returned from `StringArray::iter_str()`
35pub struct StringIter<'a> {
36    inner: &'a [u8],
37}
38
39/// Consuming iterator over String, returned from `StringArray::into_iter()`
40pub struct OwnedStringIter {
41    inner: Vec<u8>,
42    cursor: usize,
43}
44
45impl Iterator for OwnedStringIter {
46    type Item = String;
47
48    fn next(&mut self) -> Option<Self::Item> {
49        match self
50            .inner
51            .iter()
52            .skip(self.cursor)
53            .position(|b| *b == b'\0')
54        {
55            None => None,
56            Some(pos) => {
57                let idx = self.cursor + pos;
58                let ret = &self.inner[self.cursor..idx];
59                self.cursor = idx + 1;
60                Some(String::from_utf8_lossy(ret).to_string())
61            }
62        }
63    }
64}
65
66/// Iterator over `CStrings` returned from `StringArray::iter_cstr()`
67pub struct CStringIter<'a> {
68    inner: &'a [u8],
69}
70
71impl<'a> StringArray {
72    /// Create an empty `StringArray`
73    #[must_use]
74    pub fn empty() -> StringArray {
75        StringArray(vec![])
76    }
77    /// Return an iterator over &str
78    #[must_use]
79    pub fn iter_str(&'a self) -> StringIter<'a> {
80        StringIter { inner: &self.0 }
81    }
82
83    /// Return an iterator over &`CStr`
84    #[must_use]
85    pub fn iter_cstr(&'a self) -> CStringIter<'a> {
86        CStringIter { inner: &self.0 }
87    }
88
89    #[inline]
90    #[must_use]
91    pub fn is_empty(&self) -> bool {
92        self.0.is_empty()
93    }
94
95    /// Reference to underlying bytes
96    #[inline]
97    #[must_use]
98    pub fn bytes(&self) -> &[u8] {
99        self.0.as_slice()
100    }
101}
102
103impl From<StringArray> for Vec<String> {
104    fn from(a: StringArray) -> Self {
105        a.into_iter().collect()
106    }
107}
108
109impl<'a> Iterator for StringIter<'a> {
110    type Item = &'a str;
111
112    fn next(&mut self) -> Option<Self::Item> {
113        match self.inner.iter().position(|c| *c == b'\0') {
114            None => None,
115            Some(idx) => {
116                let ret = &self.inner[..idx];
117                self.inner = &self.inner[idx + 1..];
118                unsafe { Some(std::str::from_utf8_unchecked(ret)) }
119            }
120        }
121    }
122}
123
124impl<'a> Iterator for CStringIter<'a> {
125    type Item = &'a CStr;
126
127    fn next(&mut self) -> Option<Self::Item> {
128        match self.inner.iter().position(|c| *c == b'\0') {
129            None => None,
130            Some(idx) => {
131                let ret = &self.inner[..=idx];
132                self.inner = &self.inner[idx + 1..];
133                unsafe { Some(CStr::from_bytes_with_nul_unchecked(ret)) }
134            }
135        }
136    }
137}
138
139impl IntoIterator for StringArray {
140    type Item = String;
141    type IntoIter = OwnedStringIter;
142
143    fn into_iter(self) -> Self::IntoIter {
144        OwnedStringIter {
145            inner: self.0,
146            cursor: 0,
147        }
148    }
149}
150
151pub(crate) fn get_string(handle: StringHandle, session: &Session) -> Result<String> {
152    let bytes = crate::ffi::get_string_bytes(session, handle)?;
153    String::from_utf8(bytes).map_err(crate::HapiError::from)
154}
155
156pub(crate) fn get_cstring(handle: StringHandle, session: &Session) -> Result<CString> {
157    let bytes = crate::ffi::get_string_bytes(session, handle)?;
158    CString::new(bytes).map_err(crate::HapiError::from)
159}
160
161pub fn get_string_array(handles: &[StringHandle], session: &Session) -> Result<StringArray> {
162    let _lock = session.lock();
163    let length = crate::ffi::get_string_batch_size(handles, session)?;
164    let bytes = if length > 0 {
165        crate::ffi::get_string_batch_bytes(length, session)?
166    } else {
167        vec![]
168    };
169    Ok(StringArray(bytes))
170}
171
172#[cfg(test)]
173mod tests {
174    use super::StringArray;
175
176    #[test]
177    fn string_array_empty_and_bytes() {
178        let arr = StringArray::empty();
179        assert!(arr.is_empty());
180        assert!(arr.bytes().is_empty());
181    }
182
183    #[test]
184    fn string_array_debug() {
185        let arr = StringArray(b"One\0Two\0".to_vec());
186        assert_eq!(format!("{arr:?}"), "StringArray[num_strings = 2]");
187        let alt = format!("{arr:#?}");
188        assert!(alt.contains("One") && alt.contains("Two"));
189    }
190
191    #[test]
192    fn string_array_into_vec_string() {
193        let arr = StringArray(b"One\0Two\0Three\0".to_vec());
194        let v: Vec<String> = arr.into();
195        assert_eq!(v, vec!["One", "Two", "Three"]);
196    }
197
198    #[test]
199    fn string_array_iter_cstr() {
200        let arr = StringArray(b"One\0Two\0Three\0".to_vec());
201        let v: Vec<_> = arr.iter_cstr().collect();
202        assert_eq!(v[0].to_bytes_with_nul(), b"One\0");
203        assert_eq!(v[2].to_bytes_with_nul(), b"Three\0");
204    }
205}