use reqwest::{header::ACCEPT, IntoUrl, RequestBuilder, Url};
use crate::Error;
use super::{
batch::BatchClient, metadata::MetadataClient, symbology::SymbologyClient,
timeseries::TimeseriesClient, HistoricalGateway, API_VERSION,
};
pub struct Client {
key: String,
base_url: Url,
gateway: HistoricalGateway,
client: reqwest::Client,
}
const USER_AGENT: &str = concat!("Databento/", env!("CARGO_PKG_VERSION"), " Rust");
impl Client {
pub fn builder() -> ClientBuilder<Unset> {
ClientBuilder::default()
}
pub fn new(key: String, gateway: HistoricalGateway) -> crate::Result<Self> {
let url = match gateway {
HistoricalGateway::Bo1 => "https://hist.databento.com",
};
Self::with_url(url, key, gateway)
}
pub fn with_url(
url: impl IntoUrl,
key: String,
gateway: HistoricalGateway,
) -> crate::Result<Self> {
let base_url = url
.into_url()
.map_err(|e| Error::bad_arg("url", format!("{e:?}")))?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(ACCEPT, "application/json".parse().unwrap());
Ok(Self {
key,
base_url,
gateway,
client: reqwest::ClientBuilder::new()
.user_agent(USER_AGENT)
.default_headers(headers)
.build()?,
})
}
pub fn key(&self) -> &str {
&self.key
}
pub fn gateway(&self) -> HistoricalGateway {
self.gateway
}
pub fn batch(&mut self) -> BatchClient {
BatchClient { inner: self }
}
pub fn metadata(&mut self) -> MetadataClient {
MetadataClient { inner: self }
}
pub fn symbology(&mut self) -> SymbologyClient {
SymbologyClient { inner: self }
}
pub fn timeseries(&mut self) -> TimeseriesClient {
TimeseriesClient { inner: self }
}
pub(crate) fn get(&mut self, slug: &str) -> crate::Result<RequestBuilder> {
self.request(reqwest::Method::GET, slug)
}
pub(crate) fn get_with_path(&mut self, path: &str) -> crate::Result<RequestBuilder> {
Ok(self
.client
.get(
self.base_url
.join(path)
.map_err(|e| Error::Internal(format!("created invalid URL: {e:?}")))?,
)
.basic_auth(&self.key, Option::<&str>::None))
}
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)]
pub struct Unset;
pub struct ClientBuilder<AK> {
key: AK,
base_url: Option<Url>,
gateway: HistoricalGateway,
}
impl Default for ClientBuilder<Unset> {
fn default() -> Self {
Self {
key: Unset,
base_url: None,
gateway: HistoricalGateway::default(),
}
}
}
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
}
}
impl ClientBuilder<Unset> {
pub fn new() -> Self {
Self::default()
}
pub fn key(self, key: impl ToString) -> crate::Result<ClientBuilder<String>> {
Ok(ClientBuilder {
key: crate::validate_key(key.to_string())?,
base_url: self.base_url,
gateway: self.gateway,
})
}
pub fn key_from_env(self) -> crate::Result<ClientBuilder<String>> {
let key = crate::key_from_env()?;
self.key(key)
}
}
impl ClientBuilder<String> {
pub fn build(self) -> crate::Result<Client> {
if let Some(url) = self.base_url {
Client::with_url(url, self.key, self.gateway)
} else {
Client::new(self.key, self.gateway)
}
}
}