sure_client_rs/client/
accounts.rs

1use crate::models::account::{
2    AccountCollection, AccountDetail, AccountableAttributes, CreateAccountData,
3    CreateAccountRequest, UpdateAccountData, UpdateAccountRequest,
4};
5use crate::models::{DeleteResponse, PaginatedResponse};
6use crate::types::AccountId;
7use crate::{ApiError, error::ApiResult};
8use bon::bon;
9use reqwest::Method;
10use rust_decimal::Decimal;
11use std::collections::HashMap;
12use url::Url;
13
14use super::SureClient;
15
16const MAX_PER_PAGE: u32 = 100;
17
18#[bon]
19impl SureClient {
20    /// List accounts
21    ///
22    /// Retrieves a paginated list of accounts.
23    ///
24    /// # Arguments
25    /// * `page` - Page number (default: 1)
26    /// * `per_page` - Items per page (default: 25, max: 100)
27    ///
28    /// # Returns
29    /// A paginated response containing accounts and pagination metadata.
30    ///
31    /// # Errors
32    /// Returns `ApiError::Unauthorized` if the API key is invalid.
33    /// Returns `ApiError::Network` if the request fails due to network issues.
34    ///
35    /// # Example
36    /// ```no_run
37    /// use sure_client_rs::{SureClient, BearerToken};
38    ///
39    /// # async fn example(client: SureClient) -> Result<(), Box<dyn std::error::Error>> {
40    /// // Use defaults (page 1, per_page 25)
41    /// let response = client.get_accounts().call().await?;
42    ///
43    /// for account in response.items.accounts {
44    ///     println!("{}: {:?} {:?}", account.name, account.balance, account.currency);
45    /// }
46    ///
47    /// // Or customize parameters using the builder
48    /// let response = client.get_accounts().page(2).per_page(50).call().await?;
49    /// # Ok(())
50    /// # }
51    /// ```
52    #[builder]
53    pub async fn get_accounts(
54        &self,
55        #[builder(default = 1)] page: u32,
56        #[builder(default = 25)] per_page: u32,
57    ) -> ApiResult<PaginatedResponse<AccountCollection>> {
58        if per_page > MAX_PER_PAGE {
59            return Err(ApiError::InvalidParameter(format!(
60                "per_page cannot exceed {MAX_PER_PAGE}",
61            )));
62        }
63
64        let mut query_params = HashMap::new();
65
66        query_params.insert("page", page.to_string());
67        query_params.insert("per_page", per_page.to_string());
68
69        self.execute_request(Method::GET, "/api/v1/accounts", Some(&query_params), None)
70            .await
71    }
72
73    /// Get a specific account by ID
74    ///
75    /// Retrieves detailed information about a single account.
76    ///
77    /// # Arguments
78    /// * `id` - The account ID to retrieve
79    ///
80    /// # Returns
81    /// Detailed account information.
82    ///
83    /// # Errors
84    /// Returns `ApiError::NotFound` if the account doesn't exist.
85    /// Returns `ApiError::Unauthorized` if the API key is invalid.
86    /// Returns `ApiError::Network` if the request fails due to network issues.
87    ///
88    /// # Example
89    /// ```no_run
90    /// use sure_client_rs::{SureClient, BearerToken, AccountId};
91    /// use uuid::Uuid;
92    ///
93    /// # async fn example(client: SureClient) -> Result<(), Box<dyn std::error::Error>> {
94    /// let account_id = AccountId::new(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap());
95    /// let account = client.get_account(&account_id).await?;
96    ///
97    /// println!("Account: {}", account.name);
98    /// println!("Balance: {}", account.balance);
99    /// # Ok(())
100    /// # }
101    /// ```
102    pub async fn get_account(&self, id: &AccountId) -> ApiResult<AccountDetail> {
103        self.execute_request(Method::GET, &format!("/api/v1/accounts/{}", id), None, None)
104            .await
105    }
106}
107
108#[bon]
109impl SureClient {
110    /// Create a new account
111    ///
112    /// Creates a new account with type-specific attributes. The account kind is
113    /// automatically derived from the attributes you provide.
114    ///
115    /// # Arguments
116    /// * `name` - The account name
117    /// * `balance` - The initial account balance
118    /// * `attributes` - Type-specific attributes that determine the account kind
119    /// * `currency` - Optional currency code (defaults to family currency if not provided)
120    /// * `institution_name` - Optional name of the financial institution
121    /// * `institution_domain` - Optional domain of the financial institution
122    /// * `notes` - Optional additional notes
123    ///
124    /// # Returns
125    /// The newly created account with full details.
126    ///
127    /// # Errors
128    /// Returns `ApiError::ValidationError` if required fields are missing or invalid.
129    /// Returns `ApiError::Unauthorized` if the API key is invalid.
130    /// Returns `ApiError::Network` if the request fails due to network issues.
131    ///
132    /// # Example
133    /// ```no_run
134    /// use sure_client_rs::{SureClient, BearerToken};
135    /// use sure_client_rs::models::account::{
136    ///     AccountableAttributes, DepositoryAttributes, DepositorySubtype
137    /// };
138    /// use rust_decimal::Decimal;
139    ///
140    /// # async fn example(client: SureClient) -> Result<(), Box<dyn std::error::Error>> {
141    /// let account = client.create_account()
142    ///     .name("Checking Account".to_string())
143    ///     .balance(Decimal::new(100000, 2)) // $1,000.00
144    ///     .attributes(AccountableAttributes::Depository(DepositoryAttributes {
145    ///         subtype: Some(DepositorySubtype::Checking),
146    ///         locked_attributes: None,
147    ///     }))
148    ///     .currency(iso_currency::Currency::USD)
149    ///     .institution_name("Bank of Example".to_string())
150    ///     .call()
151    ///     .await?;
152    ///
153    /// println!("Created account: {}", account.name);
154    /// # Ok(())
155    /// # }
156    /// ```
157    #[builder]
158    pub async fn create_account(
159        &self,
160        name: String,
161        balance: Decimal,
162        attributes: AccountableAttributes,
163        currency: Option<iso_currency::Currency>,
164        institution_name: Option<String>,
165        institution_domain: Option<Url>,
166        notes: Option<String>,
167    ) -> ApiResult<AccountDetail> {
168        // Derive the account kind from the attributes
169        let kind = attributes.kind();
170
171        let request = CreateAccountRequest {
172            account: CreateAccountData {
173                name,
174                kind,
175                balance,
176                currency,
177                institution_name,
178                institution_domain,
179                notes,
180                accountable_attributes: attributes,
181            },
182        };
183
184        self.execute_request(
185            Method::POST,
186            "/api/v1/accounts",
187            None,
188            Some(serde_json::to_string(&request)?),
189        )
190        .await
191    }
192
193    /// Update an account
194    ///
195    /// Updates an existing account with new values. Only fields provided in the
196    /// request will be updated. If updating attributes, the entire attributes object
197    /// must be provided as it replaces the existing attributes.
198    ///
199    /// # Arguments
200    /// * `id` - The account ID to update
201    /// * `name` - Optional new account name
202    /// * `balance` - Optional new balance
203    /// * `institution_name` - Optional new institution name
204    /// * `institution_domain` - Optional new institution domain
205    /// * `notes` - Optional new notes
206    /// * `attributes` - Optional new account-specific attributes (replaces existing)
207    ///
208    /// # Returns
209    /// The updated account.
210    ///
211    /// # Errors
212    /// Returns `ApiError::NotFound` if the account doesn't exist.
213    /// Returns `ApiError::ValidationError` if the provided values are invalid.
214    /// Returns `ApiError::Unauthorized` if the API key is invalid.
215    /// Returns `ApiError::Network` if the request fails due to network issues.
216    ///
217    /// # Example
218    /// ```no_run
219    /// use sure_client_rs::{SureClient, BearerToken, AccountId};
220    /// use sure_client_rs::models::account::{
221    ///     AccountableAttributes, DepositoryAttributes, DepositorySubtype
222    /// };
223    /// use rust_decimal::Decimal;
224    /// use uuid::Uuid;
225    ///
226    /// # async fn example(client: SureClient) -> Result<(), Box<dyn std::error::Error>> {
227    /// let account_id = AccountId::new(Uuid::new_v4());
228    ///
229    /// // Update just the name and balance
230    /// let account = client.update_account()
231    ///     .id(&account_id)
232    ///     .name("Updated Account Name".to_string())
233    ///     .balance(Decimal::new(150000, 2)) // $1,500.00
234    ///     .call()
235    ///     .await?;
236    ///
237    /// // Update attributes
238    /// let updated = client.update_account()
239    ///     .id(&account_id)
240    ///     .attributes(AccountableAttributes::Depository(DepositoryAttributes {
241    ///         subtype: Some(DepositorySubtype::Savings),
242    ///         locked_attributes: None,
243    ///     }))
244    ///     .call()
245    ///     .await?;
246    ///
247    /// println!("Updated account: {}", account.name);
248    /// # Ok(())
249    /// # }
250    /// ```
251    #[builder]
252    pub async fn update_account(
253        &self,
254        id: &AccountId,
255        name: Option<String>,
256        balance: Option<Decimal>,
257        institution_name: Option<String>,
258        institution_domain: Option<Url>,
259        notes: Option<String>,
260        attributes: Option<AccountableAttributes>,
261    ) -> ApiResult<AccountDetail> {
262        let request = UpdateAccountRequest {
263            account: UpdateAccountData {
264                name,
265                balance,
266                institution_name,
267                institution_domain,
268                notes,
269                accountable_attributes: attributes,
270            },
271        };
272
273        self.execute_request(
274            Method::PATCH,
275            &format!("/api/v1/accounts/{}", id),
276            None,
277            Some(serde_json::to_string(&request)?),
278        )
279        .await
280    }
281
282    /// Delete an account
283    ///
284    /// Permanently deletes an account.
285    ///
286    /// # Arguments
287    /// * `id` - The account ID to delete
288    ///
289    /// # Returns
290    /// A confirmation message.
291    ///
292    /// # Errors
293    /// Returns `ApiError::NotFound` if the account doesn't exist.
294    /// Returns `ApiError::ValidationError` if the account cannot be deleted (e.g., linked account).
295    /// Returns `ApiError::Unauthorized` if the API key is invalid.
296    /// Returns `ApiError::Network` if the request fails due to network issues.
297    ///
298    /// # Example
299    /// ```no_run
300    /// use sure_client_rs::{SureClient, BearerToken, AccountId};
301    /// use uuid::Uuid;
302    ///
303    /// # async fn example(client: SureClient) -> Result<(), Box<dyn std::error::Error>> {
304    /// let account_id = AccountId::new(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap());
305    /// let response = client.delete_account(&account_id).await?;
306    ///
307    /// println!("Deleted: {}", response.message);
308    /// # Ok(())
309    /// # }
310    /// ```
311    pub async fn delete_account(&self, id: &AccountId) -> ApiResult<DeleteResponse> {
312        self.execute_request(
313            Method::DELETE,
314            &format!("/api/v1/accounts/{}", id),
315            None,
316            None,
317        )
318        .await
319    }
320}