1#![allow(elided_named_lifetimes)]
54#![allow(missing_docs)]
55#![allow(unused_imports)]
56#![allow(clippy::needless_lifetimes)]
57#![allow(clippy::too_many_arguments)]
58#![cfg_attr(docsrs, feature(doc_cfg))]
59
60#[cfg(feature = "requests")]
61pub mod basic;
62#[cfg(feature = "requests")]
63pub mod batch;
64#[cfg(feature = "requests")]
65pub mod gdpr;
66mod methods;
67#[cfg(feature = "requests")]
68pub mod public_object;
69#[cfg(feature = "requests")]
70pub mod search;
71#[cfg(test)]
72mod tests;
73pub mod types;
74
75#[cfg(feature = "requests")]
76use std::env;
77
78#[cfg(not(target_arch = "wasm32"))]
79#[cfg(feature = "requests")]
80static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
81
82#[derive(Clone, Debug)]
84#[cfg(feature = "requests")]
85pub struct Client {
86 token: String,
87 base_url: String,
88
89 #[cfg(feature = "retry")]
90 client: reqwest_middleware::ClientWithMiddleware,
91 #[cfg(feature = "retry")]
92 #[cfg(not(target_arch = "wasm32"))]
93 #[allow(dead_code)]
94 client_http1_only: reqwest_middleware::ClientWithMiddleware,
95
96 #[cfg(not(feature = "retry"))]
97 client: reqwest::Client,
98 #[cfg(not(feature = "retry"))]
99 #[cfg(not(target_arch = "wasm32"))]
100 #[allow(dead_code)]
101 client_http1_only: reqwest::Client,
102}
103
104#[cfg(feature = "retry")]
106#[cfg(feature = "requests")]
107pub struct RequestBuilder(pub reqwest_middleware::RequestBuilder);
108#[cfg(not(feature = "retry"))]
109#[cfg(feature = "requests")]
110pub struct RequestBuilder(pub reqwest::RequestBuilder);
111
112#[cfg(feature = "requests")]
113impl Client {
114 #[tracing::instrument]
119 #[cfg(not(target_arch = "wasm32"))]
120 pub fn new_from_reqwest<T>(
121 token: T,
122 builder_http: reqwest::ClientBuilder,
123 builder_websocket: reqwest::ClientBuilder,
124 ) -> Self
125 where
126 T: ToString + std::fmt::Debug,
127 {
128 #[cfg(feature = "retry")]
129 {
130 let retry_policy =
132 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
133 match (builder_http.build(), builder_websocket.build()) {
134 (Ok(c), Ok(c1)) => {
135 let client = reqwest_middleware::ClientBuilder::new(c)
136 .with(reqwest_tracing::TracingMiddleware::default())
138 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
140 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
141 |req: &reqwest::Request| req.try_clone().is_some(),
142 ))
143 .build();
144 let client_http1_only = reqwest_middleware::ClientBuilder::new(c1)
145 .with(reqwest_tracing::TracingMiddleware::default())
146 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
147 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
148 |req: &reqwest::Request| req.try_clone().is_some(),
149 ))
150 .build();
151 Client {
152 token: token.to_string(),
153 base_url: "https://api.hubapi.com".to_string(),
154
155 client,
156 client_http1_only,
157 }
158 }
159 (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
160 }
161 }
162 #[cfg(not(feature = "retry"))]
163 {
164 match (builder_http.build(), builder_websocket.build()) {
165 (Ok(c), Ok(c1)) => Client {
166 token: token.to_string(),
167 base_url: "https://api.hubapi.com".to_string(),
168
169 client: c,
170 client_http1_only: c1,
171 },
172 (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
173 }
174 }
175 }
176
177 #[tracing::instrument]
182 #[cfg(target_arch = "wasm32")]
183 pub fn new_from_reqwest<T>(token: T, builder_http: reqwest::ClientBuilder) -> Self
184 where
185 T: ToString + std::fmt::Debug,
186 {
187 #[cfg(feature = "retry")]
188 {
189 let retry_policy =
191 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
192 match builder_http.build() {
193 Ok(c) => {
194 let client = reqwest_middleware::ClientBuilder::new(c)
195 .with(reqwest_tracing::TracingMiddleware::default())
197 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
199 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
200 |req: &reqwest::Request| req.try_clone().is_some(),
201 ))
202 .build();
203 Client {
204 token: token.to_string(),
205 base_url: "https://api.hubapi.com".to_string(),
206
207 client,
208 }
209 }
210 Err(e) => panic!("creating reqwest client failed: {:?}", e),
211 }
212 }
213 #[cfg(not(feature = "retry"))]
214 {
215 match builder_http.build() {
216 Ok(c) => Client {
217 token: token.to_string(),
218 base_url: "https://api.hubapi.com".to_string(),
219
220 client: c,
221 },
222 Err(e) => panic!("creating reqwest client failed: {:?}", e),
223 }
224 }
225 }
226
227 #[tracing::instrument]
231 pub fn new<T>(token: T) -> Self
232 where
233 T: ToString + std::fmt::Debug,
234 {
235 #[cfg(not(target_arch = "wasm32"))]
236 let client = reqwest::Client::builder()
237 .user_agent(APP_USER_AGENT)
238 .timeout(std::time::Duration::from_secs(600))
240 .connect_timeout(std::time::Duration::from_secs(60));
241 #[cfg(target_arch = "wasm32")]
242 let client = reqwest::Client::builder();
243 #[cfg(not(target_arch = "wasm32"))]
244 let client_http1 = reqwest::Client::builder()
245 .user_agent(APP_USER_AGENT)
247 .timeout(std::time::Duration::from_secs(600))
248 .connect_timeout(std::time::Duration::from_secs(60))
249 .http1_only();
250 #[cfg(not(target_arch = "wasm32"))]
251 return Self::new_from_reqwest(token, client, client_http1);
252 #[cfg(target_arch = "wasm32")]
253 Self::new_from_reqwest(token, client)
254 }
255
256 #[tracing::instrument]
258 pub fn set_base_url<H>(&mut self, base_url: H)
259 where
260 H: Into<String> + std::fmt::Display + std::fmt::Debug,
261 {
262 self.base_url = base_url.to_string().trim_end_matches('/').to_string();
263 }
264
265 #[tracing::instrument]
267 pub fn new_from_env() -> Self {
268 let token =
269 env::var("HUBSPOT_CONTACTS_API_TOKEN").expect("must set HUBSPOT_CONTACTS_API_TOKEN");
270 let base_url =
271 env::var("HUBSPOT_CONTACTS_HOST").unwrap_or("https://api.hubapi.com".to_string());
272
273 let mut c = Client::new(token);
274 c.set_base_url(base_url);
275 c
276 }
277
278 #[tracing::instrument]
280 pub async fn request_raw(
281 &self,
282 method: reqwest::Method,
283 uri: &str,
284 body: Option<reqwest::Body>,
285 ) -> anyhow::Result<RequestBuilder> {
286 let u = if uri.starts_with("https://") || uri.starts_with("http://") {
287 uri.to_string()
288 } else {
289 format!("{}/{}", self.base_url, uri.trim_start_matches('/'))
290 };
291
292 let mut req = self.client.request(method, &u);
293
294 req = req.bearer_auth(&self.token);
296
297 req = req.header(
299 reqwest::header::ACCEPT,
300 reqwest::header::HeaderValue::from_static("application/json"),
301 );
302 req = req.header(
303 reqwest::header::CONTENT_TYPE,
304 reqwest::header::HeaderValue::from_static("application/json"),
305 );
306
307 if let Some(body) = body {
308 req = req.body(body);
309 }
310
311 Ok(RequestBuilder(req))
312 }
313
314 pub fn batch(&self) -> batch::Batch {
316 batch::Batch::new(self.clone())
317 }
318
319 pub fn basic(&self) -> basic::Basic {
321 basic::Basic::new(self.clone())
322 }
323
324 pub fn public_object(&self) -> public_object::PublicObject {
326 public_object::PublicObject::new(self.clone())
327 }
328
329 pub fn gdpr(&self) -> gdpr::Gdpr {
331 gdpr::Gdpr::new(self.clone())
332 }
333
334 pub fn search(&self) -> search::Search {
336 search::Search::new(self.clone())
337 }
338}