hyperflow-rs-models 0.1.9

Generated Rust models for Hyperflow, derived from Protocol Buffer definitions.
Documentation
//! Hyperflow Rust Models
//!
//! This crate provides generated models from Protocol Buffers definitions.

pub mod models {
    #![allow(warnings)]
    // This assumes your build.rs script sets OUT_DIR correctly.
    include!(concat!(env!("OUT_DIR"), "/hyperflow.core.v1.rs"));
}

#[cfg(test)]
mod tests {
    use super::models::{EnrichedTrade, HyperliquidTrade, MarketType, Position, RawTrade};
    use prost::Message;

    /// A macro to generate standard tests for any Protobuf model struct.
    ///
    /// This macro generates a test module containing tests for:
    /// 1. Creation from sample data.
    /// 2. Correctness of default values.
    /// 3. Serialization and deserialization integrity.
    ///
    /// # Arguments
    ///
    /// * `$test_module_name`: The name of the module to generate for the tests.
    /// * `$model_type`: The type of the struct to be tested (e.g., `RawTrade`).
    /// * `$sample_data_fn`: An expression that returns a valid instance of the model type.
    /// * `$default_checks`: A closure that takes a reference to a default instance
    ///   and performs assertions on its fields.
    macro_rules! generate_model_tests {
        (
            $test_module_name:ident,
            $model_type:ty,
            $sample_data_fn:expr,
            $default_checks:expr
        ) => {
            mod $test_module_name {
                use super::*; // Import from outer `tests` module

                #[test]
                fn test_creation_with_sample_data() {
                    let instance = $sample_data_fn;
                    let expected_instance = $sample_data_fn;
                    // Assumes the struct derives PartialEq, which prost does by default.
                    assert_eq!(instance, expected_instance);
                }

                #[test]
                fn test_default_values_are_correct() {
                    let instance = <$model_type>::default();
                    // Execute the provided closure to check default fields.
                    $default_checks(&instance);
                }

                #[test]
                fn test_serialization_and_deserialization() {
                    let original = $sample_data_fn;
                    let mut buf = Vec::new();
                    original.encode(&mut buf).unwrap();
                    let deserialized = <$model_type>::decode(&buf[..]).unwrap();
                    assert_eq!(original, deserialized);
                }
            }
        };
    }

    // --- HyperliquidTrade Tests ---
    fn sample_hyperliquid_trade() -> HyperliquidTrade {
        HyperliquidTrade {
            coin: "ETH".to_string(),
            side: "buy".to_string(),
            px: "123.45".to_string(),
            sz: "1.0".to_string(),
            hash: "0xabc123".to_string(),
            time: 1678886400,
            tid: 1,
            users: vec!["buyer_address".to_string(), "seller_address".to_string()],
        }
    }

    generate_model_tests!(
        hyperliquid_trade_tests,
        HyperliquidTrade,
        sample_hyperliquid_trade(),
        |trade: &HyperliquidTrade| {
            assert_eq!(trade.coin, "");
            assert_eq!(trade.side, "");
            assert_eq!(trade.px, "");
            assert_eq!(trade.sz, "");
            assert_eq!(trade.hash, "");
            assert_eq!(trade.time, 0);
            assert_eq!(trade.tid, 0);
            assert!(trade.users.is_empty());
        }
    );

    #[test]
    fn test_create_hyperliquid_trade_with_overrides() {
        let trade = HyperliquidTrade {
            px: "0.0".to_string(),
            sz: "0.0".to_string(),
            users: vec!["buyer_address".to_string()],
            ..Default::default()
        };
        assert_eq!(trade.px, "0.0");
        assert_eq!(trade.sz, "0.0");
        assert_eq!(trade.users, vec!["buyer_address"]);
    }


    // --- RawTrade Tests ---
    fn sample_raw_trade() -> RawTrade {
        RawTrade {
            symbol: "BTC/USD".to_string(),
            is_buy: true,
            is_taker: false,
            price: "50000.0".to_string(),
            amount: "0.1".to_string(),
            quote_amount: "5000.0".to_string(),
            hash: "abc-123".to_string(),
            event_at: 1678887000,
            transaction_id: "ext-xyz".to_string(),
            wallet_address: "0x12345".to_string(),
            market_type: MarketType::Perp as i32,
            extra_data: None, // Assuming `extra_data` is Option<Struct>
        }
    }

    generate_model_tests!(
        raw_trade_tests,
        RawTrade,
        sample_raw_trade(),
        |trade: &RawTrade| {
            assert_eq!(trade.symbol, "");
            assert!(!trade.is_buy);
            assert!(!trade.is_taker);
            assert_eq!(trade.price, "");
            assert_eq!(trade.amount, "");
            assert_eq!(trade.quote_amount, "");
            assert_eq!(trade.hash, "");
            assert_eq!(trade.event_at, 0);
            assert_eq!(trade.transaction_id, "");
            assert_eq!(trade.wallet_address, "");
            assert_eq!(trade.market_type, MarketType::Perp as i32);
        }
    );

    // Specific tests for RawTrade remain here
    mod raw_trade_specific_tests {
        use super::*;

        #[test]
        fn test_serialization_all_market_types() {
            for mt in [MarketType::Perp, MarketType::Spot] {
                let mut trade = sample_raw_trade();
                trade.market_type = mt as i32;

                let mut buf = Vec::new();
                trade.encode(&mut buf).unwrap();
                let deserialized = RawTrade::decode(&buf[..]).unwrap();
                assert_eq!(deserialized.market_type, mt as i32);
            }
        }

        #[test]
        fn test_market_type_enum_values() {
            assert_eq!(MarketType::Perp as i32, 0);
            assert_eq!(MarketType::Spot as i32, 1);
        }
    }


    // --- EnrichedTrade Tests ---
    fn sample_enriched_trade() -> EnrichedTrade {
        EnrichedTrade {
            symbol: "BTC/USD".to_string(),
            is_buy: true,
            is_taker: false,
            price: "50000.0".to_string(),
            amount: "0.1".to_string(),
            quote_amount: "5000.0".to_string(),
            hash: "abc-123".to_string(),
            event_at: 1678887000,
            transaction_id: "ext-xyz".to_string(),
            wallet_address: "0x12345".to_string(),
            market_type: MarketType::Perp as i32,
            pnl: "100.0".to_string(),
            pnl_usd: "100.0".to_string(),
            holding_duration: 3600,
            extra_data: None,
        }
    }

    generate_model_tests!(
        enriched_trade_tests,
        EnrichedTrade,
        sample_enriched_trade(),
        |trade: &EnrichedTrade| {
            assert_eq!(trade.symbol, "");
            assert_eq!(trade.pnl, "");
            assert_eq!(trade.pnl_usd, "");
            assert_eq!(trade.holding_duration, 0);
        }
    );

    // --- Position Tests ---
    fn sample_position() -> Position {
        Position {
            symbol: "BTC/USD".to_string(),
            is_buy: true,
            average_price: "50000.0".to_string(),
            amount: "0.1".to_string(),
            quote_amount: "5000.0".to_string(),
            wallet_address: "0x12345".to_string(),
            market_type: MarketType::Perp as i32,
        }
    }

    generate_model_tests!(
        position_tests,
        Position,
        sample_position(),
        |pos: &Position| {
            assert_eq!(pos.symbol, "");
            assert!(!pos.is_buy);
            assert_eq!(pos.average_price, "");
            assert_eq!(pos.amount, "");
            assert_eq!(pos.quote_amount, "");
            assert_eq!(pos.wallet_address, "");
            assert_eq!(pos.market_type, MarketType::Perp as i32);
        }
    );
}