From b2cc50607f7148687e5605223d647bbdc94467f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABlle=20Huisman?= Date: Fri, 10 Apr 2026 10:35:10 +0200 Subject: [PATCH] feat(user-management): add resend invitation --- src/user_management/operations.rs | 2 + .../operations/resend_invitation.rs | 148 +++++++ .../operations/send_invitation.rs | 7 +- src/user_management/types.rs | 2 + src/user_management/types/locale.rs | 372 ++++++++++++++++++ 5 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 src/user_management/operations/resend_invitation.rs create mode 100644 src/user_management/types/locale.rs diff --git a/src/user_management/operations.rs b/src/user_management/operations.rs index 0d1f412..3b10f59 100644 --- a/src/user_management/operations.rs +++ b/src/user_management/operations.rs @@ -36,6 +36,7 @@ mod list_organization_memberships; mod list_sessions; mod list_users; mod reactivate_organization_membership; +mod resend_invitation; mod reset_password; mod revoke_invitation; mod revoke_session; @@ -81,6 +82,7 @@ pub use list_organization_memberships::*; pub use list_sessions::*; pub use list_users::*; pub use reactivate_organization_membership::*; +pub use resend_invitation::*; pub use reset_password::*; pub use revoke_invitation::*; pub use revoke_session::*; diff --git a/src/user_management/operations/resend_invitation.rs b/src/user_management/operations/resend_invitation.rs new file mode 100644 index 0000000..179303c --- /dev/null +++ b/src/user_management/operations/resend_invitation.rs @@ -0,0 +1,148 @@ +use async_trait::async_trait; +use serde::Serialize; +use thiserror::Error; + +use crate::user_management::{Invitation, InvitationId, Locale, UserManagement}; +use crate::{ResponseExt, WorkOsError, WorkOsResult}; + +/// The parameters for [`ResendInvitation`]. +#[derive(Debug, Serialize)] +pub struct ResendInvitationParams<'a> { + /// The ID of the invitation. + #[serde(skip_serializing)] + pub invitation_id: &'a InvitationId, + + /// The locale to use when rendering the invitation email. + pub locale: Option<&'a Locale>, +} + +/// An error returned from [`ResendInvitation`]. +#[derive(Debug, Error)] +pub enum ResendInvitationError {} + +impl From for WorkOsError { + fn from(err: ResendInvitationError) -> Self { + Self::Operation(err) + } +} + +/// [WorkOS Docs: Resend an invitation](https://workos.com/docs/reference/user-management/invitation/resend) +#[async_trait] +pub trait ResendInvitation { + /// Resends an invitation email to the recipient. The invitation must be in a pending state. + /// + /// [WorkOS Docs: Resend an invitation](https://workos.com/docs/reference/user-management/invitation/resend) + /// + /// # Examples + /// + /// ``` + /// # use workos::WorkOsResult; + /// # use workos::user_management::*; + /// use workos::{ApiKey, WorkOs}; + /// + /// # async fn run() -> WorkOsResult<(), ResendInvitationError> { + /// let workos = WorkOs::new(&ApiKey::from("sk_example_123456789")); + /// + /// let invitation = workos + /// .user_management() + /// .resend_invitation(&ResendInvitationParams { + /// invitation_id: &InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5"), + /// locale: None, + /// }) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + async fn resend_invitation( + &self, + params: &ResendInvitationParams<'_>, + ) -> WorkOsResult; +} + +#[async_trait] +impl ResendInvitation for UserManagement<'_> { + async fn resend_invitation( + &self, + params: &ResendInvitationParams<'_>, + ) -> WorkOsResult { + let url = self.workos.base_url().join(&format!( + "/user_management/invitations/{id}/resend", + id = params.invitation_id + ))?; + let invitation = self + .workos + .client() + .post(url) + .bearer_auth(self.workos.key()) + .json(¶ms) + .send() + .await? + .handle_unauthorized_or_generic_error() + .await? + .json::() + .await?; + + Ok(invitation) + } +} + +#[cfg(test)] +mod test { + use serde_json::json; + use tokio; + + use crate::user_management::InvitationId; + use crate::{ApiKey, WorkOs}; + + use super::*; + + #[tokio::test] + async fn it_calls_the_resend_invitation_endpoint() { + let mut server = mockito::Server::new_async().await; + + let workos = WorkOs::builder(&ApiKey::from("sk_example_123456789")) + .base_url(&server.url()) + .unwrap() + .build(); + + server + .mock("POST", "/user_management/invitations/invitation_01E4ZCR3C56J083X43JQXF3JK5/resend") + .match_header("Authorization", "Bearer sk_example_123456789") + .with_status(200) + .with_body( + json!({ + "object": "invitation", + "id": "invitation_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "accepted_at": null, + "revoked_at": null, + "expires_at": "2021-07-01T19:07:33.155Z", + "token": "Z1uX3RbwcIl5fIGJJJCXXisdI", + "accept_invitation_url": "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI", + "organization_id": "org_01E4ZCR3C56J083X43JQXF3JK5", + "inviter_user_id": "user_01HYGBX8ZGD19949T3BM4FW1C3", + "accepted_user_id": null, + "created_at": "2021-06-25T19:07:33.155Z", + "updated_at": "2021-06-25T19:07:33.155Z" + }) + .to_string(), + ) + .create_async() + .await; + + let invitation = workos + .user_management() + .resend_invitation(&ResendInvitationParams { + invitation_id: &InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5"), + locale: None, + }) + .await + .unwrap(); + + assert_eq!( + invitation.id, + InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5") + ); + } +} diff --git a/src/user_management/operations/send_invitation.rs b/src/user_management/operations/send_invitation.rs index 396b84f..051fddb 100644 --- a/src/user_management/operations/send_invitation.rs +++ b/src/user_management/operations/send_invitation.rs @@ -4,7 +4,7 @@ use thiserror::Error; use crate::organizations::OrganizationId; use crate::roles::RoleSlug; -use crate::user_management::{Invitation, UserId, UserManagement}; +use crate::user_management::{Invitation, Locale, UserId, UserManagement}; use crate::{ResponseExt, WorkOsError, WorkOsResult}; /// The parameters for [`SendInvitation`]. @@ -26,6 +26,9 @@ pub struct SendInvitationParams<'a> { /// The role that the recipient will receive when they join the organization in the invitation. pub role_slug: Option<&'a RoleSlug>, + + /// The locale to use when rendering the invitation email. + pub locale: Option<&'a Locale>, } /// An error returned from [`SendInvitation`]. @@ -63,6 +66,7 @@ pub trait SendInvitation { /// expires_in_days: None, /// inviter_user_id: None, /// role_slug: None, + /// locale: None, /// }) /// .await?; /// # Ok(()) @@ -153,6 +157,7 @@ mod test { expires_in_days: None, inviter_user_id: None, role_slug: None, + locale: None, }) .await .unwrap(); diff --git a/src/user_management/types.rs b/src/user_management/types.rs index 75e8815..9bf802e 100644 --- a/src/user_management/types.rs +++ b/src/user_management/types.rs @@ -9,6 +9,7 @@ mod email_verification; mod identity; mod impersonator; mod invitation; +mod locale; mod magic_auth; mod organization_membership; mod password; @@ -32,6 +33,7 @@ pub use email_verification::*; pub use identity::*; pub use impersonator::*; pub use invitation::*; +pub use locale::*; pub use magic_auth::*; pub use organization_membership::*; pub use password::*; diff --git a/src/user_management/types/locale.rs b/src/user_management/types/locale.rs new file mode 100644 index 0000000..dcc1a75 --- /dev/null +++ b/src/user_management/types/locale.rs @@ -0,0 +1,372 @@ +use serde::{Deserialize, Serialize}; + +/// Locale. +/// +/// [WorkOS Docs: Localization](https://workos.com/docs/authkit/hosted-ui#localization). +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] + +pub enum Locale { + /// Afrikaans + #[serde(rename = "af")] + Af, + + /// Amharic + #[serde(rename = "am")] + Am, + + /// Arabic + #[serde(rename = "ar")] + Ar, + + /// Bulgarian + #[serde(rename = "bg")] + Bg, + + /// Bengali (Bangla) + #[serde(rename = "bn")] + Bn, + + /// Bosnian + #[serde(rename = "bs")] + Bs, + + /// Catalan + #[serde(rename = "ca")] + Ca, + + /// Czech + #[serde(rename = "cs")] + Cs, + + /// Danish + #[serde(rename = "da")] + Da, + + /// German + #[serde(rename = "de")] + De, + + /// German (Germany) + #[serde(rename = "de-DE")] + DeDe, + + /// Greek + #[serde(rename = "el")] + El, + + /// English + #[serde(rename = "en")] + En, + + /// English (Australia) + #[serde(rename = "en-AU")] + EnAu, + + /// English (Canada) + #[serde(rename = "en-CA")] + EnCa, + + /// English (UK) + #[serde(rename = "en-GB")] + EnGb, + + /// English (US) + #[serde(rename = "en-US")] + EnUs, + + /// Spanish + #[serde(rename = "es")] + Es, + + /// Spanish (Latin America) + #[serde(rename = "es-419")] + Es419, + + /// Spanish (Spain) + #[serde(rename = "es-ES")] + EsEs, + + /// Spanish (US) + #[serde(rename = "es-US")] + EsUs, + + /// Estonian + #[serde(rename = "et")] + Et, + + /// Farsi (Persian) + #[serde(rename = "fa")] + Fa, + + /// Finnish + #[serde(rename = "fi")] + Fi, + + /// Filipino (Tagalog) + #[serde(rename = "fil")] + Fil, + + /// French + #[serde(rename = "fr")] + Fr, + + /// French (Belgium) + #[serde(rename = "fr-BE")] + FrBe, + + /// French (Canada) + #[serde(rename = "fr-CA")] + FrCa, + + /// French (France) + #[serde(rename = "fr-FR")] + FrFr, + + /// Frisian + #[serde(rename = "fy")] + Fy, + + /// Galician + #[serde(rename = "gl")] + Gl, + + /// Gujarati + #[serde(rename = "gu")] + Gu, + + /// Hausa + #[serde(rename = "ha")] + Ha, + + /// Hebrew + #[serde(rename = "he")] + He, + + /// Hindi + #[serde(rename = "hi")] + Hi, + + /// Croatian + #[serde(rename = "hr")] + Hr, + + /// Hungarian + #[serde(rename = "hu")] + Hu, + + /// Armenian + #[serde(rename = "hy")] + Hy, + + /// Indonesian + #[serde(rename = "id")] + Id, + + /// Icelandic + #[serde(rename = "is")] + Is, + + /// Italian + #[serde(rename = "it")] + It, + + /// Italian (Italy) + #[serde(rename = "it-IT")] + ItIt, + + /// Japanese + #[serde(rename = "ja")] + Ja, + + /// Javanese + #[serde(rename = "jv")] + Jv, + + /// Georgian + #[serde(rename = "ka")] + Ka, + + /// Kazakh + #[serde(rename = "kk")] + Kk, + + /// Khmer + #[serde(rename = "km")] + Km, + + /// Kannada + #[serde(rename = "kn")] + Kn, + + /// Korean + #[serde(rename = "ko")] + Ko, + + /// Lithuanian + #[serde(rename = "lt")] + Lt, + + /// Latvian + #[serde(rename = "lv")] + Lv, + + /// Macedonian + #[serde(rename = "mk")] + Mk, + + /// Malayalam + #[serde(rename = "ml")] + Ml, + + /// Mongolian + #[serde(rename = "mn")] + Mn, + + /// Marathi + #[serde(rename = "mr")] + Mr, + + /// Malay + #[serde(rename = "ms")] + Ms, + + /// Burmese + #[serde(rename = "my")] + My, + + /// Norwegian BokmÃ¥l + #[serde(rename = "nb")] + Nb, + + /// Nepali + #[serde(rename = "ne")] + Ne, + + /// Dutch + #[serde(rename = "nl")] + Nl, + + /// Flemish + #[serde(rename = "nl-BE")] + NlBe, + + /// Dutch (Netherlands) + #[serde(rename = "nl-NL")] + NlNl, + + /// Norwegian Nynorsk + #[serde(rename = "nn")] + Nn, + + /// Norwegian + #[serde(rename = "no")] + No, + + /// Punjabi + #[serde(rename = "pa")] + Pa, + + /// Polish + #[serde(rename = "pl")] + Pl, + + /// Portuguese + #[serde(rename = "pt")] + Pt, + + /// Portuguese (Brazil) + #[serde(rename = "pt-BR")] + PtBr, + + /// Portuguese (Portugal) + #[serde(rename = "pt-PT")] + PtPt, + + /// Romanian + #[serde(rename = "ro")] + Ro, + + /// Russian + #[serde(rename = "ru")] + Ru, + + /// Slovak + #[serde(rename = "sk")] + Sk, + + /// Slovenian + #[serde(rename = "sl")] + Sl, + + /// Albanian + #[serde(rename = "sq")] + Sq, + + /// Serbian + #[serde(rename = "sr")] + Sr, + + /// Swedish + #[serde(rename = "sv")] + Sv, + + /// Swahili + #[serde(rename = "sw")] + Sw, + + /// Tamil + #[serde(rename = "ta")] + Ta, + + /// Telgu + #[serde(rename = "te")] + Te, + + /// Thai + #[serde(rename = "th")] + Th, + + /// Turkish + #[serde(rename = "tr")] + Tr, + + /// Ukrainian + #[serde(rename = "uk")] + Uk, + + /// Urdu + #[serde(rename = "ur")] + Ur, + + /// Uzbek + #[serde(rename = "uz")] + Uz, + + /// Vietnamese + #[serde(rename = "vi")] + Vi, + + /// Chinese + #[serde(rename = "zh")] + Zh, + + /// Chinese (Simplified) + #[serde(rename = "zh-CN")] + ZhCn, + + /// Chinese (Hong Kong) + #[serde(rename = "zh-HK")] + ZhHk, + + /// Chinese (Taiwan) + #[serde(rename = "zh-TW")] + ZhTw, + + /// Zulu + #[serde(rename = "zu")] + Zu, + + /// Unknown locale. + #[serde(untagged)] + Unknown(String), +}