1use std::collections::HashMap;
22
23use indexmap::IndexMap;
24use nautilus_core::{Params, UnixNanos};
25use nautilus_model::{
26 data::BarType,
27 identifiers::{AccountId, InstrumentId},
28 types::{Currency, Money, Price, Quantity},
29};
30use nautilus_persistence_macros::custom_data;
31
32#[cfg_attr(
37 feature = "python",
38 expect(
39 clippy::unsafe_derive_deserialize,
40 reason = "test data uses the custom data macro output under test"
41 )
42)]
43#[custom_data(pyo3)]
44pub struct RustTestCustomData {
45 pub instrument_id: InstrumentId,
46 pub value: f64,
47 pub flag: bool,
48 pub ts_event: UnixNanos,
49 pub ts_init: UnixNanos,
50}
51
52#[cfg_attr(
56 feature = "python",
57 expect(
58 clippy::unsafe_derive_deserialize,
59 reason = "test data uses the custom data macro output under test"
60 )
61)]
62#[custom_data(pyo3)]
63pub struct MacroYieldCurveData {
64 pub curve_name: String,
65 pub tenors: Vec<f64>,
66 pub interest_rates: Vec<f64>,
67 pub ts_event: UnixNanos,
68 pub ts_init: UnixNanos,
69}
70
71#[cfg_attr(
73 feature = "python",
74 expect(
75 clippy::unsafe_derive_deserialize,
76 reason = "test data uses the custom data macro output under test"
77 )
78)]
79#[custom_data(pyo3)]
80pub struct RustTestParamsCustomData {
81 pub name: String,
82 pub params: Params,
83 pub ts_event: UnixNanos,
84 pub ts_init: UnixNanos,
85}
86
87#[cfg_attr(
89 feature = "python",
90 expect(
91 clippy::unsafe_derive_deserialize,
92 reason = "test data uses the custom data macro output under test"
93 )
94)]
95#[custom_data(pyo3)]
96pub struct RustTestPriceMapCustomData {
97 pub name: String,
98 #[custom_data_field(serde)]
99 pub prices: IndexMap<InstrumentId, Price>,
100 pub ts_event: UnixNanos,
101 pub ts_init: UnixNanos,
102}
103
104#[cfg_attr(
106 feature = "python",
107 expect(
108 clippy::unsafe_derive_deserialize,
109 reason = "test data uses the custom data macro output under test"
110 )
111)]
112#[custom_data(pyo3)]
113pub struct RustTestTypedMapCustomData {
114 pub name: String,
115 #[custom_data_field(serde)]
116 pub instrument_ids: IndexMap<String, InstrumentId>,
117 #[custom_data_field(serde)]
118 pub account_ids: IndexMap<String, AccountId>,
119 #[custom_data_field(serde)]
120 pub currencies: IndexMap<String, Currency>,
121 #[custom_data_field(serde)]
122 pub bar_types: IndexMap<String, BarType>,
123 #[custom_data_field(serde)]
124 pub prices: IndexMap<String, Price>,
125 #[custom_data_field(serde)]
126 pub quantities: IndexMap<String, Quantity>,
127 #[custom_data_field(serde)]
128 pub monies: IndexMap<String, Money>,
129 #[custom_data_field(serde)]
130 pub prices_by_instrument: IndexMap<InstrumentId, Price>,
131 #[custom_data_field(serde)]
132 pub quantities_by_account: IndexMap<AccountId, Quantity>,
133 #[custom_data_field(serde)]
134 pub monies_by_currency: IndexMap<Currency, Money>,
135 #[custom_data_field(serde)]
136 pub prices_by_bar_type: IndexMap<BarType, Price>,
137 #[custom_data_field(serde)]
138 pub hash_prices_by_instrument: HashMap<InstrumentId, Price>,
139 #[custom_data_field(serde)]
140 pub strings: HashMap<String, String>,
141 #[custom_data_field(serde)]
142 pub floats_64: HashMap<String, f64>,
143 #[custom_data_field(serde)]
144 pub floats_32: HashMap<String, f32>,
145 #[custom_data_field(serde)]
146 pub booleans: HashMap<String, bool>,
147 #[custom_data_field(serde)]
148 pub integers_u64: HashMap<String, u64>,
149 #[custom_data_field(serde)]
150 pub integers_i64: HashMap<String, i64>,
151 #[custom_data_field(serde)]
152 pub integers_u32: HashMap<String, u32>,
153 #[custom_data_field(serde)]
154 pub integers_i32: HashMap<String, i32>,
155 pub ts_event: UnixNanos,
156 pub ts_init: UnixNanos,
157}
158
159#[custom_data]
161pub struct RustTestHashMapCustomData {
162 pub name: String,
163 #[custom_data_field(serde)]
164 pub prices: HashMap<String, Price>,
165 pub ts_event: UnixNanos,
166 pub ts_init: UnixNanos,
167}
168
169#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
171pub enum RustTestSerdeFieldKind {
172 Alpha,
173 Beta { count: u64 },
174}
175
176#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
178pub struct RustTestSerdeFieldPayload {
179 pub kind: RustTestSerdeFieldKind,
180 pub label: String,
181 pub values: Vec<f64>,
182}
183
184#[custom_data]
186pub struct RustTestSerdeFieldCustomData {
187 pub name: String,
188 #[custom_data_field(serde)]
189 pub payload: RustTestSerdeFieldPayload,
190 pub ts_event: UnixNanos,
191 pub ts_init: UnixNanos,
192}
193
194#[cfg(test)]
195mod tests {
196 use arrow::datatypes::DataType;
197 use nautilus_serialization::arrow::{
198 ArrowSchemaProvider, DecodeDataFromRecordBatch, EncodeToRecordBatch,
199 };
200 use rstest::rstest;
201
202 use super::*;
203
204 #[rstest]
205 fn test_macro_yield_curve_data_schema_has_ts_init() {
206 let schema = MacroYieldCurveData::get_schema(None);
207 let field_names: Vec<_> = schema.fields().iter().map(|f| f.name().clone()).collect();
208 assert!(
209 field_names.iter().any(|f| f == "ts_init"),
210 "Schema must have ts_init for DataFusion ORDER BY; got: {field_names:?}",
211 );
212 assert!(
213 field_names.iter().any(|f| f == "ts_event"),
214 "Schema must have ts_event; got: {field_names:?}",
215 );
216 }
217
218 #[rstest]
219 fn test_rust_test_params_custom_data_schema_uses_utf8_for_params() {
220 let schema = RustTestParamsCustomData::get_schema(None);
221 let params_field = schema.field_with_name("params").unwrap();
222
223 assert_eq!(params_field.data_type(), &DataType::Utf8);
224 }
225
226 #[rstest]
227 fn test_rust_test_price_map_custom_data_schema_uses_utf8_for_prices() {
228 let schema = RustTestPriceMapCustomData::get_schema(None);
229 let prices_field = schema.field_with_name("prices").unwrap();
230
231 assert_eq!(prices_field.data_type(), &DataType::Utf8);
232 }
233
234 #[rstest]
235 fn test_rust_test_hash_map_custom_data_schema_uses_utf8_for_prices() {
236 let schema = RustTestHashMapCustomData::get_schema(None);
237 let prices_field = schema.field_with_name("prices").unwrap();
238
239 assert_eq!(prices_field.data_type(), &DataType::Utf8);
240 }
241
242 #[rstest]
243 fn test_rust_test_serde_field_custom_data_schema_uses_utf8_for_payload() {
244 let schema = RustTestSerdeFieldCustomData::get_schema(None);
245 let payload_field = schema.field_with_name("payload").unwrap();
246
247 assert_eq!(payload_field.data_type(), &DataType::Utf8);
248 }
249
250 #[rstest]
251 fn test_rust_test_serde_field_custom_data_roundtrip_decodes_exact_payload() {
252 let original = RustTestSerdeFieldCustomData {
253 name: "serde-field".to_string(),
254 payload: RustTestSerdeFieldPayload {
255 kind: RustTestSerdeFieldKind::Beta { count: 7 },
256 label: "payload".to_string(),
257 values: vec![1.0, 2.0, 3.0],
258 },
259 ts_event: UnixNanos::from(10),
260 ts_init: UnixNanos::from(11),
261 };
262 let metadata = original.metadata();
263 let batch =
264 RustTestSerdeFieldCustomData::encode_batch(&metadata, std::slice::from_ref(&original))
265 .unwrap();
266 let decoded = RustTestSerdeFieldCustomData::decode_data_batch(&metadata, batch).unwrap();
267
268 assert_eq!(decoded.len(), 1);
269 let decoded =
270 RustTestSerdeFieldCustomData::try_from(decoded.into_iter().next().unwrap()).unwrap();
271 assert_eq!(decoded, original);
272 }
273
274 #[rstest]
275 fn test_rust_test_typed_map_custom_data_schema_uses_utf8_for_json_maps() {
276 let schema = RustTestTypedMapCustomData::get_schema(None);
277
278 for field_name in [
279 "instrument_ids",
280 "account_ids",
281 "currencies",
282 "bar_types",
283 "prices",
284 "quantities",
285 "monies",
286 "prices_by_instrument",
287 "quantities_by_account",
288 "monies_by_currency",
289 "prices_by_bar_type",
290 "hash_prices_by_instrument",
291 "strings",
292 "floats_64",
293 "floats_32",
294 "booleans",
295 "integers_u64",
296 "integers_i64",
297 "integers_u32",
298 "integers_i32",
299 ] {
300 let field = schema.field_with_name(field_name).unwrap();
301 assert_eq!(field.data_type(), &DataType::Utf8);
302 }
303 }
304}