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::{
19    MUTEX_POISONED,
20    ffi::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///
71/// # Panics
72///
73/// Panics if the internal mutex `CURRENCY_MAP` is poisoned when locking.
74#[unsafe(no_mangle)]
75pub extern "C" fn currency_register(currency: Currency) {
76    CURRENCY_MAP
77        .lock()
78        .unwrap()
79        .insert(currency.code.to_string(), currency);
80}
81
82/// Checks whether a currency code exists in the global map for FFI.
83///
84/// # Panics
85///
86/// Panics if the internal mutex `CURRENCY_MAP` is poisoned when locking.
87///
88/// # Safety
89///
90/// Assumes `code_ptr` is a valid NUL-terminated UTF-8 C string pointer.
91#[unsafe(no_mangle)]
92pub unsafe extern "C" fn currency_exists(code_ptr: *const c_char) -> u8 {
93    let code = unsafe { cstr_as_str(code_ptr) };
94    u8::from(
95        CURRENCY_MAP
96            .lock()
97            .expect(MUTEX_POISONED)
98            .contains_key(code),
99    )
100}
101
102/// Converts a C string pointer to a `Currency` for FFI.
103///
104/// # Panics
105///
106/// Panics if the provided code string is invalid or not found (`unwrap`).
107///
108/// # Safety
109///
110/// Assumes `code_ptr` is a valid NUL-terminated UTF-8 C string pointer.
111#[unsafe(no_mangle)]
112pub unsafe extern "C" fn currency_from_cstr(code_ptr: *const c_char) -> Currency {
113    let code = unsafe { cstr_as_str(code_ptr) };
114    Currency::from_str(code).unwrap()
115}
116
117#[cfg(test)]
118mod tests {
119    use std::ffi::{CStr, CString};
120
121    use rstest::rstest;
122
123    use super::*;
124    use crate::{enums::CurrencyType, types::Currency};
125
126    #[rstest]
127    fn test_registration() {
128        let currency = Currency::new("MYC", 4, 0, "My Currency", CurrencyType::Crypto);
129        currency_register(currency);
130        unsafe {
131            assert_eq!(currency_exists(str_to_cstr("MYC")), 1);
132        }
133    }
134
135    #[rstest]
136    fn test_currency_from_py() {
137        let code = CString::new("MYC").unwrap();
138        let name = CString::new("My Currency").unwrap();
139        let currency = unsafe {
140            super::currency_from_py(code.as_ptr(), 4, 0, name.as_ptr(), CurrencyType::Crypto)
141        };
142        assert_eq!(currency.code.as_str(), "MYC");
143        assert_eq!(currency.name.as_str(), "My Currency");
144        assert_eq!(currency.currency_type, CurrencyType::Crypto);
145    }
146
147    #[rstest]
148    fn test_currency_to_cstr() {
149        let currency = Currency::USD();
150        let cstr = unsafe { CStr::from_ptr(currency_to_cstr(&currency)) };
151        let expected_output = format!("{currency:?}");
152        assert_eq!(cstr.to_str().unwrap(), expected_output);
153    }
154
155    #[rstest]
156    fn test_currency_code_to_cstr() {
157        let currency = Currency::USD();
158        let cstr = unsafe { CStr::from_ptr(currency_code_to_cstr(&currency)) };
159        assert_eq!(cstr.to_str().unwrap(), "USD");
160    }
161
162    #[rstest]
163    fn test_currency_name_to_cstr() {
164        let currency = Currency::USD();
165        let cstr = unsafe { CStr::from_ptr(currency_name_to_cstr(&currency)) };
166        assert_eq!(cstr.to_str().unwrap(), "United States dollar");
167    }
168
169    #[rstest]
170    fn test_currency_hash() {
171        let currency = Currency::USD();
172        let hash = super::currency_hash(&currency);
173        assert_eq!(hash, currency.code.precomputed_hash());
174    }
175
176    #[rstest]
177    fn test_currency_from_cstr() {
178        let code = CString::new("USD").unwrap();
179        let currency = unsafe { currency_from_cstr(code.as_ptr()) };
180        assert_eq!(currency, Currency::USD());
181    }
182}