tradier 0.1.2

This project involves the development of a Rust library for managing trades and market data using the Tradier broker API. The main objective is to provide an efficient and secure interface for executing trades, retrieving real-time quotes, managing portfolios, and accessing historical market data. The library focuses on leveraging Rust's performance and concurrency advantages, enabling integration into high-frequency trading applications and data-intensive financial processing.
Documentation
use std::str::FromStr;

use chrono::{DateTime, Utc};
use serde::Deserialize;

use crate::common::AccountType;

#[derive(Debug)]
pub struct AccountNumber(String);

impl FromStr for AccountNumber {
    type Err = crate::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.trim().is_empty() {
            return Err(crate::Error::AccountIdParseError(s.to_owned()));
        }
        let mut is_valid_ascii = false;
        for c in s.chars().map(|c| c as u8) {
            if (0x20u8..0x7fu8).contains(&c) {
                is_valid_ascii = true;
            }
        }
        if is_valid_ascii {
            Ok(Self(s.to_string()))
        } else {
            Err(crate::Error::AccountIdParseError(s.to_string()))
        }
    }
}

impl std::fmt::Display for AccountNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct GetAccountBalancesResponse {
    balances: AccountBalances,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct AccountBalances {
    option_short_value: f64,
    total_equity: f64,
    account_number: String,
    account_type: AccountType,
    close_pl: f64,
    current_requirement: f64,
    equity: f64,
    long_market_value: f64,
    market_value: f64,
    open_pl: f64,
    option_long_value: f64,
    option_requirement: f64,
    pending_orders_count: i32,
    short_market_value: f64,
    stock_long_value: f64,
    total_cash: f64,
    uncleared_funds: f64,
    pending_cash: f64,
    margin: Margin,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct Margin {
    fed_call: f64,
    maintenance_call: f64,
    option_buying_power: f64,
    stock_buying_power: f64,
    stock_short_value: f64,
    sweep: f64,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct Position {
    cost_basis: f64,
    date_acquired: DateTime<Utc>,
    id: u32,
    quantity: f64,
    symbol: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct GetAccountPositionsResponse {
    positions: Vec<Position>,
}

#[cfg(test)]
mod test {
    use proptest::prelude::*;

    use crate::{
        accounts::{
            test_support::{GetAccountBalancesResponseWire, GetAccountPositionsResponseWire},
            types::GetAccountBalancesResponse,
        },
        types::{AccountNumber, GetAccountPositionsResponse},
        Result,
    };

    #[test]
    fn test_account_number_cannot_be_empty_or_blank() {
        let account_number = "".parse::<AccountNumber>();
        assert!(account_number.is_err());
        let account_number = "     ".parse::<AccountNumber>();
        assert!(account_number.is_err());
    }

    proptest! {
        #[test]
        fn test_deserialize_account_balances_response_from_json(response in any::<GetAccountBalancesResponseWire>()) {

            let response = serde_json::to_string_pretty(&response)
                .expect("test fixture to serialize");
            let result: std::result::Result<GetAccountBalancesResponse, serde_json::Error> = serde_json::from_str(&response);
            assert!(result.is_ok());
        }

        #[test]
        fn test_account_number_from_printable_ascii_string(ascii_string in prop::collection::vec(0x20u8..0x7fu8, 1..1000)
            .prop_flat_map(|vec| {
                Just(vec.into_iter().map(|c| c as char).collect::<String>())
            })) {

            let account_number: Result<AccountNumber> = ascii_string.parse();
            assert!(account_number.is_ok());
        }

        #[test]
        fn test_account_number_from_non_printable_ascii_string(ascii_string in prop::collection::vec(0x00u8..0x20u8, 1..1000)
            .prop_flat_map(|vec| {
                Just(vec.into_iter().map(|c| c as char).collect::<String>())
            })) {
            let account_number: Result<AccountNumber> = ascii_string.parse();
            assert!(account_number.is_err());
        }

        #[test]
        fn test_deserialize_positions_from_json(response in any::<GetAccountPositionsResponseWire>()) {
            let response = serde_json::to_string_pretty(&response)
                .expect("test fixture to serialize");
            let result: std::result::Result<GetAccountPositionsResponse, serde_json::Error> = serde_json::from_str(&response);
            assert!(result.is_ok());
        }

    }
}