#[doc(inline)]
pub use enums::*;
pub mod adjustment;
pub mod corporate;
mod enums;
pub mod security;
use reqwest::{header::ACCEPT, RequestBuilder, Url};
use time::OffsetDateTime;
use crate::{
historical::{AddToForm, HistoricalGateway, ReqwestForm, API_VERSION},
reference::{
adjustment::AdjustmentFactorsClient, corporate::CorporateActionsClient,
security::SecurityMasterClient,
},
ApiKey, Error, USER_AGENT,
};
#[derive(Debug, Clone)]
pub struct Client {
key: ApiKey,
base_url: Url,
gateway: HistoricalGateway,
client: reqwest::Client,
}
impl Client {
pub fn builder() -> ClientBuilder<Unset> {
ClientBuilder::default()
}
pub fn key(&self) -> &str {
&self.key.0
}
pub fn gateway(&self) -> HistoricalGateway {
self.gateway
}
pub fn adjustment_factors(&mut self) -> AdjustmentFactorsClient<'_> {
AdjustmentFactorsClient { inner: self }
}
pub fn corporate_actions(&mut self) -> CorporateActionsClient<'_> {
CorporateActionsClient { inner: self }
}
pub fn security_master(&mut self) -> SecurityMasterClient<'_> {
SecurityMasterClient { inner: self }
}
pub(crate) fn post(&mut self, slug: &str) -> crate::Result<RequestBuilder> {
self.request(reqwest::Method::POST, slug)
}
fn request(&mut self, method: reqwest::Method, slug: &str) -> crate::Result<RequestBuilder> {
Ok(self
.client
.request(
method,
self.base_url
.join(&format!("v{API_VERSION}/{slug}"))
.map_err(|e| Error::Internal(format!("created invalid URL: {e:?}")))?,
)
.basic_auth(self.key(), Option::<&str>::None))
}
}
#[doc(hidden)]
#[derive(Debug, Copy, Clone)]
pub struct Unset;
pub struct ClientBuilder<AK> {
key: AK,
base_url: Option<Url>,
gateway: HistoricalGateway,
user_agent_ext: Option<String>,
http_client_builder: Option<reqwest::ClientBuilder>,
}
impl Default for ClientBuilder<Unset> {
fn default() -> Self {
Self {
key: Unset,
base_url: None,
gateway: HistoricalGateway::default(),
user_agent_ext: None,
http_client_builder: None,
}
}
}
impl<AK> ClientBuilder<AK> {
pub fn base_url(mut self, url: Url) -> Self {
self.base_url = Some(url);
self
}
pub fn gateway(mut self, gateway: HistoricalGateway) -> Self {
self.gateway = gateway;
self
}
pub fn user_agent_extension(mut self, extension: String) -> Self {
self.user_agent_ext = Some(extension);
self
}
pub fn http_client_builder(mut self, builder: reqwest::ClientBuilder) -> Self {
self.http_client_builder = Some(builder);
self
}
}
impl ClientBuilder<Unset> {
pub fn new() -> Self {
Self::default()
}
pub fn key(self, key: impl ToString) -> crate::Result<ClientBuilder<ApiKey>> {
Ok(ClientBuilder {
key: ApiKey::new(key.to_string())?,
base_url: self.base_url,
gateway: self.gateway,
user_agent_ext: self.user_agent_ext,
http_client_builder: self.http_client_builder,
})
}
pub fn key_from_env(self) -> crate::Result<ClientBuilder<ApiKey>> {
let key = crate::key_from_env()?;
self.key(key)
}
}
impl ClientBuilder<ApiKey> {
pub fn build(self) -> crate::Result<Client> {
let base_url = if let Some(url) = self.base_url {
url
} else {
self.gateway
.as_url()
.parse()
.map_err(|e| Error::bad_arg("gateway", format!("{e:?}")))?
};
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(ACCEPT, "application/json".parse().unwrap());
let user_agent = self
.user_agent_ext
.map(|ext| format!("{} {ext}", *USER_AGENT))
.unwrap_or_else(|| USER_AGENT.clone());
let http_client = self
.http_client_builder
.unwrap_or_default()
.user_agent(user_agent)
.default_headers(headers)
.build()?;
Ok(Client {
key: self.key,
base_url,
gateway: self.gateway,
client: http_client,
})
}
}
struct Start(OffsetDateTime);
impl AddToForm<Start> for ReqwestForm {
fn add_to_form(mut self, Start(start): &Start) -> Self {
self.push(("start", start.unix_timestamp_nanos().to_string()));
self
}
}
struct End(Option<OffsetDateTime>);
impl AddToForm<End> for ReqwestForm {
fn add_to_form(mut self, End(end): &End) -> Self {
if let Some(end) = end {
self.push(("end", end.unix_timestamp_nanos().to_string()));
}
self
}
}
impl AddToForm<Vec<Event>> for ReqwestForm {
fn add_to_form(mut self, events: &Vec<Event>) -> Self {
if !events.is_empty() {
self.push((
"events",
events
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(","),
));
}
self
}
}
impl AddToForm<Vec<Country>> for ReqwestForm {
fn add_to_form(mut self, countries: &Vec<Country>) -> Self {
if !countries.is_empty() {
self.push((
"countries",
countries
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(","),
));
}
self
}
}
impl AddToForm<Vec<SecurityType>> for ReqwestForm {
fn add_to_form(mut self, security_types: &Vec<SecurityType>) -> Self {
if !security_types.is_empty() {
self.push((
"security_types",
security_types
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(","),
));
}
self
}
}
#[cfg(test)]
mod test_infra {
use wiremock::MockServer;
use crate::{historical::test_infra::API_KEY, ReferenceClient};
pub fn client(mock_server: &MockServer) -> ReferenceClient {
ReferenceClient::builder()
.base_url(mock_server.uri().parse().unwrap())
.key(API_KEY)
.unwrap()
.build()
.unwrap()
}
}