Skip to main content

nautilus_model/ffi/types/
currency.rs

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