use std::sync::Arc;
use std::time::Duration;
use reqwest::{Client, Method};
use url::Url;
use crate::rate_limit::WindowRateLimiter;
use crate::request::SdkRequest;
pub const COMPANIES_HOUSE_API_ROOT: &str = "https://api.company-information.service.gov.uk";
#[derive(Debug, Clone)]
pub enum Auth {
ApiKey { key: String },
Bearer { token: String },
}
#[derive(Debug, Clone)]
pub struct SdkClientBuilder {
auth: Auth,
base_url: Url,
timeout: Duration,
enable_ch_rate_limit: bool,
user_agent: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum SdkBuildError {
#[error(transparent)]
HttpClient(#[from] reqwest::Error),
#[error(transparent)]
Url(#[from] url::ParseError),
#[error("API key must be non-empty")]
EmptyApiKey,
#[error("bearer token must be non-empty")]
EmptyBearer,
}
#[derive(Clone)]
pub struct SdkClient {
pub(crate) inner: Arc<SdkClientInner>,
}
pub(crate) struct SdkClientInner {
pub http: Client,
pub base_url: Url,
pub auth: Auth,
pub limiter: Option<Arc<WindowRateLimiter>>,
}
impl SdkClient {
pub fn builder(auth: Auth) -> SdkClientBuilder {
SdkClientBuilder::new(auth)
}
pub fn request(
&self,
method: Method,
path: impl AsRef<str>,
) -> crate::SdkResult<SdkRequest<'_>> {
SdkRequest::new(self, method, path)
}
pub fn get(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
self.request(Method::GET, path)
}
pub fn post(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
self.request(Method::POST, path)
}
pub fn put(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
self.request(Method::PUT, path)
}
pub fn delete(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
self.request(Method::DELETE, path)
}
pub fn patch(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
self.request(Method::PATCH, path)
}
}
impl SdkClientBuilder {
pub fn new(auth: Auth) -> Self {
let base_url =
Url::parse(COMPANIES_HOUSE_API_ROOT).expect("COMPANIES_HOUSE_API_ROOT is valid");
Self {
auth,
base_url,
timeout: Duration::from_secs(30),
enable_ch_rate_limit: true,
user_agent: None,
}
}
pub fn base_url(mut self, url: Url) -> Self {
self.base_url = url;
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn enable_companies_house_rate_limit(mut self, enable: bool) -> Self {
self.enable_ch_rate_limit = enable;
self
}
pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
self.user_agent = Some(ua.into());
self
}
pub fn build(self) -> Result<SdkClient, SdkBuildError> {
match &self.auth {
Auth::ApiKey { key } if key.is_empty() => return Err(SdkBuildError::EmptyApiKey),
Auth::Bearer { token } if token.is_empty() => return Err(SdkBuildError::EmptyBearer),
_ => {}
}
let mut base = self.base_url;
let path = base.path();
if path.is_empty() || path == "/" {
base.set_path("/");
} else if !path.ends_with('/') {
base.set_path(&format!("{path}/"));
}
let mut client_builder = Client::builder().timeout(self.timeout);
if let Some(ua) = self.user_agent {
client_builder = client_builder.user_agent(ua);
}
let http = client_builder.build()?;
let limiter = self
.enable_ch_rate_limit
.then(|| Arc::new(WindowRateLimiter::companies_house_default()));
Ok(SdkClient {
inner: Arc::new(SdkClientInner {
http,
base_url: base,
auth: self.auth,
limiter,
}),
})
}
}