diff --git a/Cargo.lock b/Cargo.lock index c911c79..c36356d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,6 +230,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.12" @@ -1690,6 +1700,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2097,6 +2113,8 @@ dependencies = [ "async-trait", "chrono", "derive_more", + "erased-serde", + "http", "jsonwebtoken", "matches", "mockito", diff --git a/Cargo.toml b/Cargo.toml index b8b7425..32a4d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,16 +9,19 @@ edition = "2024" [features] default = ["rustls-tls"] -native-tls = ["reqwest/native-tls"] -rustls-tls = ["reqwest/rustls-tls"] +reqwest=["dep:reqwest"] +native-tls = ["reqwest/native-tls","reqwest"] +rustls-tls = ["reqwest/rustls-tls","reqwest"] [dependencies] async-trait = "0.1.88" chrono = { version = "0.4.40", features = ["serde"] } derive_more = { version = "2.0.1", features = ["deref", "display", "from"] } +erased-serde = "0.4.6" +http = "1.3.1" jsonwebtoken = "9.3.1" querystring = "1.1.0" -reqwest = { version = "0.12.0", features = ["json"] } +reqwest = { version = "0.12.0", features = ["json"], optional = true } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" thiserror = "2.0.0" diff --git a/src/admin_portal.rs b/src/admin_portal.rs index cd8bab7..febd04a 100644 --- a/src/admin_portal.rs +++ b/src/admin_portal.rs @@ -12,7 +12,7 @@ use crate::WorkOs; /// /// [WorkOS Docs: Admin Portal Guide](https://workos.com/docs/admin-portal/guide) pub struct AdminPortal<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> AdminPortal<'a> { diff --git a/src/admin_portal/operations/generate_portal_link.rs b/src/admin_portal/operations/generate_portal_link.rs index 8532396..80f3267 100644 --- a/src/admin_portal/operations/generate_portal_link.rs +++ b/src/admin_portal/operations/generate_portal_link.rs @@ -109,7 +109,7 @@ impl GeneratePortalLink for AdminPortal<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(generate_link_response) diff --git a/src/core.rs b/src/core.rs index 9acfda6..547acad 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,5 +1,7 @@ mod error; mod response; +///Traits for requests and other core infrastructure +pub mod traits; mod types; pub use error::*; diff --git a/src/core/error.rs b/src/core/error.rs index 6051824..1338911 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -21,7 +21,11 @@ pub enum WorkOsError { /// An unhandled error occurred with the API request. #[error("request error")] - RequestError(#[from] reqwest::Error), + RequestError(#[from] crate::traits::RequestError), + + /// An error occurred when deserializing JSON + #[error("json error")] + Json(#[from] serde_json::Error), } /// A WorkOS SDK result. diff --git a/src/core/response.rs b/src/core/response.rs index 157b139..55c563d 100644 --- a/src/core/response.rs +++ b/src/core/response.rs @@ -1,25 +1,30 @@ -use reqwest::{Response, StatusCode}; +use http::StatusCode; +use serde::de::DeserializeOwned; -use crate::{WorkOsError, WorkOsResult}; +use crate::{WorkOsError, WorkOsResult, traits::ClientResponse}; -pub trait ResponseExt +pub trait ResponseExt<'a> where Self: Sized, { /// Handles an unauthorized error from the WorkOS API by converting it into a /// [`WorkOsError::Unauthorized`] response. - fn handle_unauthorized_error(self) -> WorkOsResult; + fn handle_unauthorized_error(self) -> WorkOsResult, E>; /// Handles a generic error from the WorkOS API by converting it into a /// [`WorkOsError::RequestError`] response. - fn handle_generic_error(self) -> WorkOsResult; + fn handle_generic_error(self) -> WorkOsResult, E>; /// Handles an unauthorized or generic error from the WorkOS API. - fn handle_unauthorized_or_generic_error(self) -> WorkOsResult; + fn handle_unauthorized_or_generic_error( + self, + ) -> WorkOsResult, E>; + + async fn json(self) -> WorkOsResult; } -impl ResponseExt for Response { - fn handle_unauthorized_error(self) -> WorkOsResult { +impl<'a> ResponseExt<'a> for Box { + fn handle_unauthorized_error(self) -> WorkOsResult, E> { if self.status() == StatusCode::UNAUTHORIZED { Err(WorkOsError::Unauthorized) } else { @@ -27,14 +32,22 @@ impl ResponseExt for Response { } } - fn handle_generic_error(self) -> WorkOsResult { + fn handle_generic_error(self) -> WorkOsResult, E> { match self.error_for_status() { Ok(response) => Ok(response), Err(err) => Err(WorkOsError::RequestError(err)), } } - fn handle_unauthorized_or_generic_error(self) -> WorkOsResult { + fn handle_unauthorized_or_generic_error( + self, + ) -> WorkOsResult, E> { self.handle_unauthorized_error()?.handle_generic_error() } + + async fn json(self) -> WorkOsResult { + let t = self.text().await.map_err(WorkOsError::RequestError)?; + + Ok(serde_json::from_str(&t)?) + } } diff --git a/src/core/traits.rs b/src/core/traits.rs new file mode 100644 index 0000000..8d012ea --- /dev/null +++ b/src/core/traits.rs @@ -0,0 +1,205 @@ +use std::fmt::Display; + +use async_trait::async_trait; +use http::StatusCode; +use url::Url; +///An error with a ststus code +pub trait StatusError: std::error::Error { + ///The status code, if available, of this error + fn status(&self) -> Option; +} +///A HTTP error +#[derive(Debug)] +pub struct RequestError { + ///The status code, if available, of this error + pub err: Box, +} + +impl StatusError for RequestError { + fn status(&self) -> Option { + self.err.status() + } +} + +impl RequestError { + ///The status code, if available, of this error + pub fn status(&self) -> Option { + StatusError::status(self) + } +} + +impl Display for RequestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.err) + } +} +impl std::error::Error for RequestError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&*self.err) + } +} + +#[async_trait] +///An HTTP client +pub trait Client { + ///Create a GET request + fn get(&self, url: Url) -> Box; + ///Create a POST request + fn post(&self, url: Url) -> Box; + ///Create a PUT request + fn put(&self, url: Url) -> Box; + ///Create a DELETE request + fn delete(&self, url: Url) -> Box; +} + +#[async_trait] +///A response to a HTTP request +pub trait ClientResponse: Send + Sync { + ///That response's status code + fn status(&self) -> StatusCode; + ///Attept to catch error-signaling status codes and return them as errors + fn error_for_status<'a>(self: Box) -> Result, RequestError> + where + Self: 'a; + ///Attept to catch error-signaling status codes and return them as errors + fn error_for_status_ref(&self) -> Result<&(dyn ClientResponse + '_), RequestError>; + ///The text of the response + async fn text(self: Box) -> Result; +} + +#[async_trait] +///A HTTP request +pub trait ClientRequest { + ///Its authenticaton + fn bearer_auth<'a>(self: Box, x: &(dyn Display + '_)) -> Box + where + Self: 'a; + ///Adds ad JSON body + fn json<'a>( + self: Box, + x: &(dyn erased_serde::Serialize + '_), + ) -> Box + where + Self: 'a; + ///Adds a query string + fn query<'a>( + self: Box, + x: &(dyn erased_serde::Serialize + '_), + ) -> Box + where + Self: 'a; + ///Adds a form body + fn form<'a>( + self: Box, + x: &(dyn erased_serde::Serialize + '_), + ) -> Box + where + Self: 'a; + ///Sends the request + async fn send<'a>(self: Box) -> Result, RequestError> + where + Self: 'a; +} +#[cfg(feature = "reqwest")] +const _: () = { + impl StatusError for reqwest::Error { + fn status(&self) -> Option { + self.status() + } + } + + impl From for RequestError { + fn from(value: reqwest::Error) -> Self { + Self { + err: Box::new(value), + } + } + } + + #[async_trait] + impl Client for reqwest::Client { + fn get(&self, url: Url) -> Box { + Box::new(self.get(url)) + } + fn post(&self, url: Url) -> Box { + Box::new(self.post(url)) + } + fn put(&self, url: Url) -> Box { + Box::new(self.put(url)) + } + fn delete(&self, url: Url) -> Box { + Box::new(self.delete(url)) + } + } + #[async_trait] + impl ClientResponse for reqwest::Response { + fn status(&self) -> StatusCode { + self.status() + } + fn error_for_status<'a>( + self: Box, + ) -> Result, RequestError> + where + Self: 'a, + { + match (*self).error_for_status() { + Err(e) => Err(e.into()), + Ok(a) => Ok(Box::new(a)), + } + } + fn error_for_status_ref(&self) -> Result<&(dyn ClientResponse + '_), RequestError> { + match self.error_for_status_ref() { + Err(e) => Err(e.into()), + Ok(v) => Ok(v), + } + } + async fn text(self: Box) -> Result { + (*self).text().await.map_err(Into::into) + } + } + #[async_trait] + impl ClientRequest for reqwest::RequestBuilder { + async fn send<'a>(self: Box) -> Result, RequestError> + where + Self: 'a, + { + match reqwest::RequestBuilder::send(*self).await { + Err(e) => Err(e.into()), + Ok(a) => Ok(Box::new(a)), + } + } + fn bearer_auth<'a>(self: Box, x: &(dyn Display + '_)) -> Box + where + Self: 'a, + { + Box::new((*self).bearer_auth(x)) + } + fn json<'a>( + self: Box, + x: &(dyn erased_serde::Serialize + '_), + ) -> Box + where + Self: 'a, + { + Box::new((*self).json(x)) + } + fn query<'a>( + self: Box, + x: &(dyn erased_serde::Serialize + '_), + ) -> Box + where + Self: 'a, + { + Box::new((*self).query(x)) + } + fn form<'a>( + self: Box, + x: &(dyn erased_serde::Serialize + '_), + ) -> Box + where + Self: 'a, + { + Box::new((*self).form(x)) + } + } +}; diff --git a/src/directory_sync.rs b/src/directory_sync.rs index 12da4df..5dc3bae 100644 --- a/src/directory_sync.rs +++ b/src/directory_sync.rs @@ -14,7 +14,7 @@ use crate::WorkOs; /// /// [WorkOS Docs: Directory Sync Guide](https://workos.com/docs/directory-sync/guide) pub struct DirectorySync<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> DirectorySync<'a> { diff --git a/src/directory_sync/operations/get_directory.rs b/src/directory_sync/operations/get_directory.rs index e70e1ea..fb2c194 100644 --- a/src/directory_sync/operations/get_directory.rs +++ b/src/directory_sync/operations/get_directory.rs @@ -53,7 +53,7 @@ impl GetDirectory for DirectorySync<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(directory) diff --git a/src/directory_sync/operations/get_directory_group.rs b/src/directory_sync/operations/get_directory_group.rs index d351870..a4f6de9 100644 --- a/src/directory_sync/operations/get_directory_group.rs +++ b/src/directory_sync/operations/get_directory_group.rs @@ -64,7 +64,7 @@ impl GetDirectoryGroup for DirectorySync<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(directory_group) diff --git a/src/directory_sync/operations/get_directory_user.rs b/src/directory_sync/operations/get_directory_user.rs index a456a94..c44d4bc 100644 --- a/src/directory_sync/operations/get_directory_user.rs +++ b/src/directory_sync/operations/get_directory_user.rs @@ -64,7 +64,7 @@ impl GetDirectoryUser for DirectorySync<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(directory_user) diff --git a/src/directory_sync/operations/list_directories.rs b/src/directory_sync/operations/list_directories.rs index eda9bdd..24b8262 100644 --- a/src/directory_sync/operations/list_directories.rs +++ b/src/directory_sync/operations/list_directories.rs @@ -74,7 +74,7 @@ impl ListDirectories for DirectorySync<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(directories) diff --git a/src/directory_sync/operations/list_directory_groups.rs b/src/directory_sync/operations/list_directory_groups.rs index 31ef1e0..5e05dcb 100644 --- a/src/directory_sync/operations/list_directory_groups.rs +++ b/src/directory_sync/operations/list_directory_groups.rs @@ -82,7 +82,7 @@ impl ListDirectoryGroups for DirectorySync<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(directory_groups) diff --git a/src/directory_sync/operations/list_directory_users.rs b/src/directory_sync/operations/list_directory_users.rs index 518ed3c..c82a2c4 100644 --- a/src/directory_sync/operations/list_directory_users.rs +++ b/src/directory_sync/operations/list_directory_users.rs @@ -82,7 +82,7 @@ impl ListDirectoryUsers for DirectorySync<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(directory_users) diff --git a/src/mfa.rs b/src/mfa.rs index e0f125a..c34e119 100644 --- a/src/mfa.rs +++ b/src/mfa.rs @@ -14,7 +14,7 @@ use crate::WorkOs; /// /// [WorkOS Docs: MFA Guide](https://workos.com/docs/mfa/guide) pub struct Mfa<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> Mfa<'a> { diff --git a/src/mfa/operations/challenge_factor.rs b/src/mfa/operations/challenge_factor.rs index bb74c63..49c65bd 100644 --- a/src/mfa/operations/challenge_factor.rs +++ b/src/mfa/operations/challenge_factor.rs @@ -93,7 +93,7 @@ impl ChallengeFactor for Mfa<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(challenge) diff --git a/src/mfa/operations/enroll_factor.rs b/src/mfa/operations/enroll_factor.rs index aa6781f..8d94913 100644 --- a/src/mfa/operations/enroll_factor.rs +++ b/src/mfa/operations/enroll_factor.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use reqwest::{Response, StatusCode}; +use http::StatusCode; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -65,13 +65,13 @@ where } #[async_trait] -impl HandleEnrollFactorError for Response { +impl<'a> HandleEnrollFactorError for Box { async fn handle_enroll_factor_error(self) -> WorkOsResult { match self.error_for_status_ref() { Ok(_) => Ok(self), Err(err) => match err.status() { Some(StatusCode::UNPROCESSABLE_ENTITY) => { - let error = self.json::().await?; + let error = self.json::().await?; Err(match error.code.as_str() { "invalid_phone_number" => { @@ -139,7 +139,7 @@ impl EnrollFactor for Mfa<'_> { .handle_unauthorized_error()? .handle_enroll_factor_error() .await? - .json::() + .json::() .await?; Ok(factor) diff --git a/src/mfa/operations/verify_challenge.rs b/src/mfa/operations/verify_challenge.rs index 8de2dd2..33d4bbd 100644 --- a/src/mfa/operations/verify_challenge.rs +++ b/src/mfa/operations/verify_challenge.rs @@ -85,7 +85,7 @@ impl VerifyChallenge for Mfa<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(verify_response) diff --git a/src/organizations.rs b/src/organizations.rs index 6318a65..2f63067 100644 --- a/src/organizations.rs +++ b/src/organizations.rs @@ -10,7 +10,7 @@ use crate::WorkOs; /// Organizations. pub struct Organizations<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> Organizations<'a> { diff --git a/src/organizations/operations/create_organization.rs b/src/organizations/operations/create_organization.rs index e0abcab..129879c 100644 --- a/src/organizations/operations/create_organization.rs +++ b/src/organizations/operations/create_organization.rs @@ -89,7 +89,7 @@ impl CreateOrganization for Organizations<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(organization) diff --git a/src/organizations/operations/get_organization.rs b/src/organizations/operations/get_organization.rs index 814d14c..12cfce2 100644 --- a/src/organizations/operations/get_organization.rs +++ b/src/organizations/operations/get_organization.rs @@ -62,7 +62,7 @@ impl GetOrganization for Organizations<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(organization) diff --git a/src/organizations/operations/list_organizations.rs b/src/organizations/operations/list_organizations.rs index 9706cb3..7210d2b 100644 --- a/src/organizations/operations/list_organizations.rs +++ b/src/organizations/operations/list_organizations.rs @@ -88,7 +88,7 @@ impl ListOrganizations for Organizations<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(organizations) diff --git a/src/organizations/operations/update_organization.rs b/src/organizations/operations/update_organization.rs index cc3997b..835303b 100644 --- a/src/organizations/operations/update_organization.rs +++ b/src/organizations/operations/update_organization.rs @@ -97,7 +97,7 @@ impl UpdateOrganization for Organizations<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(organization) diff --git a/src/passwordless.rs b/src/passwordless.rs index 7e7e837..8596004 100644 --- a/src/passwordless.rs +++ b/src/passwordless.rs @@ -14,7 +14,7 @@ use crate::WorkOs; /// /// [WorkOS Docs: Magic Link Guide](https://workos.com/docs/magic-link/guide) pub struct Passwordless<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> Passwordless<'a> { diff --git a/src/passwordless/operations/create_passwordless_session.rs b/src/passwordless/operations/create_passwordless_session.rs index fad5a2c..76f26d5 100644 --- a/src/passwordless/operations/create_passwordless_session.rs +++ b/src/passwordless/operations/create_passwordless_session.rs @@ -90,7 +90,7 @@ impl CreatePasswordlessSession for Passwordless<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(passwordless_session) diff --git a/src/sso.rs b/src/sso.rs index 5584b3d..e291c48 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -14,7 +14,7 @@ use crate::WorkOs; /// /// [WorkOS Docs: SSO Guide](https://workos.com/docs/sso/guide) pub struct Sso<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> Sso<'a> { diff --git a/src/sso/operations/get_connection.rs b/src/sso/operations/get_connection.rs index e28445a..5867f6b 100644 --- a/src/sso/operations/get_connection.rs +++ b/src/sso/operations/get_connection.rs @@ -59,7 +59,7 @@ impl GetConnection for Sso<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(connection) diff --git a/src/sso/operations/get_profile.rs b/src/sso/operations/get_profile.rs index 7192361..1e6ec64 100644 --- a/src/sso/operations/get_profile.rs +++ b/src/sso/operations/get_profile.rs @@ -51,7 +51,7 @@ impl GetProfile for Sso<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(get_profile_response) diff --git a/src/sso/operations/get_profile_and_token.rs b/src/sso/operations/get_profile_and_token.rs index 1df1e66..f0265c6 100644 --- a/src/sso/operations/get_profile_and_token.rs +++ b/src/sso/operations/get_profile_and_token.rs @@ -1,10 +1,11 @@ use async_trait::async_trait; -use reqwest::{Response, StatusCode}; +use http::StatusCode; use serde::Deserialize; use thiserror::Error; use crate::sso::{AccessToken, AuthorizationCode, ClientId, Profile, Sso}; -use crate::{WorkOsError, WorkOsResult}; +use crate::traits::ClientResponse; +use crate::{ResponseExt, WorkOsError, WorkOsResult}; /// The parameters for [`GetProfileAndToken`]. #[derive(Debug)] @@ -49,7 +50,7 @@ where } #[async_trait] -impl HandleGetProfileAndTokenError for Response { +impl<'a> HandleGetProfileAndTokenError for Box { async fn handle_get_profile_and_token_error( self, ) -> WorkOsResult { @@ -57,7 +58,7 @@ impl HandleGetProfileAndTokenError for Response { Ok(_) => Ok(self), Err(err) => match err.status() { Some(StatusCode::BAD_REQUEST) => { - let error = self.json::().await?; + let error = self.json::().await?; Err(match error.error.as_str() { "invalid_client" | "unauthorized_client" => WorkOsError::Unauthorized, @@ -125,7 +126,7 @@ impl GetProfileAndToken for Sso<'_> { .await? .handle_get_profile_and_token_error() .await? - .json::() + .json::() .await?; Ok(get_profile_and_token_response) diff --git a/src/sso/operations/list_connections.rs b/src/sso/operations/list_connections.rs index eddbdc9..5ed4692 100644 --- a/src/sso/operations/list_connections.rs +++ b/src/sso/operations/list_connections.rs @@ -68,7 +68,7 @@ impl ListConnections for Sso<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(connections) diff --git a/src/user_management.rs b/src/user_management.rs index 1975fee..5ff1479 100644 --- a/src/user_management.rs +++ b/src/user_management.rs @@ -14,7 +14,7 @@ use crate::WorkOs; /// /// [WorkOS Docs: User Management](https://workos.com/docs/user-management) pub struct UserManagement<'a> { - workos: &'a WorkOs, + workos: &'a WorkOs<'a>, } impl<'a> UserManagement<'a> { diff --git a/src/user_management/operations/authenticate_with_code.rs b/src/user_management/operations/authenticate_with_code.rs index d5986b9..d4dc085 100644 --- a/src/user_management/operations/authenticate_with_code.rs +++ b/src/user_management/operations/authenticate_with_code.rs @@ -7,7 +7,7 @@ use crate::sso::{AuthorizationCode, ClientId}; use crate::user_management::{ AuthenticateError, AuthenticationResponse, HandleAuthenticateError, UserManagement, }; -use crate::{ApiKey, WorkOsResult}; +use crate::{ApiKey, ResponseExt, WorkOsResult}; /// The parameters for [`AuthenticateWithCode`]. #[derive(Debug, Serialize)] @@ -109,7 +109,7 @@ impl AuthenticateWithCode for UserManagement<'_> { .await? .handle_authenticate_error() .await? - .json::() + .json::() .await?; Ok(authenticate_with_code_response) diff --git a/src/user_management/operations/authenticate_with_email_verification.rs b/src/user_management/operations/authenticate_with_email_verification.rs index 506670a..d40220c 100644 --- a/src/user_management/operations/authenticate_with_email_verification.rs +++ b/src/user_management/operations/authenticate_with_email_verification.rs @@ -8,7 +8,7 @@ use crate::user_management::{ AuthenticateError, AuthenticationResponse, EmailVerificationCode, HandleAuthenticateError, PendingAuthenticationToken, UserManagement, }; -use crate::{ApiKey, WorkOsResult}; +use crate::{ApiKey, ResponseExt, WorkOsResult}; /// The parameters for [`AuthenticateWithEmailVerification`]. #[derive(Debug, Serialize)] @@ -106,7 +106,7 @@ impl AuthenticateWithEmailVerification for UserManagement<'_> { .await? .handle_authenticate_error() .await? - .json::() + .json::() .await?; Ok(authenticate_with_email_verification_response) diff --git a/src/user_management/operations/authenticate_with_magic_auth.rs b/src/user_management/operations/authenticate_with_magic_auth.rs index bf32c65..af9e5b6 100644 --- a/src/user_management/operations/authenticate_with_magic_auth.rs +++ b/src/user_management/operations/authenticate_with_magic_auth.rs @@ -8,7 +8,7 @@ use crate::user_management::{ AuthenticateError, AuthenticationResponse, HandleAuthenticateError, MagicAuthCode, UserManagement, }; -use crate::{ApiKey, WorkOsResult}; +use crate::{ApiKey, ResponseExt, WorkOsResult}; /// The parameters for [`AuthenticateWithMagicAuth`]. #[derive(Debug, Serialize)] @@ -110,7 +110,7 @@ impl AuthenticateWithMagicAuth for UserManagement<'_> { .await? .handle_authenticate_error() .await? - .json::() + .json::() .await?; Ok(authenticate_with_magic_auth_response) diff --git a/src/user_management/operations/authenticate_with_password.rs b/src/user_management/operations/authenticate_with_password.rs index 224c933..4648c7c 100644 --- a/src/user_management/operations/authenticate_with_password.rs +++ b/src/user_management/operations/authenticate_with_password.rs @@ -7,7 +7,7 @@ use crate::sso::ClientId; use crate::user_management::{ AuthenticateError, AuthenticationResponse, HandleAuthenticateError, UserManagement, }; -use crate::{ApiKey, WorkOsResult}; +use crate::{ApiKey, ResponseExt, WorkOsResult}; /// The parameters for [`AuthenticateWithPassword`]. #[derive(Debug, Serialize)] @@ -109,7 +109,7 @@ impl AuthenticateWithPassword for UserManagement<'_> { .await? .handle_authenticate_error() .await? - .json::() + .json::() .await?; Ok(authenticate_with_password_response) diff --git a/src/user_management/operations/authenticate_with_refresh_token.rs b/src/user_management/operations/authenticate_with_refresh_token.rs index 525e4d6..b547f2d 100644 --- a/src/user_management/operations/authenticate_with_refresh_token.rs +++ b/src/user_management/operations/authenticate_with_refresh_token.rs @@ -9,7 +9,7 @@ use crate::user_management::{ AuthenticateError, AuthenticationResponse, HandleAuthenticateError, RefreshToken, UserManagement, }; -use crate::{ApiKey, WorkOsResult}; +use crate::{ApiKey, ResponseExt, WorkOsResult}; /// The parameters for [`AuthenticateWithRefreshToken`]. #[derive(Debug, Serialize)] @@ -107,7 +107,7 @@ impl AuthenticateWithRefreshToken for UserManagement<'_> { .await? .handle_authenticate_error() .await? - .json::() + .json::() .await?; Ok(authenticate_with_refresh_token_response) diff --git a/src/user_management/operations/authenticate_with_totp.rs b/src/user_management/operations/authenticate_with_totp.rs index bca770b..c7748d8 100644 --- a/src/user_management/operations/authenticate_with_totp.rs +++ b/src/user_management/operations/authenticate_with_totp.rs @@ -9,7 +9,7 @@ use crate::user_management::{ AuthenticateError, AuthenticationResponse, HandleAuthenticateError, PendingAuthenticationToken, UserManagement, }; -use crate::{ApiKey, WorkOsResult}; +use crate::{ApiKey, ResponseExt, WorkOsResult}; /// The parameters for [`AuthenticateWithTotp`]. #[derive(Debug, Serialize)] @@ -112,7 +112,7 @@ impl AuthenticateWithTotp for UserManagement<'_> { .await? .handle_authenticate_error() .await? - .json::() + .json::() .await?; Ok(authenticate_with_totp_response) diff --git a/src/user_management/operations/create_magic_auth.rs b/src/user_management/operations/create_magic_auth.rs index 5cce238..71bf366 100644 --- a/src/user_management/operations/create_magic_auth.rs +++ b/src/user_management/operations/create_magic_auth.rs @@ -76,7 +76,7 @@ impl CreateMagicAuth for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(user) diff --git a/src/user_management/operations/create_password_reset.rs b/src/user_management/operations/create_password_reset.rs index 82c9990..661618b 100644 --- a/src/user_management/operations/create_password_reset.rs +++ b/src/user_management/operations/create_password_reset.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use reqwest::{Response, StatusCode}; +use http::StatusCode; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::traits::ClientResponse; use crate::user_management::{PasswordReset, UserManagement}; use crate::{ResponseExt, WorkOsError, WorkOsResult}; @@ -45,7 +46,7 @@ where } #[async_trait] -impl HandleCreatePasswordResetError for Response { +impl<'a> HandleCreatePasswordResetError for Box { async fn handle_create_password_reset_error( self, ) -> WorkOsResult { @@ -53,7 +54,7 @@ impl HandleCreatePasswordResetError for Response { Ok(_) => Ok(self), Err(err) => match err.status() { Some(StatusCode::NOT_FOUND) => { - let error = self.json::().await?; + let error = self.json::().await?; Err(WorkOsError::Operation(error)) } @@ -118,7 +119,7 @@ impl CreatePasswordReset for UserManagement<'_> { .handle_unauthorized_error()? .handle_create_password_reset_error() .await? - .json::() + .json::() .await?; Ok(user) diff --git a/src/user_management/operations/create_user.rs b/src/user_management/operations/create_user.rs index c2195c1..38012e1 100644 --- a/src/user_management/operations/create_user.rs +++ b/src/user_management/operations/create_user.rs @@ -99,7 +99,7 @@ impl CreateUser for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(user) diff --git a/src/user_management/operations/enroll_auth_factor.rs b/src/user_management/operations/enroll_auth_factor.rs index 1c5e9bb..4490b2b 100644 --- a/src/user_management/operations/enroll_auth_factor.rs +++ b/src/user_management/operations/enroll_auth_factor.rs @@ -1,9 +1,10 @@ use async_trait::async_trait; -use reqwest::{Response, StatusCode}; +use http::StatusCode; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::mfa::{AuthenticationChallenge, AuthenticationFactor}; +use crate::traits::ClientResponse; use crate::user_management::{UserId, UserManagement}; use crate::{ResponseExt, WorkOsError, WorkOsResult}; @@ -77,14 +78,14 @@ where } #[async_trait] -impl HandleEnrollAuthFactorError for Response { +impl<'a> HandleEnrollAuthFactorError for Box { async fn handle_enroll_auth_factor_error(self) -> WorkOsResult { match self.error_for_status_ref() { Ok(_) => Ok(self), Err(err) => match err.status() { Some(StatusCode::BAD_REQUEST) | Some(StatusCode::UNPROCESSABLE_ENTITY) => { // let error = self.json::().await?; - let error = self.json::().await?; + let error = self.json::().await?; println!("{error:#?}"); @@ -158,7 +159,7 @@ impl EnrollAuthFactor for UserManagement<'_> { .handle_unauthorized_error()? .handle_enroll_auth_factor_error() .await? - .json::() + .json::() .await?; Ok(response) diff --git a/src/user_management/operations/get_email_verification.rs b/src/user_management/operations/get_email_verification.rs index c198197..73e2c78 100644 --- a/src/user_management/operations/get_email_verification.rs +++ b/src/user_management/operations/get_email_verification.rs @@ -62,7 +62,7 @@ impl GetEmailVerification for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(organization) diff --git a/src/user_management/operations/get_jwks.rs b/src/user_management/operations/get_jwks.rs index d4dcd71..0414708 100644 --- a/src/user_management/operations/get_jwks.rs +++ b/src/user_management/operations/get_jwks.rs @@ -52,7 +52,7 @@ impl GetJwks for UserManagement<'_> { .send() .await? .handle_generic_error()? - .json::() + .json::() .await?; Ok(jwks) diff --git a/src/user_management/operations/get_magic_auth.rs b/src/user_management/operations/get_magic_auth.rs index 1edca6c..f88c7b9 100644 --- a/src/user_management/operations/get_magic_auth.rs +++ b/src/user_management/operations/get_magic_auth.rs @@ -56,7 +56,7 @@ impl GetMagicAuth for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(organization) diff --git a/src/user_management/operations/get_password_reset.rs b/src/user_management/operations/get_password_reset.rs index 545de2f..fa1842a 100644 --- a/src/user_management/operations/get_password_reset.rs +++ b/src/user_management/operations/get_password_reset.rs @@ -62,7 +62,7 @@ impl GetPasswordReset for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(organization) diff --git a/src/user_management/operations/get_user.rs b/src/user_management/operations/get_user.rs index 0c818d5..9547db8 100644 --- a/src/user_management/operations/get_user.rs +++ b/src/user_management/operations/get_user.rs @@ -56,7 +56,7 @@ impl GetUser for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(user) diff --git a/src/user_management/operations/get_user_by_external_id.rs b/src/user_management/operations/get_user_by_external_id.rs index d742451..1739ae7 100644 --- a/src/user_management/operations/get_user_by_external_id.rs +++ b/src/user_management/operations/get_user_by_external_id.rs @@ -62,7 +62,7 @@ impl GetUserByExternalId for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(user) diff --git a/src/user_management/operations/get_user_identities.rs b/src/user_management/operations/get_user_identities.rs index 621d058..4e0c0e1 100644 --- a/src/user_management/operations/get_user_identities.rs +++ b/src/user_management/operations/get_user_identities.rs @@ -63,7 +63,7 @@ impl GetUserIdentities for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(users) diff --git a/src/user_management/operations/list_auth_factors.rs b/src/user_management/operations/list_auth_factors.rs index 191ddf3..febd595 100644 --- a/src/user_management/operations/list_auth_factors.rs +++ b/src/user_management/operations/list_auth_factors.rs @@ -81,7 +81,7 @@ impl ListAuthFactors for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(organizations) diff --git a/src/user_management/operations/list_users.rs b/src/user_management/operations/list_users.rs index 2ecc745..1849830 100644 --- a/src/user_management/operations/list_users.rs +++ b/src/user_management/operations/list_users.rs @@ -79,7 +79,7 @@ impl ListUsers for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::>() + .json::, _>() .await?; Ok(users) diff --git a/src/user_management/operations/reset_password.rs b/src/user_management/operations/reset_password.rs index c932510..57d928c 100644 --- a/src/user_management/operations/reset_password.rs +++ b/src/user_management/operations/reset_password.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use reqwest::{Response, StatusCode}; +use http::StatusCode; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::traits::ClientResponse; use crate::user_management::{PasswordResetToken, User, UserManagement}; use crate::{ResponseExt, WorkOsError, WorkOsResult}; @@ -85,13 +86,13 @@ where } #[async_trait] -impl HandleResetPasswordError for Response { +impl<'a> HandleResetPasswordError for Box { async fn handle_reset_password_error(self) -> WorkOsResult { match self.error_for_status_ref() { Ok(_) => Ok(self), Err(err) => match err.status() { Some(StatusCode::BAD_REQUEST) | Some(StatusCode::NOT_FOUND) => { - let error = self.json::().await?; + let error = self.json::().await?; Err(WorkOsError::Operation(error)) } @@ -158,7 +159,7 @@ impl ResetPassword for UserManagement<'_> { .handle_unauthorized_error()? .handle_reset_password_error() .await? - .json::() + .json::() .await?; Ok(response) diff --git a/src/user_management/operations/update_user.rs b/src/user_management/operations/update_user.rs index 5575252..0cd9642 100644 --- a/src/user_management/operations/update_user.rs +++ b/src/user_management/operations/update_user.rs @@ -108,7 +108,7 @@ impl UpdateUser for UserManagement<'_> { .send() .await? .handle_unauthorized_or_generic_error()? - .json::() + .json::() .await?; Ok(user) diff --git a/src/user_management/types/authenticate_error.rs b/src/user_management/types/authenticate_error.rs index 3e2489c..6f5dd5f 100644 --- a/src/user_management/types/authenticate_error.rs +++ b/src/user_management/types/authenticate_error.rs @@ -1,10 +1,10 @@ use async_trait::async_trait; -use reqwest::{Response, StatusCode}; +use http::StatusCode; use serde::Deserialize; use thiserror::Error; use crate::{ - WorkOsError, WorkOsResult, mfa::AuthenticationFactorIdAndType, + ResponseExt, WorkOsError, WorkOsResult, mfa::AuthenticationFactorIdAndType, organizations::OrganizationIdAndName, sso::ConnectionId, }; @@ -270,13 +270,13 @@ where } #[async_trait] -impl HandleAuthenticateError for Response { +impl<'a> HandleAuthenticateError for Box { async fn handle_authenticate_error(self) -> WorkOsResult { match self.error_for_status_ref() { Ok(_) => Ok(self), Err(err) => match err.status() { Some(StatusCode::BAD_REQUEST) => { - let authenticate_error = self.json::().await?; + let authenticate_error = self.json::().await?; Err(match &authenticate_error { AuthenticateError::WithError(AuthenticateErrorWithError::Other { @@ -290,7 +290,7 @@ impl HandleAuthenticateError for Response { }) } Some(StatusCode::FORBIDDEN) => { - let authenticate_error = self.json::().await?; + let authenticate_error = self.json::().await?; Err(WorkOsError::Operation(authenticate_error)) } diff --git a/src/workos.rs b/src/workos.rs index 3d9ce2c..3822830 100644 --- a/src/workos.rs +++ b/src/workos.rs @@ -1,3 +1,6 @@ +use std::marker::PhantomData; +use std::sync::Arc; + use url::{ParseError, Url}; use crate::ApiKey; @@ -7,17 +10,19 @@ use crate::mfa::Mfa; use crate::organizations::Organizations; use crate::passwordless::Passwordless; use crate::sso::Sso; +use crate::traits::Client; use crate::user_management::UserManagement; /// The WorkOS client. #[derive(Clone)] -pub struct WorkOs { +pub struct WorkOs<'n> { base_url: Url, key: ApiKey, - client: reqwest::Client, + client: Arc, + phantom: PhantomData<&'n ()>, } -impl WorkOs { +impl WorkOs<'_> { /// Returns a new instance of the WorkOS client using the provided API key. pub fn new(key: &ApiKey) -> Self { WorkOsBuilder::new(key).build() @@ -36,8 +41,8 @@ impl WorkOs { &self.key } - pub(crate) fn client(&self) -> &reqwest::Client { - &self.client + pub(crate) fn client(&self) -> &(dyn crate::traits::Client + '_) { + &*self.client } /// Returns an [`AdminPortal`] instance. @@ -103,17 +108,29 @@ impl<'a> WorkOsBuilder<'a> { self } + #[cfg(feature = "reqwest")] /// Consumes the builder and returns the constructed client. - pub fn build(self) -> WorkOs { + pub fn build(self) -> WorkOs<'static> { let client = reqwest::Client::builder() .user_agent(concat!("workos-rust/", env!("CARGO_PKG_VERSION"))) .build() .unwrap(); + self.build_with_client(Arc::new(client)) + } + + /// Consumes the builder and returns the constructed client. + pub fn build_with_client<'b>(self, client: Arc) -> WorkOs<'b> { + // let client = reqwest::Client::builder() + // .user_agent(concat!("workos-rust/", env!("CARGO_PKG_VERSION"))) + // .build() + // .unwrap(); + WorkOs { base_url: self.base_url, key: self.key.to_owned(), client, + phantom: PhantomData, } } }