1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::{
5 auth::Auth,
6 corporate_actions::CorporateActionsClient,
7 crypto::CryptoClient,
8 error::Error,
9 news::NewsClient,
10 options::OptionsClient,
11 stocks::StocksClient,
12 transport::{http::HttpClient, rate_limit::RateLimiter, retry::RetryPolicy},
13};
14
15#[derive(Clone, Debug)]
31pub struct Client {
32 pub(crate) inner: Arc<Inner>,
33}
34
35#[allow(dead_code)]
36#[derive(Debug)]
37pub(crate) struct Inner {
38 pub(crate) auth: Auth,
39 pub(crate) base_url: String,
40 pub(crate) timeout: Duration,
41 pub(crate) max_retries: u32,
42 pub(crate) max_in_flight: Option<usize>,
43 pub(crate) http: HttpClient,
44}
45
46#[derive(Clone, Debug)]
47pub struct ClientBuilder {
48 api_key: Option<String>,
49 secret_key: Option<String>,
50 base_url: Option<String>,
51 timeout: Duration,
52 max_retries: u32,
53 max_in_flight: Option<usize>,
54}
55
56impl Client {
57 pub fn new() -> Self {
61 Self::builder()
62 .build()
63 .expect("client builder is infallible during bootstrap")
64 }
65
66 pub fn builder() -> ClientBuilder {
68 ClientBuilder::default()
69 }
70
71 pub fn stocks(&self) -> StocksClient {
73 StocksClient::new(self.inner.clone())
74 }
75
76 pub fn options(&self) -> OptionsClient {
78 OptionsClient::new(self.inner.clone())
79 }
80
81 pub fn crypto(&self) -> CryptoClient {
83 CryptoClient::new(self.inner.clone())
84 }
85
86 pub fn news(&self) -> NewsClient {
88 NewsClient::new(self.inner.clone())
89 }
90
91 pub fn corporate_actions(&self) -> CorporateActionsClient {
93 CorporateActionsClient::new(self.inner.clone())
94 }
95
96 pub(crate) fn from_parts(
97 auth: Auth,
98 base_url: String,
99 timeout: Duration,
100 max_retries: u32,
101 max_in_flight: Option<usize>,
102 ) -> Result<Self, Error> {
103 let http = HttpClient::new(
104 timeout,
105 RetryPolicy::new(max_retries),
106 RateLimiter::new(max_in_flight),
107 )?;
108
109 Ok(Self {
110 inner: Arc::new(Inner {
111 auth,
112 base_url,
113 timeout,
114 max_retries,
115 max_in_flight,
116 http,
117 }),
118 })
119 }
120}
121
122impl Default for ClientBuilder {
123 fn default() -> Self {
124 Self {
125 api_key: None,
126 secret_key: None,
127 base_url: None,
128 timeout: Duration::from_secs(10),
129 max_retries: 3,
130 max_in_flight: None,
131 }
132 }
133}
134
135impl ClientBuilder {
136 pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
138 self.api_key = Some(api_key.into());
139 self
140 }
141
142 pub fn secret_key(mut self, secret_key: impl Into<String>) -> Self {
144 self.secret_key = Some(secret_key.into());
145 self
146 }
147
148 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
150 self.base_url = Some(base_url.into());
151 self
152 }
153
154 pub fn timeout(mut self, timeout: Duration) -> Self {
156 self.timeout = timeout;
157 self
158 }
159
160 pub fn max_retries(mut self, max_retries: u32) -> Self {
162 self.max_retries = max_retries;
163 self
164 }
165
166 pub fn max_in_flight(mut self, max_in_flight: usize) -> Self {
168 self.max_in_flight = Some(max_in_flight);
169 self
170 }
171
172 pub fn build(self) -> Result<Client, Error> {
176 let auth = Auth::new(self.api_key, self.secret_key)?;
177 let base_url = self
178 .base_url
179 .unwrap_or_else(|| "https://data.alpaca.markets".to_string());
180
181 Client::from_parts(
182 auth,
183 base_url,
184 self.timeout,
185 self.max_retries,
186 self.max_in_flight,
187 )
188 }
189}