hyperflow_rs_models/
lib.rs

1//! Hyperflow Rust Models
2//!
3//! This crate provides generated models from Protocol Buffers definitions.
4
5pub mod models {
6    #![allow(warnings)]
7    // This assumes your build.rs script sets OUT_DIR correctly.
8    include!(concat!(env!("OUT_DIR"), "/hyperflow.core.v1.rs"));
9}
10
11#[cfg(test)]
12mod tests {
13    use super::models::{EnrichedTrade, HyperliquidTrade, MarketType, Position, RawTrade};
14    use prost::Message;
15
16    /// A macro to generate standard tests for any Protobuf model struct.
17    ///
18    /// This macro generates a test module containing tests for:
19    /// 1. Creation from sample data.
20    /// 2. Correctness of default values.
21    /// 3. Serialization and deserialization integrity.
22    ///
23    /// # Arguments
24    ///
25    /// * `$test_module_name`: The name of the module to generate for the tests.
26    /// * `$model_type`: The type of the struct to be tested (e.g., `RawTrade`).
27    /// * `$sample_data_fn`: An expression that returns a valid instance of the model type.
28    /// * `$default_checks`: A closure that takes a reference to a default instance
29    ///   and performs assertions on its fields.
30    macro_rules! generate_model_tests {
31        (
32            $test_module_name:ident,
33            $model_type:ty,
34            $sample_data_fn:expr,
35            $default_checks:expr
36        ) => {
37            mod $test_module_name {
38                use super::*; // Import from outer `tests` module
39
40                #[test]
41                fn test_creation_with_sample_data() {
42                    let instance = $sample_data_fn;
43                    let expected_instance = $sample_data_fn;
44                    // Assumes the struct derives PartialEq, which prost does by default.
45                    assert_eq!(instance, expected_instance);
46                }
47
48                #[test]
49                fn test_default_values_are_correct() {
50                    let instance = <$model_type>::default();
51                    // Execute the provided closure to check default fields.
52                    $default_checks(&instance);
53                }
54
55                #[test]
56                fn test_serialization_and_deserialization() {
57                    let original = $sample_data_fn;
58                    let mut buf = Vec::new();
59                    original.encode(&mut buf).unwrap();
60                    let deserialized = <$model_type>::decode(&buf[..]).unwrap();
61                    assert_eq!(original, deserialized);
62                }
63            }
64        };
65    }
66
67    // --- HyperliquidTrade Tests ---
68    fn sample_hyperliquid_trade() -> HyperliquidTrade {
69        HyperliquidTrade {
70            coin: "ETH".to_string(),
71            side: "buy".to_string(),
72            px: "123.45".to_string(),
73            sz: "1.0".to_string(),
74            hash: "0xabc123".to_string(),
75            time: 1678886400,
76            tid: 1,
77            users: vec!["buyer_address".to_string(), "seller_address".to_string()],
78        }
79    }
80
81    generate_model_tests!(
82        hyperliquid_trade_tests,
83        HyperliquidTrade,
84        sample_hyperliquid_trade(),
85        |trade: &HyperliquidTrade| {
86            assert_eq!(trade.coin, "");
87            assert_eq!(trade.side, "");
88            assert_eq!(trade.px, "");
89            assert_eq!(trade.sz, "");
90            assert_eq!(trade.hash, "");
91            assert_eq!(trade.time, 0);
92            assert_eq!(trade.tid, 0);
93            assert!(trade.users.is_empty());
94        }
95    );
96
97    #[test]
98    fn test_create_hyperliquid_trade_with_overrides() {
99        let trade = HyperliquidTrade {
100            px: "0.0".to_string(),
101            sz: "0.0".to_string(),
102            users: vec!["buyer_address".to_string()],
103            ..Default::default()
104        };
105        assert_eq!(trade.px, "0.0");
106        assert_eq!(trade.sz, "0.0");
107        assert_eq!(trade.users, vec!["buyer_address"]);
108    }
109
110
111    // --- RawTrade Tests ---
112    fn sample_raw_trade() -> RawTrade {
113        RawTrade {
114            symbol: "BTC/USD".to_string(),
115            is_buy: true,
116            is_taker: false,
117            price: "50000.0".to_string(),
118            amount: "0.1".to_string(),
119            quote_amount: "5000.0".to_string(),
120            hash: "abc-123".to_string(),
121            event_at: 1678887000,
122            transaction_id: "ext-xyz".to_string(),
123            wallet_address: "0x12345".to_string(),
124            market_type: MarketType::Perp as i32,
125            extra_data: None, // Assuming `extra_data` is Option<Struct>
126        }
127    }
128
129    generate_model_tests!(
130        raw_trade_tests,
131        RawTrade,
132        sample_raw_trade(),
133        |trade: &RawTrade| {
134            assert_eq!(trade.symbol, "");
135            assert!(!trade.is_buy);
136            assert!(!trade.is_taker);
137            assert_eq!(trade.price, "");
138            assert_eq!(trade.amount, "");
139            assert_eq!(trade.quote_amount, "");
140            assert_eq!(trade.hash, "");
141            assert_eq!(trade.event_at, 0);
142            assert_eq!(trade.transaction_id, "");
143            assert_eq!(trade.wallet_address, "");
144            assert_eq!(trade.market_type, MarketType::Perp as i32);
145        }
146    );
147
148    // Specific tests for RawTrade remain here
149    mod raw_trade_specific_tests {
150        use super::*;
151
152        #[test]
153        fn test_serialization_all_market_types() {
154            for mt in [MarketType::Perp, MarketType::Spot] {
155                let mut trade = sample_raw_trade();
156                trade.market_type = mt as i32;
157
158                let mut buf = Vec::new();
159                trade.encode(&mut buf).unwrap();
160                let deserialized = RawTrade::decode(&buf[..]).unwrap();
161                assert_eq!(deserialized.market_type, mt as i32);
162            }
163        }
164
165        #[test]
166        fn test_market_type_enum_values() {
167            assert_eq!(MarketType::Perp as i32, 0);
168            assert_eq!(MarketType::Spot as i32, 1);
169        }
170    }
171
172
173    // --- EnrichedTrade Tests ---
174    fn sample_enriched_trade() -> EnrichedTrade {
175        EnrichedTrade {
176            symbol: "BTC/USD".to_string(),
177            is_buy: true,
178            is_taker: false,
179            price: "50000.0".to_string(),
180            amount: "0.1".to_string(),
181            quote_amount: "5000.0".to_string(),
182            hash: "abc-123".to_string(),
183            event_at: 1678887000,
184            transaction_id: "ext-xyz".to_string(),
185            wallet_address: "0x12345".to_string(),
186            market_type: MarketType::Perp as i32,
187            pnl: "100.0".to_string(),
188            pnl_usd: "100.0".to_string(),
189            holding_duration: 3600,
190            extra_data: None,
191        }
192    }
193
194    generate_model_tests!(
195        enriched_trade_tests,
196        EnrichedTrade,
197        sample_enriched_trade(),
198        |trade: &EnrichedTrade| {
199            assert_eq!(trade.symbol, "");
200            assert_eq!(trade.pnl, "");
201            assert_eq!(trade.pnl_usd, "");
202            assert_eq!(trade.holding_duration, 0);
203        }
204    );
205
206    // --- Position Tests ---
207    fn sample_position() -> Position {
208        Position {
209            symbol: "BTC/USD".to_string(),
210            is_buy: true,
211            average_price: "50000.0".to_string(),
212            amount: "0.1".to_string(),
213            quote_amount: "5000.0".to_string(),
214            wallet_address: "0x12345".to_string(),
215            market_type: MarketType::Perp as i32,
216        }
217    }
218
219    generate_model_tests!(
220        position_tests,
221        Position,
222        sample_position(),
223        |pos: &Position| {
224            assert_eq!(pos.symbol, "");
225            assert!(!pos.is_buy);
226            assert_eq!(pos.average_price, "");
227            assert_eq!(pos.amount, "");
228            assert_eq!(pos.quote_amount, "");
229            assert_eq!(pos.wallet_address, "");
230            assert_eq!(pos.market_type, MarketType::Perp as i32);
231        }
232    );
233}