apca/api/v2/
account_config.rs

1// Copyright (C) 2020-2024 The apca Developers
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use http::Method;
5use http_endpoint::Bytes;
6
7use serde::Deserialize;
8use serde::Serialize;
9use serde_json::to_vec as to_json;
10
11use crate::Str;
12
13
14/// An enum representing the possible trade confirmation settings.
15#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
16#[non_exhaustive]
17pub enum TradeConfirmation {
18  /// Send an e-mail to confirm trades.
19  #[serde(rename = "all")]
20  Email,
21  /// Provide no confirmation for trades.
22  #[serde(rename = "none")]
23  None,
24}
25
26
27/// A response as returned by the /v2/account/configurations endpoint.
28// TODO: Not all fields are hooked up yet.
29#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct Configuration {
31  /// Whether and how trades are confirmed.
32  #[serde(rename = "trade_confirm_email")]
33  pub trade_confirmation: TradeConfirmation,
34  /// If enabled, new orders are blocked.
35  #[serde(rename = "suspend_trade")]
36  pub trading_suspended: bool,
37  /// If enabled, the account can only submit buy orders.
38  #[serde(rename = "no_shorting")]
39  pub no_shorting: bool,
40  /// The type is non-exhaustive and open to extension.
41  #[doc(hidden)]
42  #[serde(skip)]
43  pub _non_exhaustive: (),
44}
45
46
47Endpoint! {
48  /// The representation of a GET request to the
49  /// /v2/account/configurations endpoint.
50  pub Get(()),
51  Ok => Configuration, [
52    /// The account configuration was retrieved successfully.
53    /* 200 */ OK,
54  ],
55  Err => GetError, []
56
57  #[inline]
58  fn path(_input: &Self::Input) -> Str {
59    "/v2/account/configurations".into()
60  }
61}
62
63
64Endpoint! {
65  /// The representation of a PATCH request to the
66  /// /v2/account/configurations endpoint.
67  pub Change(Configuration),
68  Ok => Configuration, [
69    /// The account configuration was updated successfully.
70    /* 200 */ OK,
71  ],
72  Err => ChangeError, [
73    /// One of the new values is invalid/unacceptable.
74    /* 400 */ BAD_REQUEST => InvalidValues,
75  ]
76
77  #[inline]
78  fn method() -> Method {
79    Method::PATCH
80  }
81
82  #[inline]
83  fn path(_input: &Self::Input) -> Str {
84    "/v2/account/configurations".into()
85  }
86
87  fn body(input: &Self::Input) -> Result<Option<Bytes>, Self::ConversionError> {
88    let json = to_json(input)?;
89    let bytes = Bytes::from(json);
90    Ok(Some(bytes))
91  }
92}
93
94
95#[cfg(test)]
96mod tests {
97  use super::*;
98
99  use serde_json::from_str as from_json;
100
101  use test_log::test;
102
103  use crate::api_info::ApiInfo;
104  use crate::Client;
105
106
107  #[test]
108  fn parse_reference_configuration() {
109    let response = r#"{
110  "dtbp_check": "entry",
111  "no_shorting": false,
112  "suspend_trade": false,
113  "trade_confirm_email": "all"
114}"#;
115
116    let config = from_json::<Configuration>(response).unwrap();
117    assert_eq!(config.trade_confirmation, TradeConfirmation::Email);
118    assert!(!config.trading_suspended);
119    assert!(!config.no_shorting);
120  }
121
122  #[test(tokio::test)]
123  async fn retrieve_and_update_configuration() {
124    let api_info = ApiInfo::from_env().unwrap();
125    let client = Client::new(api_info);
126    let config = client.issue::<Get>(&()).await.unwrap();
127
128    // We invert the trade confirmation strategy, which should be a
129    // change not affecting any tests running concurrently.
130    let new_confirmation = match config.trade_confirmation {
131      TradeConfirmation::Email => TradeConfirmation::None,
132      TradeConfirmation::None => TradeConfirmation::Email,
133    };
134
135    let changed = Configuration {
136      trade_confirmation: new_confirmation,
137      ..config
138    };
139    let change_result = client.issue::<Change>(&changed).await;
140    // Also retrieve the configuration again.
141    let get_result = client.issue::<Get>(&()).await;
142    // Revert back to the original setting.
143    let reverted = client.issue::<Change>(&config).await.unwrap();
144
145    assert_eq!(change_result.unwrap(), changed);
146    assert_eq!(get_result.unwrap(), changed);
147    assert_eq!(reverted, config);
148  }
149}