Skip to main content

resend_rs/
api_keys.rs

1use std::fmt;
2use std::sync::Arc;
3
4use reqwest::Method;
5
6use crate::{Config, Result};
7use crate::{
8    list_opts::{ListOptions, ListResponse},
9    types::{ApiKey, ApiKeyToken, CreateApiKeyOptions, UpdateApiKeyOptions, UpdateApiKeyResponse},
10};
11
12/// `Resend` APIs for `/api-keys` endpoints.
13#[derive(Clone)]
14pub struct ApiKeysSvc(pub(crate) Arc<Config>);
15
16impl ApiKeysSvc {
17    /// Add a new API key to authenticate communications with Resend.
18    ///
19    /// <https://resend.com/docs/api-reference/api-keys/create-api-key>
20    #[maybe_async::maybe_async]
21    pub async fn create(&self, api_key: CreateApiKeyOptions) -> Result<ApiKeyToken> {
22        let request = self.0.build(Method::POST, "/api-keys");
23        let response = self.0.send(request.json(&api_key)).await?;
24        let content = response.json::<ApiKeyToken>().await?;
25
26        Ok(content)
27    }
28
29    /// Retrieve a list of API keys for the authenticated user.
30    ///
31    /// - Default limit: no limit (return everything)
32    ///
33    /// <https://resend.com/docs/api-reference/api-keys/list-api-keys>
34    #[maybe_async::maybe_async]
35    pub async fn list<T>(&self, list_opts: ListOptions<T>) -> Result<ListResponse<ApiKey>> {
36        let request = self.0.build(Method::GET, "/api-keys").query(&list_opts);
37        let response = self.0.send(request).await?;
38        let content = response.json::<ListResponse<ApiKey>>().await?;
39
40        Ok(content)
41    }
42
43    /// Updates an existing API key.
44    ///
45    /// Only the name of the API key can be changed. Permission and domain access are fixed at
46    /// creation time and cannot be widened after the fact.
47    ///
48    /// <https://resend.com/docs/api-reference/api-keys/update-api-key>
49    #[maybe_async::maybe_async]
50    pub async fn update(
51        &self,
52        api_key_id: &str,
53        changes: UpdateApiKeyOptions,
54    ) -> Result<UpdateApiKeyResponse> {
55        let path = format!("/api-keys/{api_key_id}");
56
57        let request = self.0.build(Method::PATCH, &path);
58        let response = self.0.send(request.json(&changes)).await?;
59        let content = response.json::<UpdateApiKeyResponse>().await?;
60
61        Ok(content)
62    }
63
64    /// Remove an existing API key.
65    ///
66    /// <https://resend.com/docs/api-reference/api-keys/delete-api-key>
67    #[maybe_async::maybe_async]
68    pub async fn delete(&self, api_key_id: &str) -> Result<()> {
69        let path = format!("/api-keys/{api_key_id}");
70
71        let request = self.0.build(Method::DELETE, &path);
72        let _response = self.0.send(request).await?;
73
74        Ok(())
75    }
76}
77
78impl fmt::Debug for ApiKeysSvc {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        fmt::Debug::fmt(&self.0, f)
81    }
82}
83
84#[allow(unreachable_pub)]
85pub mod types {
86    use serde::{Deserialize, Serialize};
87
88    use crate::types::DomainId;
89
90    crate::define_id_type!(ApiKeyId);
91
92    /// Name and permissions of the new [`ApiKey`].
93    #[must_use]
94    #[derive(Debug, Clone, Serialize)]
95    pub struct CreateApiKeyOptions {
96        /// The API key name.
97        name: String,
98
99        /// The API key can have full access to Resend’s API or be only restricted to send emails.
100        /// * `full_access` - Can create, delete, get, and update any resource.
101        /// * `sending_access` - Can only send emails.
102        #[serde(skip_serializing_if = "Option::is_none")]
103        permission: Option<Permission>,
104        /// Restrict an API key to send emails only from a specific domain.
105        /// Only used when the permission is `sending_access`.
106        #[serde(skip_serializing_if = "Option::is_none")]
107        domain_id: Option<DomainId>,
108    }
109
110    impl CreateApiKeyOptions {
111        /// Creates a new [`CreateApiKeyOptions`].
112        #[inline]
113        pub fn new(name: &str) -> Self {
114            Self {
115                name: name.to_owned(),
116                permission: None,
117                domain_id: None,
118            }
119        }
120
121        /// The API key can have full access to Resend’s API or be only restricted to send emails.
122        /// * `full_access` - Can create, delete, get, and update any resource.
123        /// * `sending_access` - Can only send emails.
124        #[inline]
125        pub const fn with_full_access(mut self) -> Self {
126            self.permission = Some(Permission::FullAccess);
127            self
128        }
129
130        /// Restricts an API key to only sending emails
131        #[inline]
132        pub const fn with_sending_access(mut self) -> Self {
133            self.permission = Some(Permission::SendingAccess);
134            self
135        }
136
137        /// Restricts an API key to send emails only from a specific domain.
138        #[inline]
139        pub fn with_domain_access(mut self, domain_id: &DomainId) -> Self {
140            self.permission = Some(Permission::SendingAccess);
141            self.domain_id = Some(domain_id.clone());
142            self
143        }
144    }
145
146    /// Full or restricted access of the [`ApiKey`].
147    ///
148    /// * `full_access` - Can create, delete, get, and update any resource.
149    /// * `sending_access` - Can only send emails.
150    #[must_use]
151    #[derive(Debug, Copy, Clone, Serialize)]
152    pub enum Permission {
153        #[serde(rename = "full_access")]
154        FullAccess,
155        #[serde(rename = "sending_access")]
156        SendingAccess,
157    }
158
159    /// Changes to apply to an existing [`ApiKey`].
160    ///
161    /// Only the name can be changed. `permission` and `domain_id` are deliberately not
162    /// patchable through this endpoint.
163    #[must_use]
164    #[derive(Debug, Clone, Serialize)]
165    pub struct UpdateApiKeyOptions {
166        /// The API key name.
167        name: String,
168    }
169
170    impl UpdateApiKeyOptions {
171        /// Creates a new [`UpdateApiKeyOptions`].
172        #[inline]
173        pub fn new(name: &str) -> Self {
174            Self {
175                name: name.to_owned(),
176            }
177        }
178    }
179
180    /// ID of the updated [`ApiKey`].
181    #[must_use]
182    #[derive(Debug, Clone, Serialize, Deserialize)]
183    pub struct UpdateApiKeyResponse {
184        /// The ID of the updated API key.
185        pub id: ApiKeyId,
186    }
187
188    /// Token and ID of the newly created [`ApiKey`].
189    #[must_use]
190    #[derive(Debug, Clone, Serialize, Deserialize)]
191    pub struct ApiKeyToken {
192        /// The ID of the API key.
193        pub id: ApiKeyId,
194        /// The token of the API key.
195        pub token: String,
196    }
197
198    /// Name and ID of an existing API key.
199    #[must_use]
200    #[derive(Debug, Clone, Serialize, Deserialize)]
201    pub struct ApiKey {
202        /// The ID of the API key.
203        pub id: ApiKeyId,
204        /// The name of the API key.
205        pub name: String,
206        /// The date and time the API key was created.
207        pub created_at: String,
208        pub last_used_at: Option<String>,
209    }
210}
211
212#[cfg(test)]
213#[allow(clippy::needless_return)]
214mod test {
215    #[cfg(not(feature = "blocking"))]
216    use crate::{
217        list_opts::ListOptions,
218        test::{CLIENT, DebugResult},
219        types::{CreateApiKeyOptions, UpdateApiKeyOptions},
220    };
221
222    #[tokio_shared_rt::test(shared = true)]
223    #[serial_test::serial]
224    #[cfg(not(feature = "blocking"))]
225    async fn all() -> DebugResult<()> {
226        let resend = &*CLIENT;
227
228        let api_key = "test_";
229
230        // Create.
231        let request = CreateApiKeyOptions::new(api_key).with_full_access();
232        let response = resend.api_keys.create(request).await?;
233        let id = response.id;
234
235        // List.
236        let api_keys = resend.api_keys.list(ListOptions::default()).await?;
237        let api_keys_amt = api_keys.len();
238
239        let update = UpdateApiKeyOptions::new("test_renamed");
240        let response = resend.api_keys.update(&id, update).await?;
241        assert_eq!(response.id, id);
242
243        // Delete.
244        resend.api_keys.delete(&id).await?;
245
246        // List.
247        let api_keys = resend.api_keys.list(ListOptions::default()).await?;
248        assert_eq!(api_keys_amt, api_keys.len() + 1);
249
250        Ok(())
251    }
252}