gusto_api/
lib.rs

1//! A fully generated, opinionated API client library for Gusto.
2//!
3//! [![docs.rs](https://docs.rs/gusto-api/badge.svg)](https://docs.rs/gusto-api)
4//!
5//! ## API Details
6//!
7//! Welcome to Gusto's API documentation.
8//!
9//! [API Terms of Service](https://gusto.com/about/terms/developer-terms-of-service)
10//!
11//! ### Contact
12//!
13//!
14//! | name | email |
15//! |----|----|
16//! | Developer Relations | developer@gusto.com |
17//!
18//!
19//!
20//! ## Client Details
21//!
22//! This client is generated from the [Gusto OpenAPI
23//! specs](https://github.com/Gusto-API/api.gusto.dev) based on API spec version `1.0`. This way it will remain
24//! up to date as features are added. The documentation for the crate is generated
25//! along with the code to make this library easy to use.
26//!
27//!
28//! To install the library, add the following to your `Cargo.toml` file.
29//!
30//! ```toml
31//! [dependencies]
32//! gusto-api = "0.7.0"
33//! ```
34//!
35//! ## Basic example
36//!
37//! Typical use will require intializing a `Client`. This requires
38//! a user agent string and set of credentials.
39//!
40//! ```rust
41//! use gusto_api::Client;
42//!
43//! let gusto = Client::new(
44//!     String::from("client-id"),
45//!     String::from("client-secret"),
46//!     String::from("redirect-uri"),
47//!     String::from("token"),
48//!     String::from("refresh-token"),
49//!     gusto_api::RootProductionServer
50//! );
51//! ```
52//!
53//! Alternatively, the library can search for most of the variables required for
54//! the client in the environment:
55//!
56//! - `GUSTO_CLIENT_ID`
57//! - `GUSTO_CLIENT_SECRET`
58//! - `GUSTO_REDIRECT_URI`
59//!
60//! And then you can create a client from the environment.
61//!
62//! ```rust
63//! use gusto_api::Client;
64//!
65//! let gusto = Client::new_from_env(
66//!     String::from("token"),
67//!     String::from("refresh-token"),
68//!     gusto_api::RootProductionServer
69//! );
70//! ```
71//!
72//! It is okay to pass empty values for `token` and `refresh_token`. In
73//! the initial state of the client, you will not know these values.
74//!
75//! To start off a fresh client and get a `token` and `refresh_token`, use the following.
76//!
77//! ```rust
78//! use gusto_api::Client;
79//!
80//! async fn do_call() {
81//!     let mut gusto = Client::new_from_env("", "", gusto_api::RootProductionServer);
82//!
83//!     // Get the URL to request consent from the user.
84//!     // You can optionally pass in scopes. If none are provided, then the
85//!     // resulting URL will not have any scopes.
86//!     let user_consent_url = gusto.user_consent_url(&["some-scope".to_string()]);
87//!
88//!     // In your redirect URL capture the code sent and our state.
89//!     // Send it along to the request for the token.
90//!     let code = "thing-from-redirect-url";
91//!     let state = "state-from-redirect-url";
92//!     let mut access_token = gusto.get_access_token(code, state).await.unwrap();
93//!
94//!     // You can additionally refresh the access token with the following.
95//!     // You must have a refresh token to be able to call this function.
96//!     access_token = gusto.refresh_access_token().await.unwrap();
97//! }
98//! ```
99//!
100#![allow(clippy::derive_partial_eq_without_eq)]
101#![allow(clippy::too_many_arguments)]
102#![allow(clippy::nonstandard_macro_braces)]
103#![allow(clippy::large_enum_variant)]
104#![allow(clippy::tabs_in_doc_comments)]
105#![allow(missing_docs)]
106#![cfg_attr(docsrs, feature(doc_cfg))]
107
108pub mod admins_beta;
109pub mod benefits;
110pub mod companies;
111pub mod company_bank_accounts_beta;
112pub mod compensations;
113pub mod contractor_payments;
114pub mod contractors;
115pub mod current_user;
116pub mod custom_fields;
117pub mod earning_type;
118pub mod employees;
119pub mod garnishments;
120pub mod job_applicants_beta;
121pub mod jobs;
122pub mod locations;
123pub mod pay_schedules;
124pub mod payroll;
125pub mod terminations;
126pub mod time_off_requests;
127pub mod types;
128#[doc(hidden)]
129pub mod utils;
130
131pub use reqwest::{header::HeaderMap, StatusCode};
132
133#[derive(Debug)]
134pub struct Response<T> {
135    pub status: reqwest::StatusCode,
136    pub headers: reqwest::header::HeaderMap,
137    pub body: T,
138}
139
140impl<T> Response<T> {
141    pub fn new(status: reqwest::StatusCode, headers: reqwest::header::HeaderMap, body: T) -> Self {
142        Self {
143            status,
144            headers,
145            body,
146        }
147    }
148}
149
150type ClientResult<T> = Result<T, ClientError>;
151
152use thiserror::Error;
153
154/// Errors returned by the client
155#[derive(Debug, Error)]
156pub enum ClientError {
157    // Generic Token Client
158    /// Empty refresh auth token
159    #[error("Refresh AuthToken is empty")]
160    EmptyRefreshToken,
161    /// utf8 convertion error
162    #[error(transparent)]
163    FromUtf8Error(#[from] std::string::FromUtf8Error),
164    /// URL Parsing Error
165    #[error(transparent)]
166    UrlParserError(#[from] url::ParseError),
167    /// Serde JSON parsing error
168    #[error(transparent)]
169    SerdeJsonError(#[from] serde_json::Error),
170    /// Errors returned by reqwest
171    #[error(transparent)]
172    ReqwestError(#[from] reqwest::Error),
173    /// Errors returned by reqwest::header
174    #[error(transparent)]
175    InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
176    /// Errors returned by reqwest middleware
177    #[error(transparent)]
178    ReqwestMiddleWareError(#[from] reqwest_middleware::Error),
179    /// Generic HTTP Error
180    #[error("HTTP Error. Code: {status}, message: {error}")]
181    HttpError {
182        status: http::StatusCode,
183        headers: reqwest::header::HeaderMap,
184        error: String,
185    },
186}
187
188pub const FALLBACK_HOST: &str = "https://api.gusto.com";
189
190mod progenitor_support {
191    use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
192
193    const PATH_SET: &AsciiSet = &CONTROLS
194        .add(b' ')
195        .add(b'"')
196        .add(b'#')
197        .add(b'<')
198        .add(b'>')
199        .add(b'?')
200        .add(b'`')
201        .add(b'{')
202        .add(b'}');
203
204    #[allow(dead_code)]
205    pub(crate) fn encode_path(pc: &str) -> String {
206        utf8_percent_encode(pc, PATH_SET).to_string()
207    }
208}
209
210#[derive(Debug, Default)]
211pub(crate) struct Message {
212    pub body: Option<reqwest::Body>,
213    pub content_type: Option<String>,
214}
215
216use std::convert::TryInto;
217use std::env;
218use std::ops::Add;
219use std::sync::Arc;
220use std::time::{Duration, Instant};
221use tokio::sync::RwLock;
222
223const TOKEN_ENDPOINT: &str = "https://api.gusto.com/oauth/token";
224const USER_CONSENT_ENDPOINT: &str = "https://api.gusto.com/oauth/authorize";
225
226pub enum RootDefaultServers {
227    RootDemoServer(RootDemoServer),
228    RootProductionServer(RootProductionServer),
229}
230
231impl RootDefaultServers {
232    pub fn default_url(&self) -> &str {
233        match self {
234            Self::RootDemoServer(inner) => inner.default_url(),
235            Self::RootProductionServer(inner) => inner.default_url(),
236        }
237    }
238}
239
240#[derive(Debug, Default, Clone)]
241pub struct RootDemoServer {}
242
243impl RootDemoServer {
244    pub fn default_url(&self) -> &str {
245        "https://api.gusto-demo.com"
246    }
247}
248
249#[derive(Debug, Default, Clone)]
250pub struct RootProductionServer {}
251
252impl RootProductionServer {
253    pub fn default_url(&self) -> &str {
254        "https://api.gusto.com"
255    }
256}
257
258impl From<RootDemoServer> for RootDefaultServers {
259    fn from(server: RootDemoServer) -> Self {
260        Self::RootDemoServer(server)
261    }
262}
263impl From<RootProductionServer> for RootDefaultServers {
264    fn from(server: RootProductionServer) -> Self {
265        Self::RootProductionServer(server)
266    }
267}
268
269/// Entrypoint for interacting with the API client.
270#[derive(Clone)]
271pub struct Client {
272    host: String,
273    host_override: Option<String>,
274    token: Arc<RwLock<InnerToken>>,
275    client_id: String,
276    client_secret: String,
277    redirect_uri: String,
278
279    auto_refresh: bool,
280    client: reqwest_middleware::ClientWithMiddleware,
281}
282
283use schemars::JsonSchema;
284use serde::{Deserialize, Serialize};
285
286#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
287pub struct AccessToken {
288    #[serde(
289        default,
290        skip_serializing_if = "String::is_empty",
291        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
292    )]
293    pub token_type: String,
294
295    #[serde(
296        default,
297        skip_serializing_if = "String::is_empty",
298        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
299    )]
300    pub access_token: String,
301    #[serde(default)]
302    pub expires_in: i64,
303
304    #[serde(
305        default,
306        skip_serializing_if = "String::is_empty",
307        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
308    )]
309    pub refresh_token: String,
310    #[serde(default, alias = "x_refresh_token_expires_in")]
311    pub refresh_token_expires_in: i64,
312
313    #[serde(
314        default,
315        skip_serializing_if = "String::is_empty",
316        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
317    )]
318    pub scope: String,
319}
320
321/// Time in seconds before the access token expiration point that a refresh should
322/// be performed. This value is subtracted from the `expires_in` value returned by
323/// the provider prior to storing
324const REFRESH_THRESHOLD: Duration = Duration::from_secs(60);
325
326#[derive(Debug, Clone)]
327struct InnerToken {
328    access_token: String,
329    refresh_token: String,
330    expires_at: Option<Instant>,
331}
332
333impl Client {
334    /// Create a new Client struct. Requires OAuth2 configuration values as well as an access and refresh token.
335    ///
336    /// # Panics
337    ///
338    /// This function will panic if the internal http client fails to create
339    pub fn new<I, K, R, T, Q>(
340        client_id: I,
341        client_secret: K,
342        redirect_uri: R,
343        token: T,
344        refresh_token: Q,
345
346        server: impl Into<RootDefaultServers>,
347    ) -> Self
348    where
349        I: ToString,
350        K: ToString,
351        R: ToString,
352        T: ToString,
353        Q: ToString,
354    {
355        // Retry up to 3 times with increasing intervals between attempts.
356        let retry_policy =
357            reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
358        let client = reqwest::Client::builder()
359            .redirect(reqwest::redirect::Policy::none())
360            .build();
361        match client {
362            Ok(c) => {
363                let client = reqwest_middleware::ClientBuilder::new(c)
364                    // Trace HTTP requests. See the tracing crate to make use of these traces.
365                    .with(reqwest_tracing::TracingMiddleware::default())
366                    // Retry failed requests.
367                    .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
368                        reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
369                        |req: &reqwest::Request| req.try_clone().is_some(),
370                    ))
371                    .build();
372
373                let host = server.into().default_url().to_string();
374
375                Client {
376                    host,
377                    host_override: None,
378                    client_id: client_id.to_string(),
379                    client_secret: client_secret.to_string(),
380                    redirect_uri: redirect_uri.to_string(),
381                    token: Arc::new(RwLock::new(InnerToken {
382                        access_token: token.to_string(),
383                        refresh_token: refresh_token.to_string(),
384                        expires_at: None,
385                    })),
386
387                    auto_refresh: false,
388                    client,
389                }
390            }
391            Err(e) => panic!("creating reqwest client failed: {:?}", e),
392        }
393    }
394
395    /// Enables or disables the automatic refreshing of access tokens upon expiration
396    pub fn set_auto_access_token_refresh(&mut self, enabled: bool) -> &mut Self {
397        self.auto_refresh = enabled;
398        self
399    }
400
401    /// Sets a specific `Instant` at which the access token should be considered expired.
402    /// The expiration value will only be used when automatic access token refreshing is
403    /// also enabled. `None` may be passed in if the expiration is unknown. In this case
404    /// automatic refreshes will be attempted when encountering an UNAUTHENTICATED status
405    /// code on a response.
406    pub async fn set_expires_at(&self, expires_at: Option<Instant>) -> &Self {
407        self.token.write().await.expires_at = expires_at;
408        self
409    }
410
411    /// Gets the `Instant` at which the access token used by this client is set to expire
412    /// if one is known
413    pub async fn expires_at(&self) -> Option<Instant> {
414        self.token.read().await.expires_at
415    }
416
417    /// Sets the number of seconds in which the current access token should be considered
418    /// expired
419    pub async fn set_expires_in(&self, expires_in: i64) -> &Self {
420        self.token.write().await.expires_at = Self::compute_expires_at(expires_in);
421        self
422    }
423
424    /// Gets the number of seconds from now in which the current access token will be
425    /// considered expired if one is known
426    pub async fn expires_in(&self) -> Option<Duration> {
427        self.token
428            .read()
429            .await
430            .expires_at
431            .map(|i| i.duration_since(Instant::now()))
432    }
433
434    /// Determines if the access token currently stored in the client is expired. If the
435    /// expiration can not be determined, None is returned
436    pub async fn is_expired(&self) -> Option<bool> {
437        self.token
438            .read()
439            .await
440            .expires_at
441            .map(|expiration| expiration <= Instant::now())
442    }
443
444    fn compute_expires_at(expires_in: i64) -> Option<Instant> {
445        let seconds_valid = expires_in
446            .try_into()
447            .ok()
448            .map(Duration::from_secs)
449            .and_then(|dur| dur.checked_sub(REFRESH_THRESHOLD))
450            .or_else(|| Some(Duration::from_secs(0)));
451
452        seconds_valid.map(|seconds_valid| Instant::now().add(seconds_valid))
453    }
454
455    /// Override the host for all endpoins in the client.
456    pub fn with_host_override<H>(&mut self, host: H) -> &mut Self
457    where
458        H: ToString,
459    {
460        self.host_override = Some(host.to_string());
461        self
462    }
463
464    /// Disables the global host override for the client.
465    pub fn remove_host_override(&mut self) -> &mut Self {
466        self.host_override = None;
467        self
468    }
469
470    pub fn get_host_override(&self) -> Option<&str> {
471        self.host_override.as_deref()
472    }
473
474    pub(crate) fn url(&self, path: &str, host: Option<&str>) -> String {
475        format!(
476            "{}{}",
477            self.get_host_override()
478                .or(host)
479                .unwrap_or(self.host.as_str()),
480            path
481        )
482    }
483
484    /// Create a new Client struct from environment variables. Requires an existing access and refresh token.
485    ///
486    /// The following environment variables are expected to be set:
487    ///   * `GUSTO_CLIENT_ID`
488    ///   * `GUSTO_CLIENT_SECRET`
489    ///   * `GUSTO_REDIRECT_URI`
490    ///
491    /// # Panics
492    ///
493    /// This function will panic if the expected environment variables can not be found
494    pub fn new_from_env<T, R>(
495        token: T,
496        refresh_token: R,
497        server: impl Into<RootDefaultServers>,
498    ) -> Self
499    where
500        T: ToString,
501        R: ToString,
502    {
503        let client_id = env::var("GUSTO_CLIENT_ID").expect("must set GUSTO_CLIENT_ID");
504        let client_secret = env::var("GUSTO_CLIENT_SECRET").expect("must set GUSTO_CLIENT_SECRET");
505        let redirect_uri = env::var("GUSTO_REDIRECT_URI").expect("must set GUSTO_REDIRECT_URI");
506
507        Client::new(
508            client_id,
509            client_secret,
510            redirect_uri,
511            token,
512            refresh_token,
513            server,
514        )
515    }
516
517    /// Return a user consent url with an optional set of scopes.
518    /// If no scopes are provided, they will not be passed in the url.
519    pub fn user_consent_url(&self, scopes: &[String]) -> String {
520        let state = uuid::Uuid::new_v4();
521
522        let url = format!(
523            "{}?client_id={}&response_type=code&redirect_uri={}&state={}",
524            USER_CONSENT_ENDPOINT, self.client_id, self.redirect_uri, state
525        );
526
527        if scopes.is_empty() {
528            return url;
529        }
530
531        // Add the scopes.
532        format!("{}&scope={}", url, scopes.join(" "))
533    }
534
535    /// Refresh an access token from a refresh token. Client must have a refresh token
536    /// for this to work.
537    pub async fn refresh_access_token(&self) -> ClientResult<AccessToken> {
538        let response = {
539            let refresh_token = &self.token.read().await.refresh_token;
540
541            if refresh_token.is_empty() {
542                return Err(ClientError::EmptyRefreshToken);
543            }
544
545            let mut headers = reqwest::header::HeaderMap::new();
546            headers.append(
547                reqwest::header::ACCEPT,
548                reqwest::header::HeaderValue::from_static("application/json"),
549            );
550
551            let params = [
552                ("grant_type", "refresh_token"),
553                ("refresh_token", refresh_token),
554                ("client_id", &self.client_id),
555                ("client_secret", &self.client_secret),
556                ("redirect_uri", &self.redirect_uri),
557            ];
558            let client = reqwest::Client::new();
559            client
560                .post(TOKEN_ENDPOINT)
561                .headers(headers)
562                .form(&params)
563                .basic_auth(&self.client_id, Some(&self.client_secret))
564                .send()
565                .await?
566        };
567
568        // Unwrap the response.
569        let t: AccessToken = response.json().await?;
570
571        let refresh_token = self.token.read().await.refresh_token.clone();
572
573        *self.token.write().await = InnerToken {
574            access_token: t.access_token.clone(),
575            refresh_token,
576            expires_at: Self::compute_expires_at(t.expires_in),
577        };
578
579        Ok(t)
580    }
581
582    /// Get an access token from the code returned by the URL paramter sent to the
583    /// redirect URL.
584    pub async fn get_access_token(&mut self, code: &str, state: &str) -> ClientResult<AccessToken> {
585        let mut headers = reqwest::header::HeaderMap::new();
586        headers.append(
587            reqwest::header::ACCEPT,
588            reqwest::header::HeaderValue::from_static("application/json"),
589        );
590
591        let params = [
592            ("grant_type", "authorization_code"),
593            ("code", code),
594            ("client_id", &self.client_id),
595            ("client_secret", &self.client_secret),
596            ("redirect_uri", &self.redirect_uri),
597            ("state", state),
598        ];
599        let client = reqwest::Client::new();
600        let resp = client
601            .post(TOKEN_ENDPOINT)
602            .headers(headers)
603            .form(&params)
604            .basic_auth(&self.client_id, Some(&self.client_secret))
605            .send()
606            .await?;
607
608        // Unwrap the response.
609        let t: AccessToken = resp.json().await?;
610
611        *self.token.write().await = InnerToken {
612            access_token: t.access_token.clone(),
613            refresh_token: t.refresh_token.clone(),
614            expires_at: Self::compute_expires_at(t.expires_in),
615        };
616
617        Ok(t)
618    }
619
620    async fn url_and_auth(&self, uri: &str) -> ClientResult<(reqwest::Url, Option<String>)> {
621        let parsed_url = uri.parse::<reqwest::Url>()?;
622
623        let auth = format!("Bearer {}", self.token.read().await.access_token);
624        Ok((parsed_url, Some(auth)))
625    }
626
627    async fn make_request(
628        &self,
629        method: &reqwest::Method,
630        uri: &str,
631        message: Message,
632    ) -> ClientResult<reqwest::Request> {
633        let (url, auth) = self.url_and_auth(uri).await?;
634
635        let instance = <&Client>::clone(&self);
636
637        let mut req = instance.client.request(method.clone(), url);
638
639        // Set the default headers.
640        req = req.header(
641            reqwest::header::ACCEPT,
642            reqwest::header::HeaderValue::from_static("application/json"),
643        );
644
645        if let Some(content_type) = &message.content_type {
646            req = req.header(
647                reqwest::header::CONTENT_TYPE,
648                reqwest::header::HeaderValue::from_str(content_type).unwrap(),
649            );
650        } else {
651            req = req.header(
652                reqwest::header::CONTENT_TYPE,
653                reqwest::header::HeaderValue::from_static("application/json"),
654            );
655        }
656
657        if let Some(auth_str) = auth {
658            req = req.header(http::header::AUTHORIZATION, &*auth_str);
659        }
660
661        if let Some(body) = message.body {
662            req = req.body(body);
663        }
664
665        Ok(req.build()?)
666    }
667
668    async fn request_raw(
669        &self,
670        method: reqwest::Method,
671        uri: &str,
672        message: Message,
673    ) -> ClientResult<reqwest::Response> {
674        if self.auto_refresh {
675            let expired = self.is_expired().await;
676
677            match expired {
678                // We have a known expired token, we know we need to perform a refresh prior to
679                // attempting to make a request
680                Some(true) => {
681                    self.refresh_access_token().await?;
682                }
683
684                // We have a (theoretically) known good token available. We make an optimistic
685                // attempting at the request. If the token is no longer good, then something other
686                // than the expiration is triggering the failure. We defer handling of these errors
687                // to the caller
688                Some(false) => (),
689
690                // We do not know what state we are in. We could have a valid or expired token.
691                // Generally this means we are in one of two cases:
692                //   1. We have not yet performed a token refresh, nor has the user provided
693                //      expiration data, and therefore do not know the expiration of the user
694                //      provided token
695                //   2. The provider is returning unusable expiration times, at which point we
696                //      choose to ignore them
697                None => (),
698            }
699        }
700
701        let req = self.make_request(&method, uri, message).await?;
702        let resp = self.client.execute(req).await?;
703
704        Ok(resp)
705    }
706
707    async fn request<Out>(
708        &self,
709        method: reqwest::Method,
710        uri: &str,
711        message: Message,
712    ) -> ClientResult<crate::Response<Out>>
713    where
714        Out: serde::de::DeserializeOwned + 'static + Send,
715    {
716        let response = self.request_raw(method, uri, message).await?;
717
718        let status = response.status();
719        let headers = response.headers().clone();
720
721        let response_body = response.bytes().await?;
722
723        if status.is_success() {
724            log::debug!("Received successful response. Read payload.");
725            let parsed_response = if status == http::StatusCode::NO_CONTENT
726                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
727            {
728                serde_json::from_str("null")?
729            } else {
730                serde_json::from_slice::<Out>(&response_body)?
731            };
732            Ok(crate::Response::new(status, headers, parsed_response))
733        } else {
734            let error = if response_body.is_empty() {
735                ClientError::HttpError {
736                    status,
737                    headers,
738                    error: "empty response".into(),
739                }
740            } else {
741                ClientError::HttpError {
742                    status,
743                    headers,
744                    error: String::from_utf8_lossy(&response_body).into(),
745                }
746            };
747
748            Err(error)
749        }
750    }
751
752    async fn request_with_links<Out>(
753        &self,
754        method: http::Method,
755        uri: &str,
756        message: Message,
757    ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Out>)>
758    where
759        Out: serde::de::DeserializeOwned + 'static + Send,
760    {
761        let response = self.request_raw(method, uri, message).await?;
762
763        let status = response.status();
764        let headers = response.headers().clone();
765        let link = response
766            .headers()
767            .get(http::header::LINK)
768            .and_then(|l| l.to_str().ok())
769            .and_then(|l| parse_link_header::parse(l).ok())
770            .as_ref()
771            .and_then(crate::utils::next_link);
772
773        let response_body = response.bytes().await?;
774
775        if status.is_success() {
776            log::debug!("Received successful response. Read payload.");
777
778            let parsed_response = if status == http::StatusCode::NO_CONTENT
779                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
780            {
781                serde_json::from_str("null")?
782            } else {
783                serde_json::from_slice::<Out>(&response_body)?
784            };
785            Ok((link, crate::Response::new(status, headers, parsed_response)))
786        } else {
787            let error = if response_body.is_empty() {
788                ClientError::HttpError {
789                    status,
790                    headers,
791                    error: "empty response".into(),
792                }
793            } else {
794                ClientError::HttpError {
795                    status,
796                    headers,
797                    error: String::from_utf8_lossy(&response_body).into(),
798                }
799            };
800            Err(error)
801        }
802    }
803
804    /* TODO: make this more DRY */
805    #[allow(dead_code)]
806    async fn post_form<Out>(
807        &self,
808        uri: &str,
809        form: reqwest::multipart::Form,
810    ) -> ClientResult<crate::Response<Out>>
811    where
812        Out: serde::de::DeserializeOwned + 'static + Send,
813    {
814        let (url, auth) = self.url_and_auth(uri).await?;
815
816        let instance = <&Client>::clone(&self);
817
818        let mut req = instance.client.request(http::Method::POST, url);
819
820        // Set the default headers.
821        req = req.header(
822            reqwest::header::ACCEPT,
823            reqwest::header::HeaderValue::from_static("application/json"),
824        );
825
826        if let Some(auth_str) = auth {
827            req = req.header(http::header::AUTHORIZATION, &*auth_str);
828        }
829
830        req = req.multipart(form);
831
832        let response = req.send().await?;
833
834        let status = response.status();
835        let headers = response.headers().clone();
836
837        let response_body = response.bytes().await?;
838
839        if status.is_success() {
840            log::debug!("Received successful response. Read payload.");
841            let parsed_response = if status == http::StatusCode::NO_CONTENT
842                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
843            {
844                serde_json::from_str("null")?
845            } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
846                // Parse the output as a string.
847                let s = String::from_utf8(response_body.to_vec())?;
848                serde_json::from_value(serde_json::json!(&s))?
849            } else {
850                serde_json::from_slice::<Out>(&response_body)?
851            };
852            Ok(crate::Response::new(status, headers, parsed_response))
853        } else {
854            let error = if response_body.is_empty() {
855                ClientError::HttpError {
856                    status,
857                    headers,
858                    error: "empty response".into(),
859                }
860            } else {
861                ClientError::HttpError {
862                    status,
863                    headers,
864                    error: String::from_utf8_lossy(&response_body).into(),
865                }
866            };
867
868            Err(error)
869        }
870    }
871
872    /* TODO: make this more DRY */
873    #[allow(dead_code)]
874    async fn request_with_accept_mime<Out>(
875        &self,
876        method: reqwest::Method,
877        uri: &str,
878        accept_mime_type: &str,
879    ) -> ClientResult<crate::Response<Out>>
880    where
881        Out: serde::de::DeserializeOwned + 'static + Send,
882    {
883        let (url, auth) = self.url_and_auth(uri).await?;
884
885        let instance = <&Client>::clone(&self);
886
887        let mut req = instance.client.request(method, url);
888
889        // Set the default headers.
890        req = req.header(
891            reqwest::header::ACCEPT,
892            reqwest::header::HeaderValue::from_str(accept_mime_type)?,
893        );
894
895        if let Some(auth_str) = auth {
896            req = req.header(http::header::AUTHORIZATION, &*auth_str);
897        }
898
899        let response = req.send().await?;
900
901        let status = response.status();
902        let headers = response.headers().clone();
903
904        let response_body = response.bytes().await?;
905
906        if status.is_success() {
907            log::debug!("Received successful response. Read payload.");
908            let parsed_response = if status == http::StatusCode::NO_CONTENT
909                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
910            {
911                serde_json::from_str("null")?
912            } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
913                // Parse the output as a string.
914                let s = String::from_utf8(response_body.to_vec())?;
915                serde_json::from_value(serde_json::json!(&s))?
916            } else {
917                serde_json::from_slice::<Out>(&response_body)?
918            };
919            Ok(crate::Response::new(status, headers, parsed_response))
920        } else {
921            let error = if response_body.is_empty() {
922                ClientError::HttpError {
923                    status,
924                    headers,
925                    error: "empty response".into(),
926                }
927            } else {
928                ClientError::HttpError {
929                    status,
930                    headers,
931                    error: String::from_utf8_lossy(&response_body).into(),
932                }
933            };
934
935            Err(error)
936        }
937    }
938
939    /* TODO: make this more DRY */
940    #[allow(dead_code)]
941    async fn request_with_mime<Out>(
942        &self,
943        method: reqwest::Method,
944        uri: &str,
945        content: &[u8],
946        mime_type: &str,
947    ) -> ClientResult<crate::Response<Out>>
948    where
949        Out: serde::de::DeserializeOwned + 'static + Send,
950    {
951        let (url, auth) = self.url_and_auth(uri).await?;
952
953        let instance = <&Client>::clone(&self);
954
955        let mut req = instance.client.request(method, url);
956
957        // Set the default headers.
958        req = req.header(
959            reqwest::header::ACCEPT,
960            reqwest::header::HeaderValue::from_static("application/json"),
961        );
962        req = req.header(
963            reqwest::header::CONTENT_TYPE,
964            reqwest::header::HeaderValue::from_bytes(mime_type.as_bytes()).unwrap(),
965        );
966        // We are likely uploading a file so add the right headers.
967        req = req.header(
968            reqwest::header::HeaderName::from_static("x-upload-content-type"),
969            reqwest::header::HeaderValue::from_static("application/octet-stream"),
970        );
971        req = req.header(
972            reqwest::header::HeaderName::from_static("x-upload-content-length"),
973            reqwest::header::HeaderValue::from_bytes(format!("{}", content.len()).as_bytes())
974                .unwrap(),
975        );
976
977        if let Some(auth_str) = auth {
978            req = req.header(http::header::AUTHORIZATION, &*auth_str);
979        }
980
981        if content.len() > 1 {
982            let b = bytes::Bytes::copy_from_slice(content);
983            // We are uploading a file so add that as the body.
984            req = req.body(b);
985        }
986
987        let response = req.send().await?;
988
989        let status = response.status();
990        let headers = response.headers().clone();
991
992        let response_body = response.bytes().await?;
993
994        if status.is_success() {
995            log::debug!("Received successful response. Read payload.");
996            let parsed_response = if status == http::StatusCode::NO_CONTENT
997                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
998            {
999                serde_json::from_str("null")?
1000            } else {
1001                serde_json::from_slice::<Out>(&response_body)?
1002            };
1003            Ok(crate::Response::new(status, headers, parsed_response))
1004        } else {
1005            let error = if response_body.is_empty() {
1006                ClientError::HttpError {
1007                    status,
1008                    headers,
1009                    error: "empty response".into(),
1010                }
1011            } else {
1012                ClientError::HttpError {
1013                    status,
1014                    headers,
1015                    error: String::from_utf8_lossy(&response_body).into(),
1016                }
1017            };
1018
1019            Err(error)
1020        }
1021    }
1022
1023    async fn request_entity<D>(
1024        &self,
1025        method: http::Method,
1026        uri: &str,
1027        message: Message,
1028    ) -> ClientResult<crate::Response<D>>
1029    where
1030        D: serde::de::DeserializeOwned + 'static + Send,
1031    {
1032        let r = self.request(method, uri, message).await?;
1033        Ok(r)
1034    }
1035
1036    #[allow(dead_code)]
1037    async fn get<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1038    where
1039        D: serde::de::DeserializeOwned + 'static + Send,
1040    {
1041        self.request_entity(http::Method::GET, uri, message).await
1042    }
1043
1044    #[allow(dead_code)]
1045    async fn get_all_pages<D>(&self, uri: &str, _message: Message) -> ClientResult<Response<Vec<D>>>
1046    where
1047        D: serde::de::DeserializeOwned + 'static + Send,
1048    {
1049        // TODO: implement this.
1050        self.unfold(uri).await
1051    }
1052
1053    /// "unfold" paginated results of a vector of items
1054    #[allow(dead_code)]
1055    async fn unfold<D>(&self, uri: &str) -> ClientResult<crate::Response<Vec<D>>>
1056    where
1057        D: serde::de::DeserializeOwned + 'static + Send,
1058    {
1059        let mut global_items = Vec::new();
1060        let (new_link, mut response) = self.get_pages(uri).await?;
1061        let mut link = new_link;
1062        while !response.body.is_empty() {
1063            global_items.append(&mut response.body);
1064            // We need to get the next link.
1065            if let Some(url) = &link {
1066                let url = reqwest::Url::parse(&url.0)?;
1067                let (new_link, new_response) = self.get_pages_url(&url).await?;
1068                link = new_link;
1069                response = new_response;
1070            }
1071        }
1072
1073        Ok(Response::new(
1074            response.status,
1075            response.headers,
1076            global_items,
1077        ))
1078    }
1079
1080    #[allow(dead_code)]
1081    async fn get_pages<D>(
1082        &self,
1083        uri: &str,
1084    ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
1085    where
1086        D: serde::de::DeserializeOwned + 'static + Send,
1087    {
1088        self.request_with_links(http::Method::GET, uri, Message::default())
1089            .await
1090    }
1091
1092    #[allow(dead_code)]
1093    async fn get_pages_url<D>(
1094        &self,
1095        url: &reqwest::Url,
1096    ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
1097    where
1098        D: serde::de::DeserializeOwned + 'static + Send,
1099    {
1100        self.request_with_links(http::Method::GET, url.as_str(), Message::default())
1101            .await
1102    }
1103
1104    #[allow(dead_code)]
1105    async fn post<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1106    where
1107        D: serde::de::DeserializeOwned + 'static + Send,
1108    {
1109        self.request_entity(http::Method::POST, uri, message).await
1110    }
1111
1112    #[allow(dead_code)]
1113    async fn patch<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1114    where
1115        D: serde::de::DeserializeOwned + 'static + Send,
1116    {
1117        self.request_entity(http::Method::PATCH, uri, message).await
1118    }
1119
1120    #[allow(dead_code)]
1121    async fn put<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1122    where
1123        D: serde::de::DeserializeOwned + 'static + Send,
1124    {
1125        self.request_entity(http::Method::PUT, uri, message).await
1126    }
1127
1128    #[allow(dead_code)]
1129    async fn delete<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1130    where
1131        D: serde::de::DeserializeOwned + 'static + Send,
1132    {
1133        self.request_entity(http::Method::DELETE, uri, message)
1134            .await
1135    }
1136
1137    /// Return a reference to an interface that provides access to Current User operations.
1138    pub fn current_user(&self) -> current_user::CurrentUser {
1139        current_user::CurrentUser::new(self.clone())
1140    }
1141
1142    /// Return a reference to an interface that provides access to Companies operations.
1143    pub fn companies(&self) -> companies::Companies {
1144        companies::Companies::new(self.clone())
1145    }
1146
1147    /// Return a reference to an interface that provides access to Employees operations.
1148    pub fn employees(&self) -> employees::Employees {
1149        employees::Employees::new(self.clone())
1150    }
1151
1152    /// Return a reference to an interface that provides access to Contractors operations.
1153    pub fn contractors(&self) -> contractors::Contractors {
1154        contractors::Contractors::new(self.clone())
1155    }
1156
1157    /// Return a reference to an interface that provides access to Payroll operations.
1158    pub fn payroll(&self) -> payroll::Payroll {
1159        payroll::Payroll::new(self.clone())
1160    }
1161
1162    /// Return a reference to an interface that provides access to Contractor Payments operations.
1163    pub fn contractor_payments(&self) -> contractor_payments::ContractorPayments {
1164        contractor_payments::ContractorPayments::new(self.clone())
1165    }
1166
1167    /// Return a reference to an interface that provides access to Company Bank Accounts (Beta) operations.
1168    pub fn company_bank_accounts_beta(
1169        &self,
1170    ) -> company_bank_accounts_beta::CompanyBankAccountsBeta {
1171        company_bank_accounts_beta::CompanyBankAccountsBeta::new(self.clone())
1172    }
1173
1174    /// Return a reference to an interface that provides access to Benefits operations.
1175    pub fn benefits(&self) -> benefits::Benefits {
1176        benefits::Benefits::new(self.clone())
1177    }
1178
1179    /// Return a reference to an interface that provides access to Locations operations.
1180    pub fn locations(&self) -> locations::Locations {
1181        locations::Locations::new(self.clone())
1182    }
1183
1184    /// Return a reference to an interface that provides access to Jobs operations.
1185    pub fn jobs(&self) -> jobs::Jobs {
1186        jobs::Jobs::new(self.clone())
1187    }
1188
1189    /// Return a reference to an interface that provides access to Job Applicants (Beta) operations.
1190    pub fn job_applicants_beta(&self) -> job_applicants_beta::JobApplicantsBeta {
1191        job_applicants_beta::JobApplicantsBeta::new(self.clone())
1192    }
1193
1194    /// Return a reference to an interface that provides access to Compensations operations.
1195    pub fn compensations(&self) -> compensations::Compensations {
1196        compensations::Compensations::new(self.clone())
1197    }
1198
1199    /// Return a reference to an interface that provides access to Pay Schedules operations.
1200    pub fn pay_schedules(&self) -> pay_schedules::PaySchedules {
1201        pay_schedules::PaySchedules::new(self.clone())
1202    }
1203
1204    /// Return a reference to an interface that provides access to Garnishments operations.
1205    pub fn garnishments(&self) -> garnishments::Garnishments {
1206        garnishments::Garnishments::new(self.clone())
1207    }
1208
1209    /// Return a reference to an interface that provides access to Time Off Requests operations.
1210    pub fn time_off_requests(&self) -> time_off_requests::TimeOffRequests {
1211        time_off_requests::TimeOffRequests::new(self.clone())
1212    }
1213
1214    /// Return a reference to an interface that provides access to Earning Type operations.
1215    pub fn earning_type(&self) -> earning_type::EarningType {
1216        earning_type::EarningType::new(self.clone())
1217    }
1218
1219    /// Return a reference to an interface that provides access to Terminations operations.
1220    pub fn terminations(&self) -> terminations::Terminations {
1221        terminations::Terminations::new(self.clone())
1222    }
1223
1224    /// Return a reference to an interface that provides access to Custom Fields operations.
1225    pub fn custom_fields(&self) -> custom_fields::CustomFields {
1226        custom_fields::CustomFields::new(self.clone())
1227    }
1228
1229    /// Return a reference to an interface that provides access to Admins (Beta) operations.
1230    pub fn admins_beta(&self) -> admins_beta::AdminsBeta {
1231        admins_beta::AdminsBeta::new(self.clone())
1232    }
1233}