1#![allow(clippy::field_reassign_with_default)]
31
32use reqwest::{header, Method, StatusCode, Url};
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::error::Error;
36use std::fmt;
37
38const ENDPOINT: &str = "https://api.clerk.com/v1/";
40
41#[derive(Debug)]
43pub enum ClerkClientError {
44 RequestError(reqwest::Error),
46 ApiError { status: StatusCode, message: String },
48 HeaderError(reqwest::header::InvalidHeaderValue),
50 InvalidUrl(String),
52}
53
54impl fmt::Display for ClerkClientError {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::RequestError(e) => write!(f, "Request error: {}", e),
58 Self::ApiError { status, message } => write!(f, "API error ({}): {}", status, message),
59 Self::HeaderError(e) => write!(f, "Header error: {}", e),
60 Self::InvalidUrl(msg) => write!(f, "Invalid URL: {}", msg),
61 }
62 }
63}
64
65impl Error for ClerkClientError {}
66
67impl From<reqwest::Error> for ClerkClientError {
68 fn from(err: reqwest::Error) -> Self {
69 Self::RequestError(err)
70 }
71}
72
73impl From<reqwest::header::InvalidHeaderValue> for ClerkClientError {
74 fn from(err: reqwest::header::InvalidHeaderValue) -> Self {
75 Self::HeaderError(err)
76 }
77}
78
79pub type Result<T> = std::result::Result<T, ClerkClientError>;
80
81pub struct ClerkClient {
83 secret_key: String,
84 client: reqwest::Client,
85}
86
87pub fn secret_key_from_env() -> String {
89 std::env::var("CLERK_SECRET_KEY").unwrap_or_default()
90}
91
92impl ClerkClient {
93 pub fn new<K>(secret_key: K) -> Result<Self>
95 where
96 K: ToString,
97 {
98 let client = reqwest::Client::builder()
99 .timeout(std::time::Duration::from_secs(30))
100 .build()?;
101
102 Ok(Self {
103 secret_key: secret_key.to_string(),
104 client,
105 })
106 }
107
108 pub fn new_from_env() -> Result<Self> {
110 ClerkClient::new(secret_key_from_env())
111 }
112
113 pub fn get_key(&self) -> &str {
115 &self.secret_key
116 }
117
118 async fn request<B>(
119 &self,
120 method: Method,
121 path: String,
122 body: B,
123 query: Option<Vec<(&str, String)>>,
124 ) -> Result<reqwest::Response>
125 where
126 B: Serialize,
127 {
128 let base = Url::parse(ENDPOINT).map_err(|e| {
129 ClerkClientError::InvalidUrl(e.to_string())
130 })?;
131 let url = base.join(&path).map_err(|e| {
132 ClerkClientError::InvalidUrl(e.to_string())
133 })?;
134
135 let bt = format!("Bearer {}", self.secret_key);
136 let bearer = header::HeaderValue::from_str(&bt)?;
137
138 let mut headers = header::HeaderMap::new();
140 headers.append(header::AUTHORIZATION, bearer);
141 headers.append(
142 header::CONTENT_TYPE,
143 header::HeaderValue::from_static("application/json"),
144 );
145 headers.append(
146 header::USER_AGENT,
147 header::HeaderValue::from_static("libauth-rs/0.1.0"),
148 );
149
150 let mut rb = self.client.request(method.clone(), url).headers(headers);
151
152 if let Some(val) = query {
153 rb = rb.query(&val);
154 }
155
156 if method != Method::GET && method != Method::DELETE {
158 rb = rb.json(&body);
159 }
160
161 let resp = rb.send().await?;
162 Ok(resp)
163 }
164
165 pub async fn list_users(&self, params: ListUsersParams) -> Result<Vec<User>> {
168 let mut query_params = vec![];
169
170 if let Some(limit) = params.limit {
171 query_params.push(("limit", limit.to_string()));
172 }
173 if let Some(offset) = params.offset {
174 query_params.push(("offset", offset.to_string()));
175 }
176 if let Some(email_address) = params.email_address {
177 query_params.push(("email_address", email_address));
178 }
179 if let Some(phone_number) = params.phone_number {
180 query_params.push(("phone_number", phone_number));
181 }
182 if let Some(username) = params.username {
183 query_params.push(("username", username));
184 }
185 if let Some(user_id) = params.user_id {
186 query_params.push(("user_id", user_id));
187 }
188 if let Some(order_by) = params.order_by {
189 query_params.push(("order_by", order_by));
190 }
191
192 let resp = self.request(Method::GET, "users".to_string(), (), Some(query_params)).await?;
193
194 if !resp.status().is_success() {
195 let status = resp.status();
196 let body = resp.text().await?;
197 return Err(ClerkClientError::ApiError { status, message: body });
198 }
199
200 let users: Vec<User> = resp.json().await?;
201 Ok(users)
202 }
203
204 pub async fn get_user(&self, user_id: &str) -> Result<User> {
207 let resp = self.request(Method::GET, format!("users/{}", user_id), (), None).await?;
208
209 if !resp.status().is_success() {
210 let status = resp.status();
211 let body = resp.text().await?;
212 return Err(ClerkClientError::ApiError { status, message: body });
213 }
214
215 let user: User = resp.json().await?;
216 Ok(user)
217 }
218
219 pub async fn create_user(&self, req: CreateUserRequest) -> Result<User> {
222 let resp = self.request(Method::POST, "users".to_string(), req, None).await?;
223
224 if !resp.status().is_success() {
225 let status = resp.status();
226 let body = resp.text().await?;
227 return Err(ClerkClientError::ApiError { status, message: body });
228 }
229
230 let user: User = resp.json().await?;
231 Ok(user)
232 }
233
234 pub async fn update_user(&self, user_id: &str, req: UpdateUserRequest) -> Result<User> {
237 let resp = self.request(Method::PATCH, format!("users/{}", user_id), req, None).await?;
238
239 if !resp.status().is_success() {
240 let status = resp.status();
241 let body = resp.text().await?;
242 return Err(ClerkClientError::ApiError { status, message: body });
243 }
244
245 let user: User = resp.json().await?;
246 Ok(user)
247 }
248
249 pub async fn delete_user(&self, user_id: &str) -> Result<DeletedObject> {
252 let resp = self.request(Method::DELETE, format!("users/{}", user_id), (), None).await?;
253
254 if !resp.status().is_success() {
255 let status = resp.status();
256 let body = resp.text().await?;
257 return Err(ClerkClientError::ApiError { status, message: body });
258 }
259
260 let result: DeletedObject = resp.json().await?;
261 Ok(result)
262 }
263
264 pub async fn list_sessions(&self, params: ListSessionsParams) -> Result<Vec<Session>> {
267 let mut query_params = vec![];
268
269 if let Some(limit) = params.limit {
270 query_params.push(("limit", limit.to_string()));
271 }
272 if let Some(offset) = params.offset {
273 query_params.push(("offset", offset.to_string()));
274 }
275 if let Some(client_id) = params.client_id {
276 query_params.push(("client_id", client_id));
277 }
278 if let Some(user_id) = params.user_id {
279 query_params.push(("user_id", user_id));
280 }
281 if let Some(status) = params.status {
282 query_params.push(("status", status));
283 }
284
285 let resp = self.request(Method::GET, "sessions".to_string(), (), Some(query_params)).await?;
286
287 if !resp.status().is_success() {
288 let status = resp.status();
289 let body = resp.text().await?;
290 return Err(ClerkClientError::ApiError { status, message: body });
291 }
292
293 let sessions: Vec<Session> = resp.json().await?;
294 Ok(sessions)
295 }
296
297 pub async fn get_session(&self, session_id: &str) -> Result<Session> {
300 let resp = self.request(Method::GET, format!("sessions/{}", session_id), (), None).await?;
301
302 if !resp.status().is_success() {
303 let status = resp.status();
304 let body = resp.text().await?;
305 return Err(ClerkClientError::ApiError { status, message: body });
306 }
307
308 let session: Session = resp.json().await?;
309 Ok(session)
310 }
311
312 pub async fn verify_session(&self, session_id: &str) -> Result<Session> {
315 let resp = self.request(Method::POST, format!("sessions/{}/verify", session_id), (), None).await?;
316
317 if !resp.status().is_success() {
318 let status = resp.status();
319 let body = resp.text().await?;
320 return Err(ClerkClientError::ApiError { status, message: body });
321 }
322
323 let session: Session = resp.json().await?;
324 Ok(session)
325 }
326
327 pub async fn revoke_session(&self, session_id: &str) -> Result<Session> {
330 let resp = self.request(Method::POST, format!("sessions/{}/revoke", session_id), (), None).await?;
331
332 if !resp.status().is_success() {
333 let status = resp.status();
334 let body = resp.text().await?;
335 return Err(ClerkClientError::ApiError { status, message: body });
336 }
337
338 let session: Session = resp.json().await?;
339 Ok(session)
340 }
341
342 pub async fn list_organizations(&self, params: ListOrganizationsParams) -> Result<Vec<Organization>> {
345 let mut query_params = vec![];
346
347 if let Some(limit) = params.limit {
348 query_params.push(("limit", limit.to_string()));
349 }
350 if let Some(offset) = params.offset {
351 query_params.push(("offset", offset.to_string()));
352 }
353 if let Some(query) = params.query {
354 query_params.push(("query", query));
355 }
356
357 let resp = self.request(Method::GET, "organizations".to_string(), (), Some(query_params)).await?;
358
359 if !resp.status().is_success() {
360 let status = resp.status();
361 let body = resp.text().await?;
362 return Err(ClerkClientError::ApiError { status, message: body });
363 }
364
365 let orgs: Vec<Organization> = resp.json().await?;
366 Ok(orgs)
367 }
368
369 pub async fn get_organization(&self, organization_id: &str) -> Result<Organization> {
372 let resp = self.request(Method::GET, format!("organizations/{}", organization_id), (), None).await?;
373
374 if !resp.status().is_success() {
375 let status = resp.status();
376 let body = resp.text().await?;
377 return Err(ClerkClientError::ApiError { status, message: body });
378 }
379
380 let org: Organization = resp.json().await?;
381 Ok(org)
382 }
383
384 pub async fn create_organization(&self, req: CreateOrganizationRequest) -> Result<Organization> {
387 let resp = self.request(Method::POST, "organizations".to_string(), req, None).await?;
388
389 if !resp.status().is_success() {
390 let status = resp.status();
391 let body = resp.text().await?;
392 return Err(ClerkClientError::ApiError { status, message: body });
393 }
394
395 let org: Organization = resp.json().await?;
396 Ok(org)
397 }
398
399 pub async fn update_organization(
402 &self,
403 organization_id: &str,
404 req: UpdateOrganizationRequest,
405 ) -> Result<Organization> {
406 let resp = self.request(Method::PATCH, format!("organizations/{}", organization_id), req, None).await?;
407
408 if !resp.status().is_success() {
409 let status = resp.status();
410 let body = resp.text().await?;
411 return Err(ClerkClientError::ApiError { status, message: body });
412 }
413
414 let org: Organization = resp.json().await?;
415 Ok(org)
416 }
417
418 pub async fn delete_organization(&self, organization_id: &str) -> Result<DeletedObject> {
421 let resp = self.request(Method::DELETE, format!("organizations/{}", organization_id), (), None).await?;
422
423 if !resp.status().is_success() {
424 let status = resp.status();
425 let body = resp.text().await?;
426 return Err(ClerkClientError::ApiError { status, message: body });
427 }
428
429 let result: DeletedObject = resp.json().await?;
430 Ok(result)
431 }
432}
433
434#[derive(Debug, Default, Clone)]
440pub struct ListUsersParams {
441 pub limit: Option<u32>,
442 pub offset: Option<u32>,
443 pub email_address: Option<String>,
444 pub phone_number: Option<String>,
445 pub username: Option<String>,
446 pub user_id: Option<String>,
447 pub order_by: Option<String>,
448}
449
450#[derive(Debug, Default, Clone, Serialize, Deserialize)]
452pub struct CreateUserRequest {
453 #[serde(skip_serializing_if = "Option::is_none")]
454 pub email_address: Option<Vec<String>>,
455 #[serde(skip_serializing_if = "Option::is_none")]
456 pub phone_number: Option<Vec<String>>,
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub username: Option<String>,
459 #[serde(skip_serializing_if = "Option::is_none")]
460 pub password: Option<String>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 pub first_name: Option<String>,
463 #[serde(skip_serializing_if = "Option::is_none")]
464 pub last_name: Option<String>,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 pub external_id: Option<String>,
467 #[serde(skip_serializing_if = "Option::is_none")]
468 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
469 #[serde(skip_serializing_if = "Option::is_none")]
470 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
471 #[serde(skip_serializing_if = "Option::is_none")]
472 pub unsafe_metadata: Option<HashMap<String, serde_json::Value>>,
473}
474
475#[derive(Debug, Default, Clone, Serialize, Deserialize)]
477pub struct UpdateUserRequest {
478 #[serde(skip_serializing_if = "Option::is_none")]
479 pub first_name: Option<String>,
480 #[serde(skip_serializing_if = "Option::is_none")]
481 pub last_name: Option<String>,
482 #[serde(skip_serializing_if = "Option::is_none")]
483 pub username: Option<String>,
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub primary_email_address_id: Option<String>,
486 #[serde(skip_serializing_if = "Option::is_none")]
487 pub primary_phone_number_id: Option<String>,
488 #[serde(skip_serializing_if = "Option::is_none")]
489 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
490 #[serde(skip_serializing_if = "Option::is_none")]
491 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
492 #[serde(skip_serializing_if = "Option::is_none")]
493 pub unsafe_metadata: Option<HashMap<String, serde_json::Value>>,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct User {
499 pub id: String,
500 #[serde(default, skip_serializing_if = "Option::is_none")]
501 pub object: Option<String>,
502 #[serde(default, skip_serializing_if = "Option::is_none")]
503 pub username: Option<String>,
504 #[serde(default, skip_serializing_if = "Option::is_none")]
505 pub first_name: Option<String>,
506 #[serde(default, skip_serializing_if = "Option::is_none")]
507 pub last_name: Option<String>,
508 #[serde(default, skip_serializing_if = "Option::is_none")]
509 pub image_url: Option<String>,
510 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub has_image: Option<bool>,
512 #[serde(default, skip_serializing_if = "Option::is_none")]
513 pub primary_email_address_id: Option<String>,
514 #[serde(default, skip_serializing_if = "Option::is_none")]
515 pub primary_phone_number_id: Option<String>,
516 #[serde(default, skip_serializing_if = "Option::is_none")]
517 pub primary_web3_wallet_id: Option<String>,
518 #[serde(default)]
519 pub email_addresses: Vec<EmailAddress>,
520 #[serde(default)]
521 pub phone_numbers: Vec<PhoneNumber>,
522 #[serde(default)]
523 pub web3_wallets: Vec<Web3Wallet>,
524 #[serde(default, skip_serializing_if = "Option::is_none")]
525 pub external_id: Option<String>,
526 #[serde(default)]
527 pub public_metadata: HashMap<String, serde_json::Value>,
528 #[serde(default)]
529 pub private_metadata: HashMap<String, serde_json::Value>,
530 #[serde(default)]
531 pub unsafe_metadata: HashMap<String, serde_json::Value>,
532 pub created_at: i64,
533 pub updated_at: i64,
534 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub last_sign_in_at: Option<i64>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
537 pub banned: Option<bool>,
538 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub locked: Option<bool>,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct EmailAddress {
545 pub id: String,
546 #[serde(default, skip_serializing_if = "Option::is_none")]
547 pub object: Option<String>,
548 pub email_address: String,
549 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub verification: Option<Verification>,
551 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub linked_to: Option<Vec<LinkedIdentity>>,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct PhoneNumber {
558 pub id: String,
559 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub object: Option<String>,
561 pub phone_number: String,
562 #[serde(default, skip_serializing_if = "Option::is_none")]
563 pub verification: Option<Verification>,
564 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub linked_to: Option<Vec<LinkedIdentity>>,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct Web3Wallet {
571 pub id: String,
572 #[serde(default, skip_serializing_if = "Option::is_none")]
573 pub object: Option<String>,
574 pub web3_wallet: String,
575 #[serde(default, skip_serializing_if = "Option::is_none")]
576 pub verification: Option<Verification>,
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct Verification {
582 pub status: String,
583 #[serde(default, skip_serializing_if = "Option::is_none")]
584 pub strategy: Option<String>,
585 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub attempts: Option<i32>,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
588 pub expire_at: Option<i64>,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct LinkedIdentity {
594 pub id: String,
595 #[serde(rename = "type")]
596 pub identity_type: String,
597}
598
599#[derive(Debug, Default, Clone)]
605pub struct ListSessionsParams {
606 pub limit: Option<u32>,
607 pub offset: Option<u32>,
608 pub client_id: Option<String>,
609 pub user_id: Option<String>,
610 pub status: Option<String>,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct Session {
616 pub id: String,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
618 pub object: Option<String>,
619 pub client_id: String,
620 pub user_id: String,
621 pub status: String,
622 #[serde(default, skip_serializing_if = "Option::is_none")]
623 pub last_active_at: Option<i64>,
624 #[serde(default, skip_serializing_if = "Option::is_none")]
625 pub expire_at: Option<i64>,
626 #[serde(default, skip_serializing_if = "Option::is_none")]
627 pub abandon_at: Option<i64>,
628 pub created_at: i64,
629 pub updated_at: i64,
630}
631
632#[derive(Debug, Default, Clone)]
638pub struct ListOrganizationsParams {
639 pub limit: Option<u32>,
640 pub offset: Option<u32>,
641 pub query: Option<String>,
642}
643
644#[derive(Debug, Default, Clone, Serialize, Deserialize)]
646pub struct CreateOrganizationRequest {
647 pub name: String,
648 #[serde(skip_serializing_if = "Option::is_none")]
649 pub slug: Option<String>,
650 #[serde(skip_serializing_if = "Option::is_none")]
651 pub created_by: Option<String>,
652 #[serde(skip_serializing_if = "Option::is_none")]
653 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
654 #[serde(skip_serializing_if = "Option::is_none")]
655 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
656 #[serde(skip_serializing_if = "Option::is_none")]
657 pub max_allowed_memberships: Option<i32>,
658}
659
660#[derive(Debug, Default, Clone, Serialize, Deserialize)]
662pub struct UpdateOrganizationRequest {
663 #[serde(skip_serializing_if = "Option::is_none")]
664 pub name: Option<String>,
665 #[serde(skip_serializing_if = "Option::is_none")]
666 pub slug: Option<String>,
667 #[serde(skip_serializing_if = "Option::is_none")]
668 pub public_metadata: Option<HashMap<String, serde_json::Value>>,
669 #[serde(skip_serializing_if = "Option::is_none")]
670 pub private_metadata: Option<HashMap<String, serde_json::Value>>,
671 #[serde(skip_serializing_if = "Option::is_none")]
672 pub max_allowed_memberships: Option<i32>,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize)]
677pub struct Organization {
678 pub id: String,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
680 pub object: Option<String>,
681 pub name: String,
682 pub slug: String,
683 #[serde(default, skip_serializing_if = "Option::is_none")]
684 pub image_url: Option<String>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
686 pub has_image: Option<bool>,
687 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub members_count: Option<i32>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub pending_invitations_count: Option<i32>,
691 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub max_allowed_memberships: Option<i32>,
693 #[serde(default)]
694 pub public_metadata: HashMap<String, serde_json::Value>,
695 #[serde(default)]
696 pub private_metadata: HashMap<String, serde_json::Value>,
697 pub created_at: i64,
698 pub updated_at: i64,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
707pub struct DeletedObject {
708 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub object: Option<String>,
710 pub id: String,
711 #[serde(default, skip_serializing_if = "Option::is_none")]
712 pub deleted: Option<bool>,
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize)]
717pub struct ClerkError {
718 #[serde(default)]
719 pub errors: Vec<ClerkErrorDetail>,
720 #[serde(default, skip_serializing_if = "Option::is_none")]
721 pub clerk_trace_id: Option<String>,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize)]
726pub struct ClerkErrorDetail {
727 pub message: String,
728 #[serde(default, skip_serializing_if = "Option::is_none")]
729 pub long_message: Option<String>,
730 pub code: String,
731 #[serde(default, skip_serializing_if = "Option::is_none")]
732 pub meta: Option<HashMap<String, serde_json::Value>>,
733}