nautilus_model/ffi/types/
currency.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::{ffi::c_char, str::FromStr};
17
18use nautilus_core::ffi::string::{cstr_as_str, str_to_cstr};
19
20use crate::{currencies::CURRENCY_MAP, enums::CurrencyType, types::Currency};
21
22/// Returns a [`Currency`] from pointers and primitives.
23///
24/// # Safety
25///
26/// This function assumes:
27/// - `code_ptr` is a valid C string pointer.
28/// - `name_ptr` is a valid C string pointer.
29#[unsafe(no_mangle)]
30pub unsafe extern "C" fn currency_from_py(
31    code_ptr: *const c_char,
32    precision: u8,
33    iso4217: u16,
34    name_ptr: *const c_char,
35    currency_type: CurrencyType,
36) -> Currency {
37    Currency::new(
38        unsafe { cstr_as_str(code_ptr) },
39        precision,
40        iso4217,
41        unsafe { cstr_as_str(name_ptr) },
42        currency_type,
43    )
44}
45
46#[unsafe(no_mangle)]
47pub extern "C" fn currency_to_cstr(currency: &Currency) -> *const c_char {
48    str_to_cstr(format!("{currency:?}").as_str())
49}
50
51#[unsafe(no_mangle)]
52pub extern "C" fn currency_code_to_cstr(currency: &Currency) -> *const c_char {
53    str_to_cstr(&currency.code)
54}
55
56#[unsafe(no_mangle)]
57pub extern "C" fn currency_name_to_cstr(currency: &Currency) -> *const c_char {
58    str_to_cstr(&currency.name)
59}
60
61#[unsafe(no_mangle)]
62pub extern "C" fn currency_hash(currency: &Currency) -> u64 {
63    currency.code.precomputed_hash()
64}
65
66/// Registers a currency in the global map for FFI.
67///
68/// # Panics
69///
70/// Panics if the internal mutex `CURRENCY_MAP` is poisoned when locking.
71#[unsafe(no_mangle)]
72pub extern "C" fn currency_register(currency: Currency) {
73    CURRENCY_MAP
74        .lock()
75        .unwrap()
76        .insert(currency.code.to_string(), currency);
77}
78
79/// Checks whether a currency code exists in the global map for FFI.
80///
81/// # Panics
82///
83/// Panics if the internal mutex `CURRENCY_MAP` is poisoned when locking.
84///
85/// # Safety
86///
87/// Assumes `code_ptr` is a valid NUL-terminated UTF-8 C string pointer.
88#[unsafe(no_mangle)]
89pub unsafe extern "C" fn currency_exists(code_ptr: *const c_char) -> u8 {
90    let code = unsafe { cstr_as_str(code_ptr) };
91    u8::from(CURRENCY_MAP.lock().unwrap().contains_key(code))
92}
93
94/// Converts a C string pointer to a `Currency` for FFI.
95///
96/// # Panics
97///
98/// Panics if the provided code string is invalid or not found (`unwrap`).
99///
100/// # Safety
101///
102/// Assumes `code_ptr` is a valid NUL-terminated UTF-8 C string pointer.
103#[unsafe(no_mangle)]
104pub unsafe extern "C" fn currency_from_cstr(code_ptr: *const c_char) -> Currency {
105    let code = unsafe { cstr_as_str(code_ptr) };
106    Currency::from_str(code).unwrap()
107}
108
109////////////////////////////////////////////////////////////////////////////////
110// Tests
111////////////////////////////////////////////////////////////////////////////////
112#[cfg(test)]
113mod tests {
114    use std::ffi::{CStr, CString};
115
116    use rstest::rstest;
117
118    use super::*;
119    use crate::{enums::CurrencyType, types::Currency};
120
121    #[rstest]
122    fn test_registration() {
123        let currency = Currency::new("MYC", 4, 0, "My Currency", CurrencyType::Crypto);
124        currency_register(currency);
125        unsafe {
126            assert_eq!(currency_exists(str_to_cstr("MYC")), 1);
127        }
128    }
129
130    #[rstest]
131    fn test_currency_from_py() {
132        let code = CString::new("MYC").unwrap();
133        let name = CString::new("My Currency").unwrap();
134        let currency = unsafe {
135            super::currency_from_py(code.as_ptr(), 4, 0, name.as_ptr(), CurrencyType::Crypto)
136        };
137        assert_eq!(currency.code.as_str(), "MYC");
138        assert_eq!(currency.name.as_str(), "My Currency");
139        assert_eq!(currency.currency_type, CurrencyType::Crypto);
140    }
141
142    #[rstest]
143    fn test_currency_to_cstr() {
144        let currency = Currency::USD();
145        let cstr = unsafe { CStr::from_ptr(currency_to_cstr(&currency)) };
146        let expected_output = format!("{currency:?}");
147        assert_eq!(cstr.to_str().unwrap(), expected_output);
148    }
149
150    #[rstest]
151    fn test_currency_code_to_cstr() {
152        let currency = Currency::USD();
153        let cstr = unsafe { CStr::from_ptr(currency_code_to_cstr(&currency)) };
154        assert_eq!(cstr.to_str().unwrap(), "USD");
155    }
156
157    #[rstest]
158    fn test_currency_name_to_cstr() {
159        let currency = Currency::USD();
160        let cstr = unsafe { CStr::from_ptr(currency_name_to_cstr(&currency)) };
161        assert_eq!(cstr.to_str().unwrap(), "United States dollar");
162    }
163
164    #[rstest]
165    fn test_currency_hash() {
166        let currency = Currency::USD();
167        let hash = super::currency_hash(&currency);
168        assert_eq!(hash, currency.code.precomputed_hash());
169    }
170
171    #[rstest]
172    fn test_currency_from_cstr() {
173        let code = CString::new("USD").unwrap();
174        let currency = unsafe { currency_from_cstr(code.as_ptr()) };
175        assert_eq!(currency, Currency::USD());
176    }
177}