gov_uk_sdk_core/
client.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use reqwest::{Client, Method};
5use url::Url;
6
7use crate::rate_limit::WindowRateLimiter;
8use crate::request::SdkRequest;
9
10pub const COMPANIES_HOUSE_API_ROOT: &str = "https://api.company-information.service.gov.uk";
12
13#[derive(Debug, Clone)]
15pub enum Auth {
16 ApiKey { key: String },
18 Bearer { token: String },
20}
21
22#[derive(Debug, Clone)]
24pub struct SdkClientBuilder {
25 auth: Auth,
26 base_url: Url,
27 timeout: Duration,
28 enable_ch_rate_limit: bool,
29 user_agent: Option<String>,
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum SdkBuildError {
34 #[error(transparent)]
35 HttpClient(#[from] reqwest::Error),
36 #[error(transparent)]
37 Url(#[from] url::ParseError),
38 #[error("API key must be non-empty")]
39 EmptyApiKey,
40 #[error("bearer token must be non-empty")]
41 EmptyBearer,
42}
43
44#[derive(Clone)]
46pub struct SdkClient {
47 pub(crate) inner: Arc<SdkClientInner>,
48}
49
50pub(crate) struct SdkClientInner {
51 pub http: Client,
52 pub base_url: Url,
53 pub auth: Auth,
54 pub limiter: Option<Arc<WindowRateLimiter>>,
55}
56
57impl SdkClient {
58 pub fn builder(auth: Auth) -> SdkClientBuilder {
60 SdkClientBuilder::new(auth)
61 }
62
63 pub fn request(
65 &self,
66 method: Method,
67 path: impl AsRef<str>,
68 ) -> crate::SdkResult<SdkRequest<'_>> {
69 SdkRequest::new(self, method, path)
70 }
71
72 pub fn get(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
74 self.request(Method::GET, path)
75 }
76
77 pub fn post(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
79 self.request(Method::POST, path)
80 }
81
82 pub fn put(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
84 self.request(Method::PUT, path)
85 }
86
87 pub fn delete(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
89 self.request(Method::DELETE, path)
90 }
91
92 pub fn patch(&self, path: impl AsRef<str>) -> crate::SdkResult<SdkRequest<'_>> {
94 self.request(Method::PATCH, path)
95 }
96}
97
98impl SdkClientBuilder {
99 pub fn new(auth: Auth) -> Self {
101 let base_url =
102 Url::parse(COMPANIES_HOUSE_API_ROOT).expect("COMPANIES_HOUSE_API_ROOT is valid");
103 Self {
104 auth,
105 base_url,
106 timeout: Duration::from_secs(30),
107 enable_ch_rate_limit: true,
108 user_agent: None,
109 }
110 }
111
112 pub fn base_url(mut self, url: Url) -> Self {
114 self.base_url = url;
115 self
116 }
117
118 pub fn timeout(mut self, timeout: Duration) -> Self {
120 self.timeout = timeout;
121 self
122 }
123
124 pub fn enable_companies_house_rate_limit(mut self, enable: bool) -> Self {
127 self.enable_ch_rate_limit = enable;
128 self
129 }
130
131 pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
133 self.user_agent = Some(ua.into());
134 self
135 }
136
137 pub fn build(self) -> Result<SdkClient, SdkBuildError> {
139 match &self.auth {
140 Auth::ApiKey { key } if key.is_empty() => return Err(SdkBuildError::EmptyApiKey),
141 Auth::Bearer { token } if token.is_empty() => return Err(SdkBuildError::EmptyBearer),
142 _ => {}
143 }
144
145 let mut base = self.base_url;
146 let path = base.path();
147 if path.is_empty() || path == "/" {
148 base.set_path("/");
149 } else if !path.ends_with('/') {
150 base.set_path(&format!("{path}/"));
151 }
152
153 let mut client_builder = Client::builder().timeout(self.timeout);
154 if let Some(ua) = self.user_agent {
155 client_builder = client_builder.user_agent(ua);
156 }
157 let http = client_builder.build()?;
158
159 let limiter = self
160 .enable_ch_rate_limit
161 .then(|| Arc::new(WindowRateLimiter::companies_house_default()));
162
163 Ok(SdkClient {
164 inner: Arc::new(SdkClientInner {
165 http,
166 base_url: base,
167 auth: self.auth,
168 limiter,
169 }),
170 })
171 }
172}