nautilus_core/ffi/
parsing.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{
17    collections::HashMap,
18    ffi::{c_char, CStr, CString},
19};
20
21use serde_json::{Result, Value};
22use ustr::Ustr;
23
24use crate::{
25    ffi::string::cstr_as_str,
26    parsing::{min_increment_precision_from_str, precision_from_str},
27};
28
29/// Convert a C bytes pointer into an owned `Vec<String>`.
30///
31/// # Safety
32///
33/// - Assumes `ptr` is a valid C string pointer.
34#[must_use]
35pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
36    assert!(!ptr.is_null(), "`ptr` was NULL");
37
38    let c_str = unsafe { CStr::from_ptr(ptr) };
39    let bytes = c_str.to_bytes();
40    let json_string = std::str::from_utf8(bytes).unwrap();
41    let parsed_value: serde_json::Value = serde_json::from_str(json_string).unwrap();
42
43    match parsed_value {
44        serde_json::Value::Array(arr) => arr
45            .into_iter()
46            .filter_map(|value| match value {
47                serde_json::Value::String(string_value) => Some(string_value),
48                _ => None,
49            })
50            .collect(),
51        _ => Vec::new(),
52    }
53}
54
55#[must_use]
56pub fn string_vec_to_bytes(strings: Vec<String>) -> *const c_char {
57    let json_string = serde_json::to_string(&strings).unwrap();
58    let c_string = CString::new(json_string).unwrap();
59    c_string.into_raw()
60}
61
62/// Convert a C bytes pointer into an owned `Option<HashMap<String, Value>>`.
63///
64/// # Safety
65///
66/// - Assumes `ptr` is a valid C string pointer.
67#[must_use]
68pub unsafe fn optional_bytes_to_json(ptr: *const c_char) -> Option<HashMap<String, Value>> {
69    if ptr.is_null() {
70        None
71    } else {
72        let c_str = unsafe { CStr::from_ptr(ptr) };
73        let bytes = c_str.to_bytes();
74        let json_string = std::str::from_utf8(bytes).unwrap();
75        let result: Result<HashMap<String, Value>> = serde_json::from_str(json_string);
76        match result {
77            Ok(map) => Some(map),
78            Err(e) => {
79                eprintln!("Error parsing JSON: {e}");
80                None
81            }
82        }
83    }
84}
85
86/// Convert a C bytes pointer into an owned `Option<HashMap<Ustr, Ustr>>`.
87///
88/// # Safety
89///
90/// - Assumes `ptr` is a valid C string pointer.
91#[must_use]
92pub unsafe fn optional_bytes_to_str_map(ptr: *const c_char) -> Option<HashMap<Ustr, Ustr>> {
93    if ptr.is_null() {
94        None
95    } else {
96        let c_str = unsafe { CStr::from_ptr(ptr) };
97        let bytes = c_str.to_bytes();
98        let json_string = std::str::from_utf8(bytes).unwrap();
99        let result: Result<HashMap<Ustr, Ustr>> = serde_json::from_str(json_string);
100        match result {
101            Ok(map) => Some(map),
102            Err(e) => {
103                eprintln!("Error parsing JSON: {e}");
104                None
105            }
106        }
107    }
108}
109
110/// Convert a C bytes pointer into an owned `Option<Vec<String>>`.
111///
112/// # Safety
113///
114/// - Assumes `ptr` is a valid C string pointer.
115#[must_use]
116pub unsafe fn optional_bytes_to_str_vec(ptr: *const c_char) -> Option<Vec<String>> {
117    if ptr.is_null() {
118        None
119    } else {
120        let c_str = unsafe { CStr::from_ptr(ptr) };
121        let bytes = c_str.to_bytes();
122        let json_string = std::str::from_utf8(bytes).unwrap();
123        let result: Result<Vec<String>> = serde_json::from_str(json_string);
124        match result {
125            Ok(map) => Some(map),
126            Err(e) => {
127                eprintln!("Error parsing JSON: {e}");
128                None
129            }
130        }
131    }
132}
133
134/// Return the decimal precision inferred from the given C string.
135///
136/// # Safety
137///
138/// - Assumes `ptr` is a valid C string pointer.
139///
140/// # Panics
141///
142/// This function panics:
143/// - If `ptr` is null.
144#[no_mangle]
145pub unsafe extern "C" fn precision_from_cstr(ptr: *const c_char) -> u8 {
146    assert!(!ptr.is_null(), "`ptr` was NULL");
147    let s = unsafe { cstr_as_str(ptr) };
148    precision_from_str(s)
149}
150
151/// Return the minimum price increment decimal precision inferred from the given C string.
152///
153/// # Safety
154///
155/// - Assumes `ptr` is a valid C string pointer.
156///
157/// # Panics
158///
159/// This function panics:
160/// - If `ptr` is null.
161#[no_mangle]
162pub unsafe extern "C" fn min_increment_precision_from_cstr(ptr: *const c_char) -> u8 {
163    assert!(!ptr.is_null(), "`ptr` was NULL");
164    let s = unsafe { cstr_as_str(ptr) };
165    min_increment_precision_from_str(s)
166}
167
168/// Return a `bool` value from the given `u8`.
169#[must_use]
170pub const fn u8_as_bool(value: u8) -> bool {
171    value != 0
172}
173
174////////////////////////////////////////////////////////////////////////////////
175// Tests
176////////////////////////////////////////////////////////////////////////////////
177#[cfg(test)]
178mod tests {
179    use std::ffi::CString;
180
181    use rstest::rstest;
182
183    use super::*;
184
185    #[rstest]
186    fn test_optional_bytes_to_json_null() {
187        let ptr = std::ptr::null();
188        let result = unsafe { optional_bytes_to_json(ptr) };
189        assert_eq!(result, None);
190    }
191
192    #[rstest]
193    fn test_optional_bytes_to_json_empty() {
194        let json_str = CString::new("{}").unwrap();
195        let ptr = json_str.as_ptr().cast::<c_char>();
196        let result = unsafe { optional_bytes_to_json(ptr) };
197        assert_eq!(result, Some(HashMap::new()));
198    }
199
200    #[rstest]
201    fn test_string_vec_to_bytes_valid() {
202        let strings = vec!["value1", "value2", "value3"]
203            .into_iter()
204            .map(String::from)
205            .collect::<Vec<String>>();
206
207        let ptr = string_vec_to_bytes(strings.clone());
208
209        let result = unsafe { bytes_to_string_vec(ptr) };
210        assert_eq!(result, strings);
211    }
212
213    #[rstest]
214    fn test_string_vec_to_bytes_empty() {
215        let strings = Vec::new();
216        let ptr = string_vec_to_bytes(strings.clone());
217
218        let result = unsafe { bytes_to_string_vec(ptr) };
219        assert_eq!(result, strings);
220    }
221
222    #[rstest]
223    fn test_bytes_to_string_vec_valid() {
224        let json_str = CString::new(r#"["value1", "value2", "value3"]"#).unwrap();
225        let ptr = json_str.as_ptr().cast::<c_char>();
226        let result = unsafe { bytes_to_string_vec(ptr) };
227
228        let expected_vec = vec!["value1", "value2", "value3"]
229            .into_iter()
230            .map(String::from)
231            .collect::<Vec<String>>();
232
233        assert_eq!(result, expected_vec);
234    }
235
236    #[rstest]
237    fn test_bytes_to_string_vec_invalid() {
238        let json_str = CString::new(r#"["value1", 42, "value3"]"#).unwrap();
239        let ptr = json_str.as_ptr().cast::<c_char>();
240        let result = unsafe { bytes_to_string_vec(ptr) };
241
242        let expected_vec = vec!["value1", "value3"]
243            .into_iter()
244            .map(String::from)
245            .collect::<Vec<String>>();
246
247        assert_eq!(result, expected_vec);
248    }
249
250    #[rstest]
251    fn test_optional_bytes_to_json_valid() {
252        let json_str = CString::new(r#"{"key1": "value1", "key2": 2}"#).unwrap();
253        let ptr = json_str.as_ptr().cast::<c_char>();
254        let result = unsafe { optional_bytes_to_json(ptr) };
255        let mut expected_map = HashMap::new();
256        expected_map.insert("key1".to_owned(), Value::String("value1".to_owned()));
257        expected_map.insert(
258            "key2".to_owned(),
259            Value::Number(serde_json::Number::from(2)),
260        );
261        assert_eq!(result, Some(expected_map));
262    }
263
264    #[rstest]
265    fn test_optional_bytes_to_json_invalid() {
266        let json_str = CString::new(r#"{"key1": "value1", "key2": }"#).unwrap();
267        let ptr = json_str.as_ptr().cast::<c_char>();
268        let result = unsafe { optional_bytes_to_json(ptr) };
269        assert_eq!(result, None);
270    }
271
272    #[rstest]
273    #[case("1e8", 0)]
274    #[case("123", 0)]
275    #[case("123.45", 2)]
276    #[case("123.456789", 6)]
277    #[case("1.23456789e-2", 2)]
278    #[case("1.23456789e-12", 12)]
279    fn test_precision_from_cstr(#[case] input: &str, #[case] expected: u8) {
280        let c_str = CString::new(input).unwrap();
281        assert_eq!(unsafe { precision_from_cstr(c_str.as_ptr()) }, expected);
282    }
283}