Skip to main content

alpaca_data/
client.rs

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/// Root async client for Alpaca Market Data HTTP APIs.
16///
17/// Build a client once, then obtain resource clients with [`Self::stocks`],
18/// [`Self::options`], [`Self::crypto`], [`Self::news`], and
19/// [`Self::corporate_actions`].
20///
21/// # Examples
22///
23/// ```no_run
24/// use alpaca_data::Client;
25///
26/// let client = Client::builder().build()?;
27/// # let _ = client;
28/// # Ok::<(), alpaca_data::Error>(())
29/// ```
30#[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    /// Builds a client with default runtime settings and no credentials.
58    ///
59    /// This is useful for the currently implemented public crypto endpoints.
60    pub fn new() -> Self {
61        Self::builder()
62            .build()
63            .expect("client builder is infallible during bootstrap")
64    }
65
66    /// Starts a [`ClientBuilder`] for explicit runtime configuration.
67    pub fn builder() -> ClientBuilder {
68        ClientBuilder::default()
69    }
70
71    /// Returns the stocks resource client.
72    pub fn stocks(&self) -> StocksClient {
73        StocksClient::new(self.inner.clone())
74    }
75
76    /// Returns the options resource client.
77    pub fn options(&self) -> OptionsClient {
78        OptionsClient::new(self.inner.clone())
79    }
80
81    /// Returns the crypto resource client.
82    pub fn crypto(&self) -> CryptoClient {
83        CryptoClient::new(self.inner.clone())
84    }
85
86    /// Returns the news resource client.
87    pub fn news(&self) -> NewsClient {
88        NewsClient::new(self.inner.clone())
89    }
90
91    /// Returns the corporate actions resource client.
92    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    /// Sets the Alpaca API key.
137    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    /// Sets the Alpaca API secret key.
143    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    /// Overrides the default data API base URL.
149    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    /// Sets the request timeout.
155    pub fn timeout(mut self, timeout: Duration) -> Self {
156        self.timeout = timeout;
157        self
158    }
159
160    /// Sets the retry budget for the shared HTTP transport.
161    pub fn max_retries(mut self, max_retries: u32) -> Self {
162        self.max_retries = max_retries;
163        self
164    }
165
166    /// Sets the maximum number of concurrent in-flight requests.
167    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    /// Validates configuration and builds a [`Client`].
173    ///
174    /// Credentials must be provided as a pair or omitted as a pair.
175    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}