use std::ops::Deref;
use chrono::DateTime;
use chrono::Utc;
use num_decimal::Num;
use serde::Deserialize;
use serde::Serialize;
use uuid::Uuid;
use crate::Str;
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Id(pub Uuid);
impl Deref for Id {
type Target = Uuid;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub enum Status {
#[serde(rename = "ONBOARDING")]
Onboarding,
#[serde(rename = "SUBMISSION_FAILED")]
SubmissionFailed,
#[serde(rename = "SUBMITTED")]
Submitted,
#[serde(rename = "ACCOUNT_UPDATED")]
Updating,
#[serde(rename = "APPROVAL_PENDING")]
ApprovalPending,
#[serde(rename = "ACTIVE")]
Active,
#[serde(rename = "REJECTED")]
Rejected,
#[doc(hidden)]
#[serde(other, rename(serialize = "unknown"))]
Unknown,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Account {
#[serde(rename = "id")]
pub id: Id,
#[serde(rename = "status")]
pub status: Status,
#[serde(rename = "currency")]
pub currency: String,
#[serde(rename = "cash")]
pub cash: Num,
#[serde(rename = "pattern_day_trader")]
pub day_trader: bool,
#[serde(rename = "trade_suspended_by_user")]
pub trading_suspended: bool,
#[serde(rename = "trading_blocked")]
pub trading_blocked: bool,
#[serde(rename = "transfers_blocked")]
pub transfers_blocked: bool,
#[serde(rename = "account_blocked")]
pub account_blocked: bool,
#[serde(rename = "created_at")]
pub created_at: DateTime<Utc>,
#[serde(rename = "shorting_enabled")]
pub shorting_enabled: bool,
#[serde(rename = "long_market_value")]
pub market_value_long: Num,
#[serde(rename = "short_market_value")]
pub market_value_short: Num,
#[serde(rename = "equity")]
pub equity: Num,
#[serde(rename = "last_equity")]
pub last_equity: Num,
#[serde(rename = "multiplier")]
pub multiplier: Num,
#[serde(rename = "buying_power")]
pub buying_power: Num,
#[serde(rename = "initial_margin")]
pub initial_margin: Num,
#[serde(rename = "maintenance_margin")]
pub maintenance_margin: Num,
#[serde(rename = "daytrade_count")]
pub daytrade_count: u64,
#[doc(hidden)]
#[serde(skip)]
pub _non_exhaustive: (),
}
Endpoint! {
pub Get(()),
Ok => Account, [
OK,
],
Err => GetError, []
#[inline]
fn path(_input: &Self::Input) -> Str {
"/v2/account".into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::from_str as from_json;
use serde_json::to_string as to_json;
use test_log::test;
use uuid::Uuid;
use crate::api::API_BASE_URL;
use crate::api_info::ApiInfo;
use crate::Client;
use crate::RequestError;
#[test]
fn deserialize_serialize_reference_account() {
let json = r#"{
"id": "904837e3-3b76-47ec-b432-046db621571b",
"status": "ACTIVE",
"currency": "USD",
"buying_power": "0.0",
"cash": "1000.00",
"portfolio_value": "5000.00",
"pattern_day_trader": false,
"trade_suspended_by_user": false,
"trading_blocked": false,
"transfers_blocked": false,
"account_blocked": false,
"created_at": "2018-10-01T13:35:25Z",
"shorting_enabled": true,
"multiplier": "2",
"long_market_value": "7000.00",
"short_market_value": "-3000.00",
"equity": "5000.00",
"last_equity": "5000.00",
"initial_margin": "5000.00",
"maintenance_margin": "3000.00",
"daytrade_count": 0,
"sma": "0.0"
}"#;
let acc =
from_json::<Account>(&to_json(&from_json::<Account>(json).unwrap()).unwrap()).unwrap();
let id = Id(Uuid::parse_str("904837e3-3b76-47ec-b432-046db621571b").unwrap());
assert_eq!(acc.id, id);
assert_eq!(acc.status, Status::Active);
assert_eq!(acc.currency, "USD");
assert_eq!(acc.buying_power, Num::from(0));
assert!(!acc.trading_blocked);
assert_eq!(
acc.created_at,
DateTime::parse_from_rfc3339("2018-10-01T13:35:25Z").unwrap()
);
assert_eq!(acc.market_value_long, Num::from(7000));
assert_eq!(acc.market_value_short, Num::from(-3000));
assert_eq!(acc.equity, Num::from(5000));
assert_eq!(acc.last_equity, Num::from(5000));
assert_eq!(acc.maintenance_margin, Num::from(3000));
assert_eq!(acc.daytrade_count, 0);
}
#[test(tokio::test)]
async fn request_account() {
let api_info = ApiInfo::from_env().unwrap();
let client = Client::new(api_info);
let account = client.issue::<Get>(&()).await.unwrap();
assert_eq!(account.currency, "USD");
assert!(!account.account_blocked);
let multiplier = account.multiplier.to_u64().unwrap();
assert!(
multiplier == 1 || multiplier == 2 || multiplier == 4,
"{}",
multiplier,
);
}
#[test(tokio::test)]
async fn request_account_with_invalid_credentials() {
let api_info = ApiInfo::from_parts(API_BASE_URL, "invalid", "invalid-too").unwrap();
let client = Client::new(api_info);
let result = client.issue::<Get>(&()).await;
let err = result.unwrap_err();
match err {
RequestError::Endpoint(GetError::NotPermitted(_)) => (),
e => panic!("received unexpected error: {e:?}"),
}
}
}