alpaca_data/corporate_actions/
response.rs1use crate::{Error, transport::pagination::PaginatedResponse};
2
3use super::CorporateActions;
4
5#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize)]
6pub struct ListResponse {
7 pub corporate_actions: CorporateActions,
8 pub next_page_token: Option<String>,
9}
10
11impl PaginatedResponse for ListResponse {
12 fn next_page_token(&self) -> Option<&str> {
13 self.next_page_token.as_deref()
14 }
15
16 fn merge_page(&mut self, next: Self) -> Result<(), Error> {
17 self.corporate_actions.merge(next.corporate_actions);
18 self.next_page_token = next.next_page_token;
19 Ok(())
20 }
21
22 fn clear_next_page_token(&mut self) {
23 self.next_page_token = None;
24 }
25}
26
27#[cfg(test)]
28mod tests {
29 use std::str::FromStr;
30
31 use super::ListResponse;
32 use crate::transport::pagination::PaginatedResponse;
33 use rust_decimal::Decimal;
34
35 #[test]
36 fn list_response_deserializes_official_bucketed_wrapper_shape() {
37 let response: ListResponse = serde_json::from_str(
38 r#"{"corporate_actions":{"cash_dividends":[{"id":"e2b597ca-c2cb-47af-9315-cafb8708766d","symbol":"640CVR031","cusip":"640CVR031","rate":0.055284,"special":false,"foreign":false,"process_date":"2024-08-14","ex_date":"2024-08-07","record_date":"2024-08-07","payable_date":"2024-08-15"}],"name_changes":[{"id":"564620f3-dac4-4558-a227-5c8dd6f4d82e","old_symbol":"007975113","old_cusip":"007975113","new_symbol":"22112H119","new_cusip":"22112H119","process_date":"2024-08-13"}],"contract_adjustments":[{"id":"ca-undocumented","memo":"undocumented family"}],"mystery_bucket":[{"id":"mystery-1","field":"value"}]},"next_page_token":"page-2"}"#,
39 )
40 .expect("response should deserialize");
41
42 assert_eq!(response.corporate_actions.cash_dividends.len(), 1);
43 assert_eq!(
44 response.corporate_actions.cash_dividends[0].rate,
45 Decimal::from_str("0.055284").expect("decimal literal should parse")
46 );
47 assert_eq!(
48 response.corporate_actions.name_changes[0].new_symbol,
49 "22112H119"
50 );
51 assert_eq!(response.corporate_actions.name_changes.len(), 1);
52 assert_eq!(response.corporate_actions.contract_adjustments.len(), 1);
53 assert_eq!(
54 response
55 .corporate_actions
56 .other
57 .get("mystery_bucket")
58 .map(Vec::len),
59 Some(1)
60 );
61 assert_eq!(response.next_page_token.as_deref(), Some("page-2"));
62 }
63
64 #[test]
65 fn list_response_merge_appends_bucketed_pages_and_clears_next_page_token() {
66 let mut first: ListResponse = serde_json::from_str(
67 r#"{"corporate_actions":{"cash_dividends":[{"id":"ca-1","symbol":"AAA","cusip":"111111111","rate":0.1,"special":false,"foreign":false,"process_date":"2024-08-01","ex_date":"2024-08-01"}],"contract_adjustments":[{"id":"undoc-1"}],"mystery_bucket":[{"id":"mystery-1"}]},"next_page_token":"page-2"}"#,
68 )
69 .expect("first response should deserialize");
70 let second: ListResponse = serde_json::from_str(
71 r#"{"corporate_actions":{"cash_dividends":[{"id":"ca-2","symbol":"BBB","cusip":"222222222","rate":0.2,"special":false,"foreign":false,"process_date":"2024-08-02","ex_date":"2024-08-02"}],"name_changes":[{"id":"name-1","old_symbol":"OLD","old_cusip":"333333333","new_symbol":"NEW","new_cusip":"444444444","process_date":"2024-08-02"}],"contract_adjustments":[{"id":"undoc-2"}],"mystery_bucket":[{"id":"mystery-2"}]},"next_page_token":null}"#,
72 )
73 .expect("second response should deserialize");
74
75 first
76 .merge_page(second)
77 .expect("merge should append bucketed corporate action pages");
78 first.clear_next_page_token();
79
80 assert_eq!(first.corporate_actions.cash_dividends.len(), 2);
81 assert_eq!(
82 first.corporate_actions.cash_dividends[1].rate,
83 Decimal::from_str("0.2").expect("decimal literal should parse")
84 );
85 assert_eq!(first.corporate_actions.name_changes.len(), 1);
86 assert_eq!(first.corporate_actions.contract_adjustments.len(), 2);
87 assert_eq!(
88 first
89 .corporate_actions
90 .other
91 .get("mystery_bucket")
92 .map(Vec::len),
93 Some(2)
94 );
95 assert_eq!(first.next_page_token, None);
96 }
97
98 #[test]
99 fn list_response_deserializes_non_dividend_decimal_rate_fields() {
100 let response: ListResponse = serde_json::from_str(
101 r#"{"corporate_actions":{"forward_splits":[{"id":"fs-1","symbol":"ABC","cusip":"000000001","new_rate":3.0,"old_rate":2.0,"process_date":"2024-08-14","ex_date":"2024-08-07","record_date":"2024-08-07","payable_date":"2024-08-15"}],"stock_and_cash_mergers":[{"id":"scm-1","acquirer_symbol":"AAA","acquirer_cusip":"111111111","acquirer_rate":0.75,"acquiree_symbol":"BBB","acquiree_cusip":"222222222","acquiree_rate":1.0,"cash_rate":4.25,"process_date":"2024-08-14","effective_date":"2024-08-15","payable_date":"2024-08-16"}]},"next_page_token":null}"#,
102 )
103 .expect("response should deserialize");
104
105 assert_eq!(
106 response.corporate_actions.forward_splits[0].new_rate,
107 Decimal::from_str("3.0").expect("decimal literal should parse")
108 );
109 assert_eq!(
110 response.corporate_actions.stock_and_cash_mergers[0].cash_rate,
111 Decimal::from_str("4.25").expect("decimal literal should parse")
112 );
113 }
114}