claude_api/admin/
users.rs1use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7use crate::pagination::Paginated;
8
9use super::{ListParams, OrganizationRole, WriteOrganizationRole};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct User {
15 pub id: String,
17 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
19 pub ty: Option<String>,
20 pub email: String,
22 pub name: String,
24 pub role: OrganizationRole,
26 pub added_at: String,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32#[non_exhaustive]
33pub struct UserDeleted {
34 pub id: String,
36 #[serde(rename = "type")]
38 pub ty: String,
39}
40
41#[derive(Debug, Clone, Serialize)]
43#[non_exhaustive]
44pub struct UpdateUserRequest {
45 pub role: WriteOrganizationRole,
47}
48
49impl UpdateUserRequest {
50 #[must_use]
52 pub fn new(role: WriteOrganizationRole) -> Self {
53 Self { role }
54 }
55}
56
57#[derive(Debug, Clone, Default)]
59#[non_exhaustive]
60pub struct ListUsersParams {
61 pub paging: ListParams,
63 pub email: Option<String>,
65}
66
67impl ListUsersParams {
68 fn to_query(&self) -> Vec<(&'static str, String)> {
69 let mut q = self.paging.to_query();
70 if let Some(e) = &self.email {
71 q.push(("email", e.clone()));
72 }
73 q
74 }
75}
76
77pub struct Users<'a> {
79 client: &'a Client,
80}
81
82impl<'a> Users<'a> {
83 pub(crate) fn new(client: &'a Client) -> Self {
84 Self { client }
85 }
86
87 pub async fn retrieve(&self, user_id: &str) -> Result<User> {
89 let path = format!("/v1/organizations/users/{user_id}");
90 self.client
91 .execute_with_retry(
92 || self.client.request_builder(reqwest::Method::GET, &path),
93 &[],
94 )
95 .await
96 }
97
98 pub async fn list(&self, params: ListUsersParams) -> Result<Paginated<User>> {
100 let query = params.to_query();
101 self.client
102 .execute_with_retry(
103 || {
104 let mut req = self
105 .client
106 .request_builder(reqwest::Method::GET, "/v1/organizations/users");
107 for (k, v) in &query {
108 req = req.query(&[(k, v)]);
109 }
110 req
111 },
112 &[],
113 )
114 .await
115 }
116
117 pub async fn update(&self, user_id: &str, request: UpdateUserRequest) -> Result<User> {
119 let path = format!("/v1/organizations/users/{user_id}");
120 let body = &request;
121 self.client
122 .execute_with_retry(
123 || {
124 self.client
125 .request_builder(reqwest::Method::POST, &path)
126 .json(body)
127 },
128 &[],
129 )
130 .await
131 }
132
133 pub async fn delete(&self, user_id: &str) -> Result<UserDeleted> {
135 let path = format!("/v1/organizations/users/{user_id}");
136 self.client
137 .execute_with_retry(
138 || self.client.request_builder(reqwest::Method::DELETE, &path),
139 &[],
140 )
141 .await
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use serde_json::json;
149 use wiremock::matchers::{body_partial_json, method, path};
150 use wiremock::{Mock, MockServer, ResponseTemplate};
151
152 fn client_for(mock: &MockServer) -> Client {
153 Client::builder()
154 .api_key("sk-ant-admin-test")
155 .base_url(mock.uri())
156 .build()
157 .unwrap()
158 }
159
160 fn fake_user() -> serde_json::Value {
161 json!({
162 "id": "user_01",
163 "type": "user",
164 "email": "u@example.com",
165 "name": "User",
166 "role": "developer",
167 "added_at": "2026-05-01T00:00:00Z"
168 })
169 }
170
171 #[tokio::test]
172 async fn retrieve_user_returns_typed_record() {
173 let mock = MockServer::start().await;
174 Mock::given(method("GET"))
175 .and(path("/v1/organizations/users/user_01"))
176 .respond_with(ResponseTemplate::new(200).set_body_json(fake_user()))
177 .mount(&mock)
178 .await;
179 let client = client_for(&mock);
180 let u = client.admin().users().retrieve("user_01").await.unwrap();
181 assert_eq!(u.email, "u@example.com");
182 }
183
184 #[tokio::test]
185 async fn list_users_filters_by_email() {
186 let mock = MockServer::start().await;
187 Mock::given(method("GET"))
188 .and(path("/v1/organizations/users"))
189 .and(wiremock::matchers::query_param("email", "u@example.com"))
190 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
191 "data": [fake_user()],
192 "has_more": false,
193 "first_id": "user_01",
194 "last_id": "user_01"
195 })))
196 .mount(&mock)
197 .await;
198 let client = client_for(&mock);
199 let page = client
200 .admin()
201 .users()
202 .list(ListUsersParams {
203 email: Some("u@example.com".into()),
204 ..Default::default()
205 })
206 .await
207 .unwrap();
208 assert_eq!(page.data.len(), 1);
209 }
210
211 #[tokio::test]
212 async fn update_user_role_sends_role_body() {
213 let mock = MockServer::start().await;
214 Mock::given(method("POST"))
215 .and(path("/v1/organizations/users/user_01"))
216 .and(body_partial_json(json!({"role": "user"})))
217 .respond_with(ResponseTemplate::new(200).set_body_json(fake_user()))
218 .mount(&mock)
219 .await;
220 let client = client_for(&mock);
221 client
222 .admin()
223 .users()
224 .update(
225 "user_01",
226 UpdateUserRequest::new(WriteOrganizationRole::User),
227 )
228 .await
229 .unwrap();
230 }
231
232 #[tokio::test]
233 async fn delete_user_returns_deleted_marker() {
234 let mock = MockServer::start().await;
235 Mock::given(method("DELETE"))
236 .and(path("/v1/organizations/users/user_01"))
237 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
238 "id": "user_01",
239 "type": "user_deleted"
240 })))
241 .mount(&mock)
242 .await;
243 let client = client_for(&mock);
244 let r = client.admin().users().delete("user_01").await.unwrap();
245 assert_eq!(r.ty, "user_deleted");
246 }
247}