claude_api/admin/
workspace_members.rs1use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7use crate::pagination::Paginated;
8
9use super::{ListParams, WorkspaceRole, WriteWorkspaceRole};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct WorkspaceMember {
15 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
17 pub ty: Option<String>,
18 pub user_id: String,
20 pub workspace_id: String,
22 pub workspace_role: WorkspaceRole,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
29#[non_exhaustive]
30pub struct WorkspaceMemberDeleted {
31 pub user_id: String,
33 pub workspace_id: String,
35 #[serde(rename = "type")]
37 pub ty: String,
38}
39
40#[derive(Debug, Clone, Serialize)]
42#[non_exhaustive]
43pub struct CreateWorkspaceMemberRequest {
44 pub user_id: String,
46 pub workspace_role: WriteWorkspaceRole,
48}
49
50impl CreateWorkspaceMemberRequest {
51 #[must_use]
53 pub fn new(user_id: impl Into<String>, role: WriteWorkspaceRole) -> Self {
54 Self {
55 user_id: user_id.into(),
56 workspace_role: role,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize)]
64#[non_exhaustive]
65pub struct UpdateWorkspaceMemberRequest {
66 pub workspace_role: WorkspaceRole,
69}
70
71impl UpdateWorkspaceMemberRequest {
72 #[must_use]
74 pub fn new(role: WorkspaceRole) -> Self {
75 Self {
76 workspace_role: role,
77 }
78 }
79}
80
81pub struct WorkspaceMembers<'a> {
84 client: &'a Client,
85 workspace_id: String,
86}
87
88impl<'a> WorkspaceMembers<'a> {
89 pub(crate) fn new(client: &'a Client, workspace_id: String) -> Self {
90 Self {
91 client,
92 workspace_id,
93 }
94 }
95
96 pub async fn create(&self, request: CreateWorkspaceMemberRequest) -> Result<WorkspaceMember> {
98 let path = format!("/v1/organizations/workspaces/{}/members", self.workspace_id);
99 let body = &request;
100 self.client
101 .execute_with_retry(
102 || {
103 self.client
104 .request_builder(reqwest::Method::POST, &path)
105 .json(body)
106 },
107 &[],
108 )
109 .await
110 }
111
112 pub async fn retrieve(&self, user_id: &str) -> Result<WorkspaceMember> {
114 let path = format!(
115 "/v1/organizations/workspaces/{}/members/{user_id}",
116 self.workspace_id
117 );
118 self.client
119 .execute_with_retry(
120 || self.client.request_builder(reqwest::Method::GET, &path),
121 &[],
122 )
123 .await
124 }
125
126 pub async fn list(&self, params: ListParams) -> Result<Paginated<WorkspaceMember>> {
128 let path = format!("/v1/organizations/workspaces/{}/members", self.workspace_id);
129 let query = params.to_query();
130 self.client
131 .execute_with_retry(
132 || {
133 let mut req = self.client.request_builder(reqwest::Method::GET, &path);
134 for (k, v) in &query {
135 req = req.query(&[(k, v)]);
136 }
137 req
138 },
139 &[],
140 )
141 .await
142 }
143
144 pub async fn update(
146 &self,
147 user_id: &str,
148 request: UpdateWorkspaceMemberRequest,
149 ) -> Result<WorkspaceMember> {
150 let path = format!(
151 "/v1/organizations/workspaces/{}/members/{user_id}",
152 self.workspace_id
153 );
154 let body = &request;
155 self.client
156 .execute_with_retry(
157 || {
158 self.client
159 .request_builder(reqwest::Method::POST, &path)
160 .json(body)
161 },
162 &[],
163 )
164 .await
165 }
166
167 pub async fn delete(&self, user_id: &str) -> Result<WorkspaceMemberDeleted> {
169 let path = format!(
170 "/v1/organizations/workspaces/{}/members/{user_id}",
171 self.workspace_id
172 );
173 self.client
174 .execute_with_retry(
175 || self.client.request_builder(reqwest::Method::DELETE, &path),
176 &[],
177 )
178 .await
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use serde_json::json;
186 use wiremock::matchers::{body_partial_json, method, path};
187 use wiremock::{Mock, MockServer, ResponseTemplate};
188
189 fn client_for(mock: &MockServer) -> Client {
190 Client::builder()
191 .api_key("sk-ant-admin-test")
192 .base_url(mock.uri())
193 .build()
194 .unwrap()
195 }
196
197 fn fake_member() -> serde_json::Value {
198 json!({
199 "type": "workspace_member",
200 "user_id": "user_01",
201 "workspace_id": "ws_01",
202 "workspace_role": "workspace_user"
203 })
204 }
205
206 #[tokio::test]
207 async fn create_workspace_member_posts_user_id_and_role() {
208 let mock = MockServer::start().await;
209 Mock::given(method("POST"))
210 .and(path("/v1/organizations/workspaces/ws_01/members"))
211 .and(body_partial_json(json!({
212 "user_id": "user_01",
213 "workspace_role": "workspace_admin"
214 })))
215 .respond_with(ResponseTemplate::new(200).set_body_json(fake_member()))
216 .mount(&mock)
217 .await;
218 let client = client_for(&mock);
219 client
220 .admin()
221 .workspace_members("ws_01")
222 .create(CreateWorkspaceMemberRequest::new(
223 "user_01",
224 WriteWorkspaceRole::WorkspaceAdmin,
225 ))
226 .await
227 .unwrap();
228 }
229
230 #[tokio::test]
231 async fn retrieve_workspace_member_returns_role() {
232 let mock = MockServer::start().await;
233 Mock::given(method("GET"))
234 .and(path("/v1/organizations/workspaces/ws_01/members/user_01"))
235 .respond_with(ResponseTemplate::new(200).set_body_json(fake_member()))
236 .mount(&mock)
237 .await;
238 let client = client_for(&mock);
239 let m = client
240 .admin()
241 .workspace_members("ws_01")
242 .retrieve("user_01")
243 .await
244 .unwrap();
245 assert!(matches!(m.workspace_role, WorkspaceRole::User));
246 }
247
248 #[tokio::test]
249 async fn list_workspace_members_paginates() {
250 let mock = MockServer::start().await;
251 Mock::given(method("GET"))
252 .and(path("/v1/organizations/workspaces/ws_01/members"))
253 .and(wiremock::matchers::query_param("limit", "5"))
254 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
255 "data": [fake_member()],
256 "has_more": false,
257 "first_id": "user_01",
258 "last_id": "user_01"
259 })))
260 .mount(&mock)
261 .await;
262 let client = client_for(&mock);
263 let page = client
264 .admin()
265 .workspace_members("ws_01")
266 .list(ListParams {
267 limit: Some(5),
268 ..Default::default()
269 })
270 .await
271 .unwrap();
272 assert_eq!(page.data.len(), 1);
273 }
274
275 #[tokio::test]
276 async fn update_workspace_member_changes_role() {
277 let mock = MockServer::start().await;
278 Mock::given(method("POST"))
279 .and(path("/v1/organizations/workspaces/ws_01/members/user_01"))
280 .and(body_partial_json(json!({
281 "workspace_role": "workspace_developer"
282 })))
283 .respond_with(ResponseTemplate::new(200).set_body_json(fake_member()))
284 .mount(&mock)
285 .await;
286 let client = client_for(&mock);
287 client
288 .admin()
289 .workspace_members("ws_01")
290 .update(
291 "user_01",
292 UpdateWorkspaceMemberRequest::new(WorkspaceRole::Developer),
293 )
294 .await
295 .unwrap();
296 }
297
298 #[tokio::test]
299 async fn delete_workspace_member_returns_deleted_marker() {
300 let mock = MockServer::start().await;
301 Mock::given(method("DELETE"))
302 .and(path("/v1/organizations/workspaces/ws_01/members/user_01"))
303 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
304 "type": "workspace_member_deleted",
305 "user_id": "user_01",
306 "workspace_id": "ws_01"
307 })))
308 .mount(&mock)
309 .await;
310 let client = client_for(&mock);
311 let r = client
312 .admin()
313 .workspace_members("ws_01")
314 .delete("user_01")
315 .await
316 .unwrap();
317 assert_eq!(r.ty, "workspace_member_deleted");
318 }
319}