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#[derive(Clone)]
14pub struct ApiKeysSvc(pub(crate) Arc<Config>);
15
16impl ApiKeysSvc {
17 #[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 #[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 #[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 #[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 #[must_use]
94 #[derive(Debug, Clone, Serialize)]
95 pub struct CreateApiKeyOptions {
96 name: String,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
103 permission: Option<Permission>,
104 #[serde(skip_serializing_if = "Option::is_none")]
107 domain_id: Option<DomainId>,
108 }
109
110 impl CreateApiKeyOptions {
111 #[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 #[inline]
125 pub const fn with_full_access(mut self) -> Self {
126 self.permission = Some(Permission::FullAccess);
127 self
128 }
129
130 #[inline]
132 pub const fn with_sending_access(mut self) -> Self {
133 self.permission = Some(Permission::SendingAccess);
134 self
135 }
136
137 #[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 #[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 #[must_use]
164 #[derive(Debug, Clone, Serialize)]
165 pub struct UpdateApiKeyOptions {
166 name: String,
168 }
169
170 impl UpdateApiKeyOptions {
171 #[inline]
173 pub fn new(name: &str) -> Self {
174 Self {
175 name: name.to_owned(),
176 }
177 }
178 }
179
180 #[must_use]
182 #[derive(Debug, Clone, Serialize, Deserialize)]
183 pub struct UpdateApiKeyResponse {
184 pub id: ApiKeyId,
186 }
187
188 #[must_use]
190 #[derive(Debug, Clone, Serialize, Deserialize)]
191 pub struct ApiKeyToken {
192 pub id: ApiKeyId,
194 pub token: String,
196 }
197
198 #[must_use]
200 #[derive(Debug, Clone, Serialize, Deserialize)]
201 pub struct ApiKey {
202 pub id: ApiKeyId,
204 pub name: String,
206 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 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 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 resend.api_keys.delete(&id).await?;
245
246 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}