rippling_api/
lib.rs

1//! A fully generated & opinionated API client for the Rippling API.
2//!
3//! [![docs.rs](https://docs.rs/rippling-api/badge.svg)](https://docs.rs/rippling-api)
4//!
5//! ## API Details
6//!
7//! Documentation for the Rippling Platform API.
8//!
9//!
10//!
11//!
12//!
13//!
14//! ## Client Details
15//!
16//!
17//!
18//! The documentation for the crate is generated
19//! along with the code to make this library easy to use.
20//!
21//!
22//! To install the library, add the following to your `Cargo.toml` file.
23//!
24//! ```toml
25//! [dependencies]
26//! rippling-api = "0.1.10"
27//! ```
28//!
29//! ## Basic example
30//!
31//! Typical use will require intializing a `Client`. This requires
32//! a user agent string and set of credentials.
33//!
34//! ```rust,no_run
35//! use rippling_api::Client;
36//!
37//! let client = Client::new(String::from("api-key"));
38//! ```
39//!
40//! Alternatively, the library can search for most of the variables required for
41//! the client in the environment:
42//!
43//! - `RIPPLING_API_TOKEN`
44//!
45//!
46//! And then you can create a client from the environment.
47//!
48//! ```rust,no_run
49//! use rippling_api::Client;
50//!
51//! let client = Client::new_from_env();
52//! ```
53#![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/// An application by a candidate to a specific job requisition.
61#[cfg(feature = "requests")]
62pub mod candidate_applications;
63/// Someone who applies to a job requisition opened by the company.
64#[cfg(feature = "requests")]
65pub mod candidates;
66/// Companies on Rippling.
67#[cfg(feature = "requests")]
68pub mod companies;
69/// Compensation associated with workers.
70#[cfg(feature = "requests")]
71pub mod compensations;
72/// Custom fields defined by the company.
73#[cfg(feature = "requests")]
74pub mod custom_fields;
75/// Custom object fields defined by the company.
76#[cfg(feature = "requests")]
77pub mod custom_object_fields;
78/// Custom object datarows defined by the company.
79#[cfg(feature = "requests")]
80pub mod custom_object_records;
81/// Custom objects defined by the company.
82#[cfg(feature = "requests")]
83pub mod custom_objects;
84/// Departments used by the company.
85#[cfg(feature = "requests")]
86pub mod departments;
87/// Employment types used by the company.
88#[cfg(feature = "requests")]
89pub mod employment_types;
90/// Availability of API features to the company or Partners.
91#[cfg(feature = "requests")]
92pub mod entitlements;
93/// Job related information for the company.
94#[cfg(feature = "requests")]
95pub mod job;
96/// A request for a job to be filled by a candidate.
97#[cfg(feature = "requests")]
98pub mod job_requisitions;
99/// Leave balances for workers.
100#[cfg(feature = "requests")]
101pub mod leave_balances;
102/// Leave requests submitted by workers.
103#[cfg(feature = "requests")]
104pub mod leave_requests;
105/// Leave types used by the company.
106#[cfg(feature = "requests")]
107pub mod leave_types;
108/// Legal entities registered by the company.
109#[cfg(feature = "requests")]
110pub mod legal_entities;
111/// Provides the user's SSO information.
112#[cfg(feature = "requests")]
113pub mod me;
114mod methods;
115/// Object Categories defined by the company.
116#[cfg(feature = "requests")]
117pub mod object_categories;
118/// Shift inputs used by the company.
119#[cfg(feature = "requests")]
120pub mod shift_inputs;
121/// Teams at the company.
122#[cfg(feature = "requests")]
123pub mod teams;
124#[cfg(test)]
125mod tests;
126/// Time entries submitted by workers.
127#[cfg(feature = "requests")]
128pub mod time_entries;
129/// Levels and tracks used by the company for workers.
130#[cfg(feature = "requests")]
131pub mod tracks_and_levels;
132pub mod types;
133/// Users of the company.
134#[cfg(feature = "requests")]
135pub mod users;
136pub mod utils;
137/// Work locations used by the company.
138#[cfg(feature = "requests")]
139pub mod work_locations;
140/// Workers who work or have worked at the company.
141#[cfg(feature = "requests")]
142pub mod workers;
143
144#[cfg(feature = "requests")]
145use std::env;
146
147#[cfg(not(target_arch = "wasm32"))]
148#[cfg(feature = "requests")]
149static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
150
151/// Entrypoint for interacting with the API client.
152#[derive(Clone, Debug)]
153#[cfg(feature = "requests")]
154pub struct Client {
155    token: String,
156    base_url: String,
157
158    #[cfg(feature = "retry")]
159    client: reqwest_middleware::ClientWithMiddleware,
160    #[cfg(feature = "retry")]
161    #[cfg(not(target_arch = "wasm32"))]
162    #[allow(dead_code)]
163    client_http1_only: reqwest_middleware::ClientWithMiddleware,
164
165    #[cfg(not(feature = "retry"))]
166    client: reqwest::Client,
167    #[cfg(not(feature = "retry"))]
168    #[cfg(not(target_arch = "wasm32"))]
169    #[allow(dead_code)]
170    client_http1_only: reqwest::Client,
171}
172
173/// A request builder.
174#[cfg(feature = "retry")]
175#[cfg(feature = "requests")]
176pub struct RequestBuilder(pub reqwest_middleware::RequestBuilder);
177#[cfg(not(feature = "retry"))]
178#[cfg(feature = "requests")]
179pub struct RequestBuilder(pub reqwest::RequestBuilder);
180
181#[cfg(feature = "requests")]
182impl Client {
183    /// Create a new Client struct. It takes a type that can convert into
184    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
185    /// given a valid API key your requests will work.
186    /// Also takes reqwest client builders, for customizing the client's behaviour.
187    #[tracing::instrument(skip(token))]
188    #[cfg(not(target_arch = "wasm32"))]
189    pub fn new_from_reqwest<T>(
190        token: T,
191        builder_http: reqwest::ClientBuilder,
192        builder_websocket: reqwest::ClientBuilder,
193    ) -> Self
194    where
195        T: ToString + std::fmt::Debug,
196    {
197        #[cfg(feature = "retry")]
198        {
199            // Retry up to 3 times with increasing intervals between attempts.
200            let retry_policy =
201                reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
202            match (builder_http.build(), builder_websocket.build()) {
203                (Ok(c), Ok(c1)) => {
204                    let client = reqwest_middleware::ClientBuilder::new(c)
205                        // Trace HTTP requests. See the tracing crate to make use of these traces.
206                        .with(reqwest_tracing::TracingMiddleware::default())
207                        // Retry failed requests.
208                        .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
209                            reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
210                            |req: &reqwest::Request| req.try_clone().is_some(),
211                        ))
212                        .build();
213                    let client_http1_only = reqwest_middleware::ClientBuilder::new(c1)
214                        .with(reqwest_tracing::TracingMiddleware::default())
215                        .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
216                            reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
217                            |req: &reqwest::Request| req.try_clone().is_some(),
218                        ))
219                        .build();
220                    Client {
221                        token: token.to_string(),
222                        base_url: "https://rest.ripplingapis.com".to_string(),
223
224                        client,
225                        client_http1_only,
226                    }
227                }
228                (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
229            }
230        }
231        #[cfg(not(feature = "retry"))]
232        {
233            match (builder_http.build(), builder_websocket.build()) {
234                (Ok(c), Ok(c1)) => Client {
235                    token: token.to_string(),
236                    base_url: "https://rest.ripplingapis.com".to_string(),
237
238                    client: c,
239                    client_http1_only: c1,
240                },
241                (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
242            }
243        }
244    }
245
246    /// Create a new Client struct. It takes a type that can convert into
247    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
248    /// given a valid API key your requests will work.
249    /// Also takes reqwest client builders, for customizing the client's behaviour.
250    #[tracing::instrument(skip(token))]
251    #[cfg(target_arch = "wasm32")]
252    pub fn new_from_reqwest<T>(token: T, builder_http: reqwest::ClientBuilder) -> Self
253    where
254        T: ToString + std::fmt::Debug,
255    {
256        #[cfg(feature = "retry")]
257        {
258            // Retry up to 3 times with increasing intervals between attempts.
259            let retry_policy =
260                reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
261            match builder_http.build() {
262                Ok(c) => {
263                    let client = reqwest_middleware::ClientBuilder::new(c)
264                        // Trace HTTP requests. See the tracing crate to make use of these traces.
265                        .with(reqwest_tracing::TracingMiddleware::default())
266                        // Retry failed requests.
267                        .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
268                            reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
269                            |req: &reqwest::Request| req.try_clone().is_some(),
270                        ))
271                        .build();
272                    Client {
273                        token: token.to_string(),
274                        base_url: "https://rest.ripplingapis.com".to_string(),
275
276                        client,
277                    }
278                }
279                Err(e) => panic!("creating reqwest client failed: {:?}", e),
280            }
281        }
282        #[cfg(not(feature = "retry"))]
283        {
284            match builder_http.build() {
285                Ok(c) => Client {
286                    token: token.to_string(),
287                    base_url: "https://rest.ripplingapis.com".to_string(),
288
289                    client: c,
290                },
291                Err(e) => panic!("creating reqwest client failed: {:?}", e),
292            }
293        }
294    }
295
296    /// Create a new Client struct. It takes a type that can convert into
297    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
298    /// given a valid API key your requests will work.
299    #[tracing::instrument(skip(token))]
300    pub fn new<T>(token: T) -> Self
301    where
302        T: ToString + std::fmt::Debug,
303    {
304        #[cfg(not(target_arch = "wasm32"))]
305        let client = reqwest::Client::builder()
306            .user_agent(APP_USER_AGENT)
307            // For file conversions we need this to be long.
308            .timeout(std::time::Duration::from_secs(600))
309            .connect_timeout(std::time::Duration::from_secs(60));
310        #[cfg(target_arch = "wasm32")]
311        let client = reqwest::Client::builder();
312        #[cfg(not(target_arch = "wasm32"))]
313        let client_http1 = reqwest::Client::builder()
314            // For file conversions we need this to be long.
315            .user_agent(APP_USER_AGENT)
316            .timeout(std::time::Duration::from_secs(600))
317            .connect_timeout(std::time::Duration::from_secs(60))
318            .http1_only();
319        #[cfg(not(target_arch = "wasm32"))]
320        return Self::new_from_reqwest(token, client, client_http1);
321        #[cfg(target_arch = "wasm32")]
322        Self::new_from_reqwest(token, client)
323    }
324
325    /// Set the base URL for the client to something other than the default: <https://rest.ripplingapis.com>.
326    #[tracing::instrument]
327    pub fn set_base_url<H>(&mut self, base_url: H)
328    where
329        H: Into<String> + std::fmt::Display + std::fmt::Debug,
330    {
331        self.base_url = base_url.to_string().trim_end_matches('/').to_string();
332    }
333
334    /// Create a new Client struct from the environment variable: `ENV_VARIABLE_PREFIX_API_TOKEN`.
335    #[tracing::instrument]
336    pub fn new_from_env() -> Self {
337        let token = env::var("RIPPLING_API_TOKEN").expect("must set RIPPLING_API_TOKEN");
338        let base_url =
339            env::var("RIPPLING_HOST").unwrap_or("https://rest.ripplingapis.com".to_string());
340
341        let mut c = Client::new(token);
342        c.set_base_url(base_url);
343        c
344    }
345
346    /// Create a raw request to our API.
347    #[tracing::instrument]
348    pub async fn request_raw(
349        &self,
350        method: reqwest::Method,
351        uri: &str,
352        body: Option<reqwest::Body>,
353    ) -> anyhow::Result<RequestBuilder> {
354        let u = if uri.starts_with("https://") || uri.starts_with("http://") {
355            uri.to_string()
356        } else {
357            format!("{}/{}", self.base_url, uri.trim_start_matches('/'))
358        };
359
360        let mut req = self.client.request(method, &u);
361
362        // Add in our authentication.
363        req = req.bearer_auth(&self.token);
364
365        // Set the default headers.
366        req = req.header(
367            reqwest::header::ACCEPT,
368            reqwest::header::HeaderValue::from_static("application/json"),
369        );
370        req = req.header(
371            reqwest::header::CONTENT_TYPE,
372            reqwest::header::HeaderValue::from_static("application/json"),
373        );
374
375        if let Some(body) = body {
376            req = req.body(body);
377        }
378
379        Ok(RequestBuilder(req))
380    }
381
382    /// An application by a candidate to a specific job requisition.
383    pub fn candidate_applications(&self) -> candidate_applications::CandidateApplications {
384        candidate_applications::CandidateApplications::new(self.clone())
385    }
386
387    /// Someone who applies to a job requisition opened by the company.
388    pub fn candidates(&self) -> candidates::Candidates {
389        candidates::Candidates::new(self.clone())
390    }
391
392    /// Companies on Rippling.
393    pub fn companies(&self) -> companies::Companies {
394        companies::Companies::new(self.clone())
395    }
396
397    /// Compensation associated with workers.
398    pub fn compensations(&self) -> compensations::Compensations {
399        compensations::Compensations::new(self.clone())
400    }
401
402    /// Custom fields defined by the company.
403    pub fn custom_fields(&self) -> custom_fields::CustomFields {
404        custom_fields::CustomFields::new(self.clone())
405    }
406
407    /// Custom object fields defined by the company.
408    pub fn custom_object_fields(&self) -> custom_object_fields::CustomObjectFields {
409        custom_object_fields::CustomObjectFields::new(self.clone())
410    }
411
412    /// Custom object datarows defined by the company.
413    pub fn custom_object_records(&self) -> custom_object_records::CustomObjectRecords {
414        custom_object_records::CustomObjectRecords::new(self.clone())
415    }
416
417    /// Custom objects defined by the company.
418    pub fn custom_objects(&self) -> custom_objects::CustomObjects {
419        custom_objects::CustomObjects::new(self.clone())
420    }
421
422    /// Departments used by the company.
423    pub fn departments(&self) -> departments::Departments {
424        departments::Departments::new(self.clone())
425    }
426
427    /// Employment types used by the company.
428    pub fn employment_types(&self) -> employment_types::EmploymentTypes {
429        employment_types::EmploymentTypes::new(self.clone())
430    }
431
432    /// Availability of API features to the company or Partners.
433    pub fn entitlements(&self) -> entitlements::Entitlements {
434        entitlements::Entitlements::new(self.clone())
435    }
436
437    /// Job related information for the company.
438    pub fn job(&self) -> job::Job {
439        job::Job::new(self.clone())
440    }
441
442    /// A request for a job to be filled by a candidate.
443    pub fn job_requisitions(&self) -> job_requisitions::JobRequisitions {
444        job_requisitions::JobRequisitions::new(self.clone())
445    }
446
447    /// Leave balances for workers.
448    pub fn leave_balances(&self) -> leave_balances::LeaveBalances {
449        leave_balances::LeaveBalances::new(self.clone())
450    }
451
452    /// Leave requests submitted by workers.
453    pub fn leave_requests(&self) -> leave_requests::LeaveRequests {
454        leave_requests::LeaveRequests::new(self.clone())
455    }
456
457    /// Leave types used by the company.
458    pub fn leave_types(&self) -> leave_types::LeaveTypes {
459        leave_types::LeaveTypes::new(self.clone())
460    }
461
462    /// Legal entities registered by the company.
463    pub fn legal_entities(&self) -> legal_entities::LegalEntities {
464        legal_entities::LegalEntities::new(self.clone())
465    }
466
467    /// Provides the user's SSO information.
468    pub fn me(&self) -> me::Me {
469        me::Me::new(self.clone())
470    }
471
472    /// Object Categories defined by the company.
473    pub fn object_categories(&self) -> object_categories::ObjectCategories {
474        object_categories::ObjectCategories::new(self.clone())
475    }
476
477    /// Shift inputs used by the company.
478    pub fn shift_inputs(&self) -> shift_inputs::ShiftInputs {
479        shift_inputs::ShiftInputs::new(self.clone())
480    }
481
482    /// Teams at the company.
483    pub fn teams(&self) -> teams::Teams {
484        teams::Teams::new(self.clone())
485    }
486
487    /// Time entries submitted by workers.
488    pub fn time_entries(&self) -> time_entries::TimeEntries {
489        time_entries::TimeEntries::new(self.clone())
490    }
491
492    /// Levels and tracks used by the company for workers.
493    pub fn tracks_and_levels(&self) -> tracks_and_levels::TracksAndLevels {
494        tracks_and_levels::TracksAndLevels::new(self.clone())
495    }
496
497    /// Users of the company.
498    pub fn users(&self) -> users::Users {
499        users::Users::new(self.clone())
500    }
501
502    /// Work locations used by the company.
503    pub fn work_locations(&self) -> work_locations::WorkLocations {
504        work_locations::WorkLocations::new(self.clone())
505    }
506
507    /// Workers who work or have worked at the company.
508    pub fn workers(&self) -> workers::Workers {
509        workers::Workers::new(self.clone())
510    }
511}