1use grpc::heddle::v1::{
2 ApproveThreadRequest, BeginWebAuthnAuthenticationRequest, CheckMergeEligibilityRequest,
3 CheckMergeEligibilityResponse, CreateGrantRequest, CreateInvitationRequest,
4 CreateRepositoryRequest, DeleteGrantRequest, DeleteNamespaceRequest, DeleteRepositoryRequest,
5 GetCurrentUserNamespaceRequest, GrantSupportAccessRequest, GrantTargetRef,
6 Invitation as ProtoInvitation, ListGrantsRequest, ListNamespacesRequest,
7 ListRepositoriesRequest, ListSupportAccessGrantsRequest, ListThreadApprovalsRequest,
8 RevokeApprovalRequest, RevokeSupportAccessRequest, SupportAccessGrant, ThreadApproval,
9 UpdateGrantRequest, UpdateNamespaceRequest, UpdateRepositoryRequest,
10 grant_target_ref::Target as GrantTargetKind,
11};
12use proto::ProtocolError;
13use tonic::Request;
14
15use super::{
16 HostedGrpcClient,
17 helpers::{
18 status_to_protocol_error, to_protocol_grant, to_protocol_namespace, to_protocol_repository,
19 },
20};
21
22impl HostedGrpcClient {
23 pub async fn begin_login(
24 &mut self,
25 username: &str,
26 ) -> Result<(String, String, u64), ProtocolError> {
27 let request = Request::new(BeginWebAuthnAuthenticationRequest {
28 username: username.to_string(),
29 });
30 let response = self
31 .auth
32 .begin_web_authn_authentication(request)
33 .await
34 .map_err(status_to_protocol_error)?
35 .into_inner();
36 let expires_at_secs = response
37 .expires_at
38 .as_ref()
39 .map(|t| t.seconds.max(0) as u64)
40 .unwrap_or(0);
41 Ok((response.challenge_id, response.challenge, expires_at_secs))
42 }
43
44 pub async fn get_current_user_namespace(
45 &mut self,
46 ) -> Result<proto::HostedNamespaceInfo, ProtocolError> {
47 let mut request = Request::new(GetCurrentUserNamespaceRequest {});
48 self.apply_auth(&mut request)?;
49 let namespace = self
50 .user
51 .get_current_user_namespace(request)
52 .await
53 .map_err(status_to_protocol_error)?
54 .into_inner();
55 Ok(to_protocol_namespace(namespace))
56 }
57
58 pub async fn list_namespaces(
59 &mut self,
60 ) -> Result<Vec<proto::HostedNamespaceInfo>, ProtocolError> {
61 let mut request = Request::new(ListNamespacesRequest {});
62 self.apply_auth(&mut request)?;
63 let response = self
64 .user
65 .list_namespaces(request)
66 .await
67 .map_err(status_to_protocol_error)?
68 .into_inner();
69 Ok(response
70 .namespaces
71 .into_iter()
72 .map(to_protocol_namespace)
73 .collect())
74 }
75
76 pub async fn create_namespace(
77 &mut self,
78 kind: &str,
79 slug: &str,
80 parent_path: Option<&str>,
81 display_name: Option<String>,
82 ) -> Result<proto::HostedNamespaceInfo, ProtocolError> {
83 let mut request = Request::new(grpc::heddle::v1::CreateNamespaceRequest {
84 kind: parse_namespace_kind_arg(kind)? as i32,
85 slug: slug.to_string(),
86 parent_path: parent_path.unwrap_or_default().to_string(),
87 display_name: display_name.unwrap_or_default(),
88 client_operation_id: String::new(),
89 });
90 self.apply_auth(&mut request)?;
91 let namespace = self
92 .user
93 .create_namespace(request)
94 .await
95 .map_err(status_to_protocol_error)?
96 .into_inner();
97 Ok(to_protocol_namespace(namespace))
98 }
99
100 pub async fn create_repository(
101 &mut self,
102 namespace_path: &str,
103 slug: &str,
104 ) -> Result<proto::HostedRepositoryInfo, ProtocolError> {
105 let mut request = Request::new(CreateRepositoryRequest {
106 namespace_path: namespace_path.to_string(),
107 slug: slug.to_string(),
108 client_operation_id: String::new(),
109 });
110 self.apply_auth(&mut request)?;
111 let repo = self
112 .user
113 .create_repository(request)
114 .await
115 .map_err(status_to_protocol_error)?
116 .into_inner();
117 Ok(to_protocol_repository(repo))
118 }
119
120 pub async fn list_repositories(
121 &mut self,
122 namespace_path: Option<&str>,
123 ) -> Result<Vec<proto::HostedRepositoryInfo>, ProtocolError> {
124 let mut request = Request::new(ListRepositoriesRequest {
125 namespace_path: namespace_path.unwrap_or_default().to_string(),
126 });
127 self.apply_auth(&mut request)?;
128 let response = self
129 .user
130 .list_repositories(request)
131 .await
132 .map_err(status_to_protocol_error)?
133 .into_inner();
134 Ok(response
135 .repositories
136 .into_iter()
137 .map(to_protocol_repository)
138 .collect())
139 }
140
141 pub async fn update_namespace(
142 &mut self,
143 full_path: &str,
144 new_slug: Option<&str>,
145 display_name: Option<Option<String>>,
146 ) -> Result<proto::HostedNamespaceInfo, ProtocolError> {
147 let (display_name, clear_display_name) = match display_name {
148 Some(Some(value)) => (value, false),
149 Some(None) => (String::new(), true),
150 None => (String::new(), false),
151 };
152 let mut request = Request::new(UpdateNamespaceRequest {
153 full_path: full_path.to_string(),
154 new_slug: new_slug.unwrap_or_default().to_string(),
155 display_name,
156 clear_display_name,
157 client_operation_id: String::new(),
158 });
159 self.apply_auth(&mut request)?;
160 let namespace = self
161 .user
162 .update_namespace(request)
163 .await
164 .map_err(status_to_protocol_error)?
165 .into_inner();
166 Ok(to_protocol_namespace(namespace))
167 }
168
169 pub async fn delete_namespace(&mut self, full_path: &str) -> Result<(), ProtocolError> {
170 let mut request = Request::new(DeleteNamespaceRequest {
171 full_path: full_path.to_string(),
172 client_operation_id: String::new(),
173 });
174 self.apply_auth(&mut request)?;
175 self.user
176 .delete_namespace(request)
177 .await
178 .map_err(status_to_protocol_error)?;
179 Ok(())
180 }
181
182 pub async fn update_repository(
183 &mut self,
184 full_path: &str,
185 new_slug: &str,
186 ) -> Result<proto::HostedRepositoryInfo, ProtocolError> {
187 let mut request = Request::new(UpdateRepositoryRequest {
188 full_path: full_path.to_string(),
189 new_slug: new_slug.to_string(),
190 client_operation_id: String::new(),
191 });
192 self.apply_auth(&mut request)?;
193 let repo = self
194 .user
195 .update_repository(request)
196 .await
197 .map_err(status_to_protocol_error)?
198 .into_inner();
199 Ok(to_protocol_repository(repo))
200 }
201
202 pub async fn delete_repository(&mut self, full_path: &str) -> Result<(), ProtocolError> {
203 let mut request = Request::new(DeleteRepositoryRequest {
204 full_path: full_path.to_string(),
205 client_operation_id: String::new(),
206 });
207 self.apply_auth(&mut request)?;
208 self.user
209 .delete_repository(request)
210 .await
211 .map_err(status_to_protocol_error)?;
212 Ok(())
213 }
214
215 pub async fn create_grant(
216 &mut self,
217 subject: &str,
218 role: &str,
219 namespace_path: Option<&str>,
220 repo_path: Option<&str>,
221 ) -> Result<proto::HostedGrantInfo, ProtocolError> {
222 let target = build_target_ref(namespace_path, repo_path)?;
223 let mut request = Request::new(CreateGrantRequest {
224 subject: subject.to_string(),
225 role: parse_hosted_role_arg(role)? as i32,
226 target,
227 client_operation_id: String::new(),
228 });
229 self.apply_auth(&mut request)?;
230 let grant = self
231 .user
232 .create_grant(request)
233 .await
234 .map_err(status_to_protocol_error)?
235 .into_inner();
236 Ok(to_protocol_grant(grant))
237 }
238
239 pub async fn list_grants(
240 &mut self,
241 resource: Option<&str>,
242 ) -> Result<Vec<proto::HostedGrantInfo>, ProtocolError> {
243 let mut request = Request::new(ListGrantsRequest {
244 resource: resource.unwrap_or_default().to_string(),
245 });
246 self.apply_auth(&mut request)?;
247 let response = self
248 .user
249 .list_grants(request)
250 .await
251 .map_err(status_to_protocol_error)?
252 .into_inner();
253 Ok(response.grants.into_iter().map(to_protocol_grant).collect())
254 }
255
256 pub async fn update_grant(
257 &mut self,
258 subject: &str,
259 role: &str,
260 namespace_path: Option<&str>,
261 repo_path: Option<&str>,
262 ) -> Result<proto::HostedGrantInfo, ProtocolError> {
263 let target = build_target_ref(namespace_path, repo_path)?;
264 let mut request = Request::new(UpdateGrantRequest {
265 subject: subject.to_string(),
266 role: parse_hosted_role_arg(role)? as i32,
267 target,
268 client_operation_id: String::new(),
269 });
270 self.apply_auth(&mut request)?;
271 let grant = self
272 .user
273 .update_grant(request)
274 .await
275 .map_err(status_to_protocol_error)?
276 .into_inner();
277 Ok(to_protocol_grant(grant))
278 }
279
280 pub async fn delete_grant(
281 &mut self,
282 subject: &str,
283 namespace_path: Option<&str>,
284 repo_path: Option<&str>,
285 ) -> Result<(), ProtocolError> {
286 let target = build_target_ref(namespace_path, repo_path)?;
287 let mut request = Request::new(DeleteGrantRequest {
288 subject: subject.to_string(),
289 target,
290 client_operation_id: String::new(),
291 });
292 self.apply_auth(&mut request)?;
293 self.user
294 .delete_grant(request)
295 .await
296 .map_err(status_to_protocol_error)?;
297 Ok(())
298 }
299
300 pub async fn create_invitation(
303 &mut self,
304 email: &str,
305 namespace_path: &str,
306 role: &str,
307 ) -> Result<ProtoInvitation, ProtocolError> {
308 let mut request = Request::new(CreateInvitationRequest {
309 email: email.to_string(),
310 namespace_path: namespace_path.to_string(),
311 role: parse_hosted_role_arg(role)? as i32,
312 expires_at: None,
313 metadata: String::new(),
314 client_operation_id: String::new(),
315 });
316 self.apply_auth(&mut request)?;
317 let invitation = self
318 .user
319 .create_invitation(request)
320 .await
321 .map_err(status_to_protocol_error)?
322 .into_inner();
323 Ok(invitation)
324 }
325
326 pub async fn approve_thread(
331 &mut self,
332 repo_path: &str,
333 source_thread: &str,
334 target_thread: &str,
335 source_state: &str,
336 note: Option<&str>,
337 ) -> Result<ThreadApproval, ProtocolError> {
338 let mut request = Request::new(ApproveThreadRequest {
339 repo_path: repo_path.to_string(),
340 source_thread: source_thread.to_string(),
341 target_thread: target_thread.to_string(),
342 source_state: objects::object::ChangeId::parse(source_state)
343 .map(|id| id.as_bytes().to_vec())
344 .unwrap_or_default(),
345 note: note.unwrap_or_default().to_string(),
346 client_operation_id: String::new(),
347 });
348 self.apply_auth(&mut request)?;
349 Ok(self
350 .user
351 .approve_thread(request)
352 .await
353 .map_err(status_to_protocol_error)?
354 .into_inner())
355 }
356
357 pub async fn revoke_approval(&mut self, id: &str) -> Result<(), ProtocolError> {
358 let mut request = Request::new(RevokeApprovalRequest {
359 id: id.to_string(),
360 client_operation_id: String::new(),
361 });
362 self.apply_auth(&mut request)?;
363 self.user
364 .revoke_approval(request)
365 .await
366 .map_err(status_to_protocol_error)?;
367 Ok(())
368 }
369
370 pub async fn list_thread_approvals(
371 &mut self,
372 repo_path: &str,
373 source_thread: &str,
374 target_thread: &str,
375 ) -> Result<Vec<ThreadApproval>, ProtocolError> {
376 let mut request = Request::new(ListThreadApprovalsRequest {
377 repo_path: repo_path.to_string(),
378 source_thread: source_thread.to_string(),
379 target_thread: target_thread.to_string(),
380 });
381 self.apply_auth(&mut request)?;
382 Ok(self
383 .user
384 .list_thread_approvals(request)
385 .await
386 .map_err(status_to_protocol_error)?
387 .into_inner()
388 .approvals)
389 }
390
391 #[allow(clippy::too_many_arguments)]
396 pub async fn check_merge_eligibility(
397 &mut self,
398 repo_path: &str,
399 source_thread: &str,
400 target_thread: &str,
401 source_state: &str,
402 gated_action: &str,
403 changed_paths: Vec<String>,
404 author_user_id: Option<&str>,
405 ) -> Result<CheckMergeEligibilityResponse, ProtocolError> {
406 let mut request = Request::new(CheckMergeEligibilityRequest {
407 repo_path: repo_path.to_string(),
408 source_thread: source_thread.to_string(),
409 target_thread: target_thread.to_string(),
410 source_state: objects::object::ChangeId::parse(source_state)
411 .map(|id| id.as_bytes().to_vec())
412 .unwrap_or_default(),
413 gated_action: gated_action.to_string(),
414 changed_paths,
415 author_user_id: author_user_id.unwrap_or_default().to_string(),
416 });
417 self.apply_auth(&mut request)?;
418 Ok(self
419 .user
420 .check_merge_eligibility(request)
421 .await
422 .map_err(status_to_protocol_error)?
423 .into_inner())
424 }
425
426 pub async fn grant_support_access(
430 &mut self,
431 operator_email: &str,
432 namespace_path: Option<&str>,
433 repo_path: Option<&str>,
434 ttl_seconds: u32,
435 reason: &str,
436 client_operation_id: String,
437 ) -> Result<SupportAccessGrant, ProtocolError> {
438 let target = build_target_ref(namespace_path, repo_path)?;
439 let mut request = Request::new(GrantSupportAccessRequest {
440 operator_email: operator_email.to_string(),
441 target,
442 ttl_seconds: Some(prost_types::Duration {
443 seconds: i64::from(ttl_seconds),
444 nanos: 0,
445 }),
446 reason: reason.to_string(),
447 client_operation_id,
448 });
449 self.apply_auth(&mut request)?;
450 Ok(self
451 .user
452 .grant_support_access(request)
453 .await
454 .map_err(status_to_protocol_error)?
455 .into_inner())
456 }
457
458 pub async fn list_support_access_grants(
459 &mut self,
460 namespace_path: Option<&str>,
461 repo_path: Option<&str>,
462 include_inactive: bool,
463 ) -> Result<Vec<SupportAccessGrant>, ProtocolError> {
464 let target = build_target_ref(namespace_path, repo_path)?;
465 let mut request = Request::new(ListSupportAccessGrantsRequest {
466 target,
467 include_inactive,
468 });
469 self.apply_auth(&mut request)?;
470 Ok(self
471 .user
472 .list_support_access_grants(request)
473 .await
474 .map_err(status_to_protocol_error)?
475 .into_inner()
476 .grants)
477 }
478
479 pub async fn revoke_support_access(
480 &mut self,
481 id: &str,
482 client_operation_id: String,
483 ) -> Result<(), ProtocolError> {
484 let mut request = Request::new(RevokeSupportAccessRequest {
485 id: id.to_string(),
486 client_operation_id,
487 });
488 self.apply_auth(&mut request)?;
489 self.user
490 .revoke_support_access(request)
491 .await
492 .map_err(status_to_protocol_error)?;
493 Ok(())
494 }
495}
496
497fn build_target_ref(
501 namespace_path: Option<&str>,
502 repo_path: Option<&str>,
503) -> Result<Option<GrantTargetRef>, ProtocolError> {
504 match (
505 namespace_path.filter(|s| !s.is_empty()),
506 repo_path.filter(|s| !s.is_empty()),
507 ) {
508 (Some(ns), None) => Ok(Some(GrantTargetRef {
509 target: Some(GrantTargetKind::NamespacePath(ns.to_string())),
510 })),
511 (None, Some(rp)) => Ok(Some(GrantTargetRef {
512 target: Some(GrantTargetKind::RepoPath(rp.to_string())),
513 })),
514 _ => Err(ProtocolError::InvalidState(
515 "exactly one of namespace_path or repo_path must be set".into(),
516 )),
517 }
518}
519
520fn parse_namespace_kind_arg(value: &str) -> Result<grpc::heddle::v1::NamespaceKind, ProtocolError> {
524 use grpc::heddle::v1::NamespaceKind;
525 match value.trim().to_ascii_lowercase().as_str() {
526 "user" => Ok(NamespaceKind::User),
527 "namespace" | "org" => Ok(NamespaceKind::Org),
528 "team" => Ok(NamespaceKind::Team),
529 other => Err(ProtocolError::InvalidState(format!(
530 "invalid namespace kind '{other}': expected user|namespace|team"
531 ))),
532 }
533}
534
535fn parse_hosted_role_arg(value: &str) -> Result<grpc::heddle::v1::HostedRole, ProtocolError> {
537 use grpc::heddle::v1::HostedRole;
538 match value.trim().to_ascii_lowercase().as_str() {
539 "reader" => Ok(HostedRole::Reader),
540 "developer" => Ok(HostedRole::Developer),
541 "maintainer" => Ok(HostedRole::Maintainer),
542 "admin" => Ok(HostedRole::Admin),
543 "owner" => Ok(HostedRole::Owner),
544 other => Err(ProtocolError::InvalidState(format!(
545 "invalid role '{other}': expected reader|developer|maintainer|admin|owner"
546 ))),
547 }
548}