heddle_client/grpc_hosted/
collaboration.rs1use grpc::heddle::api::v1alpha1::{
20 AppendTurnRequest, Discussion as ProtoDiscussion, ListDiscussionsByStateRequest,
21 OpenDiscussionRequest, PathSymbolRef, StateId as ProtoStateId,
22};
23use objects::object::{ChangeId, StateId};
24use tonic::Request;
25use wire::ProtocolError;
26
27use super::{HostedGrpcClient, helpers::status_to_protocol_error};
28
29#[derive(Debug, Clone)]
31pub struct HostedDiscussionTurn {
32 pub author_name: String,
33 pub author_email: String,
34 pub body: String,
35 pub posted_at_secs: i64,
36}
37
38#[derive(Debug, Clone)]
41pub struct HostedDiscussion {
42 pub id: String,
44 pub file: String,
45 pub symbol: String,
46 pub opened_against_state: Option<StateId>,
48 pub visibility: String,
49 pub turns: Vec<HostedDiscussionTurn>,
50}
51
52const OPEN_METHOD: &str = "/heddle.api.v1alpha1.CollaborationService/OpenDiscussion";
53const APPEND_METHOD: &str = "/heddle.api.v1alpha1.CollaborationService/AppendTurn";
54const LIST_BY_STATE_METHOD: &str = "/heddle.api.v1alpha1.CollaborationService/ListByState";
55
56fn decode_discussion(proto: ProtoDiscussion) -> HostedDiscussion {
57 let anchor = proto.anchor.unwrap_or_default();
58 HostedDiscussion {
59 id: proto.id,
60 file: anchor.file,
61 symbol: anchor.symbol,
62 opened_against_state: proto
63 .opened_against_state
64 .and_then(|state| StateId::try_from_slice(&state.value).ok()),
65 visibility: proto.visibility,
66 turns: proto
67 .turns
68 .into_iter()
69 .map(|turn| HostedDiscussionTurn {
70 author_name: turn.author_name,
71 author_email: turn.author_email,
72 body: turn.body,
73 posted_at_secs: turn.posted_at.map(|ts| ts.seconds).unwrap_or(0),
74 })
75 .collect(),
76 }
77}
78
79fn change_id_state_field(change_id: ChangeId) -> Option<ProtoStateId> {
83 Some(ProtoStateId {
84 value: change_id.as_bytes().to_vec(),
85 })
86}
87
88impl HostedGrpcClient {
89 pub fn authenticated_username(&self) -> Option<String> {
95 self.authenticated_principal
96 .as_deref()
97 .and_then(|principal| principal.strip_prefix("principal:"))
98 .map(|subject| subject.trim().to_string())
99 .filter(|subject| !subject.is_empty())
100 }
101
102 #[allow(clippy::too_many_arguments)]
105 pub async fn open_discussion(
106 &mut self,
107 repo_path: &str,
108 change_id: ChangeId,
109 file: &str,
110 symbol: &str,
111 body: &str,
112 visibility: &str,
113 client_operation_id: String,
114 ) -> Result<HostedDiscussion, ProtocolError> {
115 let mut request = Request::new(OpenDiscussionRequest {
116 repo_path: super::helpers::repository_ref(repo_path),
117 state_id: change_id_state_field(change_id),
118 anchor: Some(PathSymbolRef {
119 file: file.to_string(),
120 symbol: symbol.to_string(),
121 }),
122 body: body.to_string(),
123 visibility: visibility.to_string(),
124 thread_ref: String::new(),
125 client_operation_id,
126 });
127 self.apply_signed_auth(&mut request, OPEN_METHOD)?;
128 let response = self
129 .collaboration
130 .open_discussion(request)
131 .await
132 .map_err(status_to_protocol_error)?
133 .into_inner();
134 Ok(decode_discussion(response))
135 }
136
137 pub async fn append_turn(
140 &mut self,
141 repo_path: &str,
142 discussion_id: &str,
143 body: &str,
144 client_operation_id: String,
145 ) -> Result<HostedDiscussion, ProtocolError> {
146 let mut request = Request::new(AppendTurnRequest {
147 repo_path: super::helpers::repository_ref(repo_path),
148 discussion_id: discussion_id.to_string(),
149 body: body.to_string(),
150 client_operation_id,
151 });
152 self.apply_signed_auth(&mut request, APPEND_METHOD)?;
153 let response = self
154 .collaboration
155 .append_turn(request)
156 .await
157 .map_err(status_to_protocol_error)?
158 .into_inner();
159 Ok(decode_discussion(response))
160 }
161
162 pub async fn list_discussions_by_state(
165 &mut self,
166 repo_path: &str,
167 change_id: ChangeId,
168 status: &str,
169 ) -> Result<Vec<HostedDiscussion>, ProtocolError> {
170 let mut request = Request::new(ListDiscussionsByStateRequest {
171 repo_path: super::helpers::repository_ref(repo_path),
172 state_id: change_id_state_field(change_id),
173 status: status.to_string(),
174 });
175 self.apply_signed_auth(&mut request, LIST_BY_STATE_METHOD)?;
176 let response = self
177 .collaboration
178 .list_by_state(request)
179 .await
180 .map_err(status_to_protocol_error)?
181 .into_inner();
182 Ok(response.discussions.into_iter().map(decode_discussion).collect())
183 }
184}