1use std::ffi::{CStr, CString};
3use std::fmt::Formatter;
4
5use crate::errors::{ErrorContext, Result};
6use crate::session::Session;
7
8#[derive(Debug, Copy, Clone)]
13#[repr(transparent)]
14pub struct StringHandle(pub(crate) i32);
15
16#[derive(Clone)]
20pub struct StringArray(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 = get_string_bytes(handle, session).context("Calling get_string_bytes")?;
148 String::from_utf8(bytes).map_err(crate::errors::HapiError::from)
149}
150
151pub(crate) fn get_cstring(handle: StringHandle, session: &Session) -> Result<CString> {
152 unsafe {
153 let bytes = get_string_bytes(handle, session).context("Calling get_string_bytes")?;
154 Ok(CString::from_vec_unchecked(bytes))
156 }
157}
158
159pub(crate) fn get_string_bytes(handle: StringHandle, session: &Session) -> Result<Vec<u8>> {
160 if handle.0 < 0 {
161 return Ok(Vec::new());
162 }
163 let length = crate::ffi::get_string_buff_len(session, handle.0)?;
164 if length == 0 {
165 Ok(Vec::new())
166 } else {
167 crate::ffi::get_string(session, handle.0, length)
168 }
169}
170
171pub fn get_string_array(handles: &[StringHandle], session: &Session) -> Result<StringArray> {
172 let _lock = session.lock();
173 let length = crate::ffi::get_string_batch_size(handles, session)?;
174 let bytes = if length > 0 {
175 crate::ffi::get_string_batch(length, session)?
176 } else {
177 vec![]
178 };
179 Ok(StringArray(bytes))
180}
181
182#[cfg(test)]
183mod tests {
184 use super::StringArray;
185 use crate::ffi;
186 use crate::session::Session;
187 use once_cell::sync::Lazy;
188 use std::ffi::CString;
189
190 static SESSION: Lazy<Session> = Lazy::new(|| {
191 let _ = env_logger::try_init().ok();
192 crate::session::quick_session(None).expect("Could not create test session")
193 });
194
195 #[test]
196 fn get_string_api() {
197 let h = ffi::get_server_env_str(&SESSION, &CString::new("HFS").unwrap()).unwrap();
198 assert!(super::get_string(h, &SESSION).is_ok());
199 assert!(super::get_cstring(h, &SESSION).is_ok());
200 }
201
202 #[test]
203 fn string_array_api() {
204 SESSION
205 .set_server_var::<str>("TEST", "177")
206 .expect("could not set var");
207 let var_count = ffi::get_server_env_var_count(&SESSION).unwrap();
208 let handles = ffi::get_server_env_var_list(&SESSION, var_count).unwrap();
209 let array = super::get_string_array(&handles, &SESSION).unwrap();
210 assert_eq!(array.iter_str().count(), var_count as usize);
211 assert_eq!(array.iter_cstr().count(), var_count as usize);
212 assert!(array.iter_str().any(|s| s == "TEST=177"));
213 assert!(array
214 .iter_cstr()
215 .any(|s| s.to_bytes_with_nul() == b"TEST=177\0"));
216 let mut owned: super::OwnedStringIter = array.into_iter();
217 assert!(owned.any(|s| s == "TEST=177"));
218
219 let arr = StringArray(b"One\0Two\0Three\0".to_vec());
220 let v: Vec<_> = arr.iter_cstr().collect();
221 assert_eq!(v[0].to_bytes_with_nul(), b"One\0");
222 assert_eq!(v[2].to_bytes_with_nul(), b"Three\0");
223 }
224}