1#![allow(missing_docs)]
73#![allow(clippy::needless_lifetimes)]
74#![allow(clippy::too_many_arguments)]
75#![cfg_attr(docsrs, feature(doc_cfg))]
76
77#[cfg(feature = "requests")]
78pub mod application_management;
79#[cfg(feature = "requests")]
80pub mod ats;
81#[cfg(feature = "requests")]
82pub mod companies;
83#[cfg(feature = "requests")]
84pub mod current_user;
85#[cfg(feature = "requests")]
86pub mod employees;
87#[cfg(feature = "requests")]
88pub mod groups;
89#[cfg(feature = "requests")]
90pub mod leaves;
91mod methods;
92#[cfg(feature = "requests")]
93pub mod saml;
94#[cfg(test)]
95mod tests;
96pub mod types;
97pub mod utils;
98
99#[cfg(feature = "requests")]
100use std::env;
101
102#[cfg(not(target_arch = "wasm32"))]
103#[cfg(feature = "requests")]
104static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
105
106#[derive(Clone, Debug)]
108#[cfg(feature = "requests")]
109pub struct Client {
110 token: String,
111 base_url: String,
112
113 #[cfg(feature = "retry")]
114 client: reqwest_middleware::ClientWithMiddleware,
115 #[cfg(feature = "retry")]
116 #[cfg(not(target_arch = "wasm32"))]
117 #[allow(dead_code)]
118 client_http1_only: reqwest_middleware::ClientWithMiddleware,
119
120 #[cfg(not(feature = "retry"))]
121 client: reqwest::Client,
122 #[cfg(not(feature = "retry"))]
123 #[cfg(not(target_arch = "wasm32"))]
124 #[allow(dead_code)]
125 client_http1_only: reqwest::Client,
126}
127
128#[cfg(feature = "retry")]
130#[cfg(feature = "requests")]
131pub struct RequestBuilder(pub reqwest_middleware::RequestBuilder);
132#[cfg(not(feature = "retry"))]
133#[cfg(feature = "requests")]
134pub struct RequestBuilder(pub reqwest::RequestBuilder);
135
136#[cfg(feature = "requests")]
137impl Client {
138 #[tracing::instrument]
143 #[cfg(not(target_arch = "wasm32"))]
144 pub fn new_from_reqwest<T>(
145 token: T,
146 builder_http: reqwest::ClientBuilder,
147 builder_websocket: reqwest::ClientBuilder,
148 ) -> Self
149 where
150 T: ToString + std::fmt::Debug,
151 {
152 #[cfg(feature = "retry")]
153 {
154 let retry_policy =
156 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
157 match (builder_http.build(), builder_websocket.build()) {
158 (Ok(c), Ok(c1)) => {
159 let client = reqwest_middleware::ClientBuilder::new(c)
160 .with(reqwest_tracing::TracingMiddleware::default())
162 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
164 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
165 |req: &reqwest::Request| req.try_clone().is_some(),
166 ))
167 .build();
168 let client_http1_only = reqwest_middleware::ClientBuilder::new(c1)
169 .with(reqwest_tracing::TracingMiddleware::default())
170 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
171 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
172 |req: &reqwest::Request| req.try_clone().is_some(),
173 ))
174 .build();
175 Client {
176 token: token.to_string(),
177 base_url: "https://api.rippling.com".to_string(),
178
179 client,
180 client_http1_only,
181 }
182 }
183 (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
184 }
185 }
186 #[cfg(not(feature = "retry"))]
187 {
188 match (builder_http.build(), builder_websocket.build()) {
189 (Ok(c), Ok(c1)) => Client {
190 token: token.to_string(),
191 base_url: "https://api.rippling.com".to_string(),
192
193 client: c,
194 client_http1_only: c1,
195 },
196 (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
197 }
198 }
199 }
200
201 #[tracing::instrument]
206 #[cfg(target_arch = "wasm32")]
207 pub fn new_from_reqwest<T>(token: T, builder_http: reqwest::ClientBuilder) -> Self
208 where
209 T: ToString + std::fmt::Debug,
210 {
211 #[cfg(feature = "retry")]
212 {
213 let retry_policy =
215 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
216 match builder_http.build() {
217 Ok(c) => {
218 let client = reqwest_middleware::ClientBuilder::new(c)
219 .with(reqwest_tracing::TracingMiddleware::default())
221 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
223 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
224 |req: &reqwest::Request| req.try_clone().is_some(),
225 ))
226 .build();
227 Client {
228 token: token.to_string(),
229 base_url: "https://api.rippling.com".to_string(),
230
231 client,
232 }
233 }
234 Err(e) => panic!("creating reqwest client failed: {:?}", e),
235 }
236 }
237 #[cfg(not(feature = "retry"))]
238 {
239 match builder_http.build() {
240 Ok(c) => Client {
241 token: token.to_string(),
242 base_url: "https://api.rippling.com".to_string(),
243
244 client: c,
245 },
246 Err(e) => panic!("creating reqwest client failed: {:?}", e),
247 }
248 }
249 }
250
251 #[tracing::instrument]
255 pub fn new<T>(token: T) -> Self
256 where
257 T: ToString + std::fmt::Debug,
258 {
259 #[cfg(not(target_arch = "wasm32"))]
260 let client = reqwest::Client::builder()
261 .user_agent(APP_USER_AGENT)
262 .timeout(std::time::Duration::from_secs(600))
264 .connect_timeout(std::time::Duration::from_secs(60));
265 #[cfg(target_arch = "wasm32")]
266 let client = reqwest::Client::builder();
267 #[cfg(not(target_arch = "wasm32"))]
268 let client_http1 = reqwest::Client::builder()
269 .user_agent(APP_USER_AGENT)
271 .timeout(std::time::Duration::from_secs(600))
272 .connect_timeout(std::time::Duration::from_secs(60))
273 .http1_only();
274 #[cfg(not(target_arch = "wasm32"))]
275 return Self::new_from_reqwest(token, client, client_http1);
276 #[cfg(target_arch = "wasm32")]
277 Self::new_from_reqwest(token, client)
278 }
279
280 #[tracing::instrument]
282 pub fn set_base_url<H>(&mut self, base_url: H)
283 where
284 H: Into<String> + std::fmt::Display + std::fmt::Debug,
285 {
286 self.base_url = base_url.to_string().trim_end_matches('/').to_string();
287 }
288
289 #[tracing::instrument]
291 pub fn new_from_env() -> Self {
292 let token = env::var("RIPPLING_BASE_API_TOKEN").expect("must set RIPPLING_BASE_API_TOKEN");
293
294 Client::new(token)
295 }
296
297 #[tracing::instrument]
299 pub async fn request_raw(
300 &self,
301 method: reqwest::Method,
302 uri: &str,
303 body: Option<reqwest::Body>,
304 ) -> anyhow::Result<RequestBuilder> {
305 let u = if uri.starts_with("https://") || uri.starts_with("http://") {
306 uri.to_string()
307 } else {
308 format!("{}/{}", self.base_url, uri.trim_start_matches('/'))
309 };
310
311 let mut req = self.client.request(method, &u);
312
313 req = req.bearer_auth(&self.token);
315
316 req = req.header(
318 reqwest::header::ACCEPT,
319 reqwest::header::HeaderValue::from_static("application/json"),
320 );
321 req = req.header(
322 reqwest::header::CONTENT_TYPE,
323 reqwest::header::HeaderValue::from_static("application/json"),
324 );
325
326 if let Some(body) = body {
327 req = req.body(body);
328 }
329
330 Ok(RequestBuilder(req))
331 }
332
333 pub fn companies(&self) -> companies::Companies {
335 companies::Companies::new(self.clone())
336 }
337
338 pub fn employees(&self) -> employees::Employees {
340 employees::Employees::new(self.clone())
341 }
342
343 pub fn groups(&self) -> groups::Groups {
345 groups::Groups::new(self.clone())
346 }
347
348 pub fn saml(&self) -> saml::Saml {
350 saml::Saml::new(self.clone())
351 }
352
353 pub fn current_user(&self) -> current_user::CurrentUser {
355 current_user::CurrentUser::new(self.clone())
356 }
357
358 pub fn ats(&self) -> ats::Ats {
360 ats::Ats::new(self.clone())
361 }
362
363 pub fn application_management(&self) -> application_management::ApplicationManagement {
365 application_management::ApplicationManagement::new(self.clone())
366 }
367
368 pub fn leaves(&self) -> leaves::Leaves {
370 leaves::Leaves::new(self.clone())
371 }
372}