1use crate::client::RequestParts;
4use crate::ids::{AccountID, InstrumentName, TransactionID};
5use crate::models::{
6 Account, AccountChanges, AccountChangesState, AccountProperties, AccountSummary,
7 ClientConfigureRejectTransaction, ClientConfigureTransaction, Instrument,
8};
9use crate::validation::Validate;
10use crate::{Client, Error, Result};
11use reqwest::Method;
12use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct ListAccountsResponse {
19 #[serde(rename = "accounts", default, skip_serializing_if = "Option::is_none")]
21 pub accounts: Option<Vec<AccountProperties>>,
22}
23
24impl Client {
25 pub async fn list_accounts(
33 &self,
34 ) -> std::result::Result<
35 crate::ApiResponse<ListAccountsResponse>,
36 crate::OperationError<crate::GenericRejection>,
37 > {
38 let path = "/v3/accounts".to_owned();
39 self.execute(RequestParts::new(Method::GET, &path)).await
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct AccountDetailsResponse {
47 #[serde(rename = "account", default, skip_serializing_if = "Option::is_none")]
49 pub account: Option<Account>,
50 #[serde(
52 rename = "lastTransactionID",
53 default,
54 skip_serializing_if = "Option::is_none"
55 )]
56 pub last_transaction_id: Option<TransactionID>,
57}
58
59impl Client {
60 pub async fn account_details(
68 &self,
69 account_id: &AccountID,
70 ) -> std::result::Result<
71 crate::ApiResponse<AccountDetailsResponse>,
72 crate::OperationError<crate::GenericRejection>,
73 > {
74 let account_id_encoded = crate::client::segment(account_id.as_str());
75 let path = format!("/v3/accounts/{account_id_encoded}");
76 self.execute(RequestParts::new(Method::GET, &path)).await
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct AccountSummaryResponse {
84 #[serde(rename = "account", default, skip_serializing_if = "Option::is_none")]
86 pub account: Option<AccountSummary>,
87 #[serde(
89 rename = "lastTransactionID",
90 default,
91 skip_serializing_if = "Option::is_none"
92 )]
93 pub last_transaction_id: Option<TransactionID>,
94}
95
96impl Client {
97 pub async fn account_summary(
105 &self,
106 account_id: &AccountID,
107 ) -> std::result::Result<
108 crate::ApiResponse<AccountSummaryResponse>,
109 crate::OperationError<crate::GenericRejection>,
110 > {
111 let account_id_encoded = crate::client::segment(account_id.as_str());
112 let path = format!("/v3/accounts/{account_id_encoded}/summary");
113 self.execute(RequestParts::new(Method::GET, &path)).await
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
121#[serde(rename_all = "camelCase")]
122pub struct AccountInstrumentsQuery {
123 #[serde(
125 rename = "instruments",
126 default,
127 skip_serializing_if = "Option::is_none"
128 )]
129 pub instruments: Option<Vec<InstrumentName>>,
130}
131
132impl Validate for AccountInstrumentsQuery {
133 fn validate(&self) -> Result<()> {
134 if let Some(instruments) = &self.instruments {
135 crate::validation::nonempty(instruments, "instruments")?;
136 }
137 Ok(())
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct AccountInstrumentsResponse {
145 #[serde(
147 rename = "instruments",
148 default,
149 skip_serializing_if = "Option::is_none"
150 )]
151 pub instruments: Option<Vec<Instrument>>,
152 #[serde(
154 rename = "lastTransactionID",
155 default,
156 skip_serializing_if = "Option::is_none"
157 )]
158 pub last_transaction_id: Option<TransactionID>,
159}
160
161impl Client {
162 pub async fn account_instruments(
170 &self,
171 account_id: &AccountID,
172 query: Option<&AccountInstrumentsQuery>,
173 ) -> std::result::Result<
174 crate::ApiResponse<AccountInstrumentsResponse>,
175 crate::OperationError<crate::GenericRejection>,
176 > {
177 let account_id_encoded = crate::client::segment(account_id.as_str());
178 let path = format!("/v3/accounts/{account_id_encoded}/instruments");
179 let request = RequestParts::new(Method::GET, &path);
180 let request = match query {
181 Some(query) => request.query(query)?,
182 None => request,
183 };
184 self.execute(request).await
185 }
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct ConfigureAccountBody {
192 #[serde(rename = "alias", default, skip_serializing_if = "Option::is_none")]
194 pub alias: Option<String>,
195 #[serde(
197 rename = "marginRate",
198 default,
199 skip_serializing_if = "Option::is_none"
200 )]
201 #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
202 pub margin_rate: Option<Decimal>,
203}
204
205impl Validate for ConfigureAccountBody {
206 fn validate(&self) -> Result<()> {
207 if self.alias.is_none() && self.margin_rate.is_none() {
208 return Err(Error::InvalidInput(
209 "mutation body must not be empty".into(),
210 ));
211 }
212 if self
213 .margin_rate
214 .is_some_and(|rate| rate <= Decimal::ZERO || rate > Decimal::ONE)
215 {
216 return Err(Error::InvalidInput("marginRate must be in (0,1]".into()));
217 }
218 Ok(())
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct ConfigureAccountResponse {
226 #[serde(
228 rename = "clientConfigureTransaction",
229 default,
230 skip_serializing_if = "Option::is_none"
231 )]
232 pub client_configure_transaction: Option<ClientConfigureTransaction>,
233 #[serde(
235 rename = "lastTransactionID",
236 default,
237 skip_serializing_if = "Option::is_none"
238 )]
239 pub last_transaction_id: Option<TransactionID>,
240}
241
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct ConfigureAccountRejection {
246 #[serde(
248 rename = "clientConfigureRejectTransaction",
249 default,
250 skip_serializing_if = "Option::is_none"
251 )]
252 pub client_configure_reject_transaction: Option<ClientConfigureRejectTransaction>,
253 #[serde(
255 rename = "lastTransactionID",
256 default,
257 skip_serializing_if = "Option::is_none"
258 )]
259 pub last_transaction_id: Option<TransactionID>,
260 #[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
262 pub error_code: Option<String>,
263 #[serde(rename = "errorMessage")]
265 pub error_message: String,
266}
267
268impl Client {
269 pub async fn configure_account(
278 &self,
279 account_id: &AccountID,
280 body: &ConfigureAccountBody,
281 ) -> std::result::Result<
282 crate::ApiResponse<ConfigureAccountResponse>,
283 crate::OperationError<ConfigureAccountRejection>,
284 > {
285 let account_id_encoded = crate::client::segment(account_id.as_str());
286 let path = format!("/v3/accounts/{account_id_encoded}/configuration");
287 self.execute(
288 RequestParts::new(Method::PATCH, &path)
289 .body(body)?
290 .mutation(crate::client::MutationAdmission::Ordinary(account_id)),
291 )
292 .await
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
300#[serde(rename_all = "camelCase")]
301pub struct AccountChangesQuery {
302 #[serde(
304 rename = "sinceTransactionID",
305 default,
306 skip_serializing_if = "Option::is_none"
307 )]
308 pub since_transaction_id: Option<TransactionID>,
309}
310
311impl Validate for AccountChangesQuery {
312 fn validate(&self) -> Result<()> {
313 Ok(())
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct AccountChangesResponse {
321 #[serde(rename = "changes", default, skip_serializing_if = "Option::is_none")]
324 pub changes: Option<AccountChanges>,
325 #[serde(rename = "state", default, skip_serializing_if = "Option::is_none")]
327 pub state: Option<AccountChangesState>,
328 #[serde(
331 rename = "lastTransactionID",
332 default,
333 skip_serializing_if = "Option::is_none"
334 )]
335 pub last_transaction_id: Option<TransactionID>,
336}
337
338impl Client {
339 pub async fn account_changes(
347 &self,
348 account_id: &AccountID,
349 query: Option<&AccountChangesQuery>,
350 ) -> std::result::Result<
351 crate::ApiResponse<AccountChangesResponse>,
352 crate::OperationError<crate::GenericRejection>,
353 > {
354 let account_id_encoded = crate::client::segment(account_id.as_str());
355 let path = format!("/v3/accounts/{account_id_encoded}/changes");
356 let request = RequestParts::new(Method::GET, &path);
357 let request = match query {
358 Some(query) => request.query(query)?,
359 None => request,
360 };
361 self.execute(request).await
362 }
363}