1use std::ffi::{CStr, CString};
3use std::fmt::Formatter;
4
5use crate::errors::Result;
6use crate::session::Session;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13#[repr(transparent)]
14pub struct StringHandle(pub(crate) i32);
15
16#[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 = self.0.iter().filter(|v| **v == b'\0').count();
29 write!(f, "StringArray[num_strings = {count}]")
30 }
31 }
32}
33
34pub struct StringIter<'a> {
36 inner: &'a [u8],
37}
38
39pub 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
66pub struct CStringIter<'a> {
68 inner: &'a [u8],
69}
70
71impl<'a> StringArray {
72 pub fn empty() -> StringArray {
74 StringArray(vec![])
75 }
76 pub fn iter_str(&'a self) -> StringIter<'a> {
78 StringIter { inner: &self.0 }
79 }
80
81 pub fn iter_cstr(&'a self) -> CStringIter<'a> {
83 CStringIter { inner: &self.0 }
84 }
85
86 #[inline]
87 pub fn is_empty(&self) -> bool {
88 self.0.is_empty()
89 }
90
91 #[inline]
93 pub fn bytes(&self) -> &[u8] {
94 self.0.as_slice()
95 }
96}
97
98impl From<StringArray> for Vec<String> {
99 fn from(a: StringArray) -> Self {
100 a.into_iter().collect()
101 }
102}
103
104impl<'a> Iterator for StringIter<'a> {
105 type Item = &'a str;
106
107 fn next(&mut self) -> Option<Self::Item> {
108 match self.inner.iter().position(|c| *c == b'\0') {
109 None => None,
110 Some(idx) => {
111 let ret = &self.inner[..idx];
112 self.inner = &self.inner[idx + 1..];
113 unsafe { Some(std::str::from_utf8_unchecked(ret)) }
114 }
115 }
116 }
117}
118
119impl<'a> Iterator for CStringIter<'a> {
120 type Item = &'a CStr;
121
122 fn next(&mut self) -> Option<Self::Item> {
123 match self.inner.iter().position(|c| *c == b'\0') {
124 None => None,
125 Some(idx) => {
126 let ret = &self.inner[..idx + 1];
127 self.inner = &self.inner[idx + 1..];
128 unsafe { Some(CStr::from_bytes_with_nul_unchecked(ret)) }
129 }
130 }
131 }
132}
133
134impl IntoIterator for StringArray {
135 type Item = String;
136 type IntoIter = OwnedStringIter;
137
138 fn into_iter(self) -> Self::IntoIter {
139 OwnedStringIter {
140 inner: self.0,
141 cursor: 0,
142 }
143 }
144}
145
146pub(crate) fn get_string(handle: StringHandle, session: &Session) -> Result<String> {
147 let bytes = crate::ffi::get_string_bytes(session, handle)?;
148 String::from_utf8(bytes).map_err(crate::HapiError::from)
149}
150
151pub(crate) fn get_cstring(handle: StringHandle, session: &Session) -> Result<CString> {
152 let bytes = crate::ffi::get_string_bytes(session, handle)?;
153 CString::new(bytes).map_err(crate::HapiError::from)
154}
155
156pub fn get_string_array(handles: &[StringHandle], session: &Session) -> Result<StringArray> {
157 let _lock = session.lock();
158 let length = crate::ffi::get_string_batch_size(handles, session)?;
159 let bytes = if length > 0 {
160 crate::ffi::get_string_batch_bytes(length, session)?
161 } else {
162 vec![]
163 };
164 Ok(StringArray(bytes))
165}
166
167#[cfg(test)]
168mod tests {
169 use super::StringArray;
170 use crate::ffi;
171 use crate::server::ServerOptions;
172 use crate::session::{Session, SessionOptions, new_thrift_session};
173 use once_cell::sync::Lazy;
174 use std::ffi::CString;
175
176 static SESSION: Lazy<Session> = Lazy::new(|| {
177 let _ = env_logger::try_init().ok();
178 new_thrift_session(
179 SessionOptions::default(),
180 ServerOptions::shared_memory_with_defaults(),
181 )
182 .expect("Could not create test session")
183 });
184
185 #[test]
186 fn get_string_api() {
187 let h = ffi::get_server_env_str(&SESSION, &CString::new("HFS").unwrap()).unwrap();
188 assert!(super::get_string(h, &SESSION).is_ok());
189 assert!(super::get_cstring(h, &SESSION).is_ok());
190 }
191
192 #[test]
193 fn string_array_api() {
194 SESSION
195 .set_server_var::<str>("TEST", "177")
196 .expect("could not set var");
197 let var_count = ffi::get_server_env_var_count(&SESSION).unwrap();
198 let handles = ffi::get_server_env_var_list(&SESSION, var_count).unwrap();
199 let array = super::get_string_array(&handles, &SESSION).unwrap();
200 assert_eq!(array.iter_str().count(), var_count as usize);
201 assert_eq!(array.iter_cstr().count(), var_count as usize);
202 assert!(array.iter_str().any(|s| s == "TEST=177"));
203 assert!(
204 array
205 .iter_cstr()
206 .any(|s| s.to_bytes_with_nul() == b"TEST=177\0")
207 );
208 let mut owned: super::OwnedStringIter = array.into_iter();
209 assert!(owned.any(|s| s == "TEST=177"));
210
211 let arr = StringArray(b"One\0Two\0Three\0".to_vec());
212 let v: Vec<_> = arr.iter_cstr().collect();
213 assert_eq!(v[0].to_bytes_with_nul(), b"One\0");
214 assert_eq!(v[2].to_bytes_with_nul(), b"Three\0");
215 }
216
217 #[test]
218 fn test_set_custom_string() {
219 let handle = SESSION.set_custom_string("HAPI_RS_CUSTOM_STRING").unwrap();
220 assert_eq!(SESSION.get_string(handle).unwrap(), "HAPI_RS_CUSTOM_STRING");
221 SESSION.remove_custom_string(handle).unwrap();
222 assert!(SESSION.get_string(handle).is_err());
223 }
224}