rippling_base_api/
lib.rs

1//! A fully generated & opinionated API client for the Rippling Base API.
2//!
3//! [![docs.rs](https://docs.rs/rippling-base-api/badge.svg)](https://docs.rs/rippling-base-api)
4//!
5//! ## API Details
6//!
7//! Using Rippling's API requires either an API key or an access token retrieved from an OAuth exchange. Each is tied to a single Rippling Company.
8//!
9//! If you are a partner building an integration to Rippling,you can use [Rippling's Installation Guide](https://developer.rippling.com/docs/rippling-api/fucwnbc121hiu-installation-guide) to learn how to retrieve an access token to start using Rippling APIs.
10//!
11//! If you are a customer, you can go [here](https://developer.rippling.com/docs/rippling-api/9rw6guf819r5f-introduction-for-customers) to learn create your API keys to start using Rippling APIs.
12//!
13//! ### Using the Interactive Documentation
14//!
15//! Rippling's Documentation Portal allows you to test the API endpoints directly within the documentation. To do so, provide your API key or Access Token as a header parameter with the form Authorization Bearer: Bearer.
16//!
17//! [API Terms of Service](https://app.rippling.com/developer/tos)
18//!
19//! ### Contact
20//!
21//!
22//! | name | email |
23//! |----|----|
24//! | Rippling Support | support@rippling.com |
25//!
26//! ### License
27//!
28//!
29//! | name |
30//! |----|
31//! | MIT |
32//!
33//!
34//! ## Client Details
35//!
36//!
37//!
38//! The documentation for the crate is generated
39//! along with the code to make this library easy to use.
40//!
41//!
42//! To install the library, add the following to your `Cargo.toml` file.
43//!
44//! ```toml
45//! [dependencies]
46//! rippling-base-api = "0.1.0"
47//! ```
48//!
49//! ## Basic example
50//!
51//! Typical use will require intializing a `Client`. This requires
52//! a user agent string and set of credentials.
53//!
54//! ```rust,no_run
55//! use rippling_base_api::Client;
56//!
57//! let client = Client::new(String::from("api-key"));
58//! ```
59//!
60//! Alternatively, the library can search for most of the variables required for
61//! the client in the environment:
62//!
63//! - `RIPPLING_BASE_API_TOKEN`
64//!
65//! And then you can create a client from the environment.
66//!
67//! ```rust,no_run
68//! use rippling_base_api::Client;
69//!
70//! let client = Client::new_from_env();
71//! ```
72#![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/// Entrypoint for interacting with the API client.
107#[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/// A request builder.
129#[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    /// Create a new Client struct. It takes a type that can convert into
139    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
140    /// given a valid API key your requests will work.
141    /// Also takes reqwest client builders, for customizing the client's behaviour.
142    #[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            // Retry up to 3 times with increasing intervals between attempts.
155            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                        // Trace HTTP requests. See the tracing crate to make use of these traces.
161                        .with(reqwest_tracing::TracingMiddleware::default())
162                        // Retry failed requests.
163                        .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    /// Create a new Client struct. It takes a type that can convert into
202    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
203    /// given a valid API key your requests will work.
204    /// Also takes reqwest client builders, for customizing the client's behaviour.
205    #[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            // Retry up to 3 times with increasing intervals between attempts.
214            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                        // Trace HTTP requests. See the tracing crate to make use of these traces.
220                        .with(reqwest_tracing::TracingMiddleware::default())
221                        // Retry failed requests.
222                        .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    /// Create a new Client struct. It takes a type that can convert into
252    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
253    /// given a valid API key your requests will work.
254    #[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            // For file conversions we need this to be long.
263            .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            // For file conversions we need this to be long.
270            .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    /// Set the base URL for the client to something other than the default: <https://api.rippling.com>.
281    #[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    /// Create a new Client struct from the environment variable: `RIPPLING_BASE_API_TOKEN`.
290    #[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    /// Create a raw request to our API.
298    #[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        // Add in our authentication.
314        req = req.bearer_auth(&self.token);
315
316        // Set the default headers.
317        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    /// Return a reference to an interface that provides access to Companies operations.
334    pub fn companies(&self) -> companies::Companies {
335        companies::Companies::new(self.clone())
336    }
337
338    /// Return a reference to an interface that provides access to Employees operations.
339    pub fn employees(&self) -> employees::Employees {
340        employees::Employees::new(self.clone())
341    }
342
343    /// Return a reference to an interface that provides access to Groups operations.
344    pub fn groups(&self) -> groups::Groups {
345        groups::Groups::new(self.clone())
346    }
347
348    /// Return a reference to an interface that provides access to SAML operations.
349    pub fn saml(&self) -> saml::Saml {
350        saml::Saml::new(self.clone())
351    }
352
353    /// Return a reference to an interface that provides access to Current User operations.
354    pub fn current_user(&self) -> current_user::CurrentUser {
355        current_user::CurrentUser::new(self.clone())
356    }
357
358    /// Return a reference to an interface that provides access to ATS operations.
359    pub fn ats(&self) -> ats::Ats {
360        ats::Ats::new(self.clone())
361    }
362
363    /// Return a reference to an interface that provides access to Application Management operations.
364    pub fn application_management(&self) -> application_management::ApplicationManagement {
365        application_management::ApplicationManagement::new(self.clone())
366    }
367
368    /// Return a reference to an interface that provides access to Leaves operations.
369    pub fn leaves(&self) -> leaves::Leaves {
370        leaves::Leaves::new(self.clone())
371    }
372}