use serde::{Deserialize, Serialize};
use super::encode_path;
use super::BugzillaClient;
use crate::error::Result;
use crate::types::{ApiMode, Comment, UpdateCommentTagsParams};
#[derive(Serialize)]
struct AddCommentBody<'a> {
comment: &'a str,
is_private: bool,
}
#[derive(Deserialize)]
struct CommentResponse {
bugs: std::collections::HashMap<String, CommentBugEntry>,
}
#[derive(Deserialize)]
struct CommentBugEntry {
comments: Vec<Comment>,
}
#[derive(Deserialize)]
struct FlatCommentsResponse {
comments: Vec<Comment>,
}
fn extract_bugs_comment_envelope(value: &serde_json::Value) -> Result<Vec<Comment>> {
let resp = CommentResponse::deserialize(value).map_err(|e| {
crate::error::BzrError::Deserialize(format!("comments `bugs` envelope: {e}"))
})?;
resp.bugs
.into_values()
.next()
.map(|e| e.comments)
.ok_or_else(|| {
crate::error::BzrError::Deserialize(
"comments `bugs` envelope: empty top-level map".into(),
)
})
}
fn extract_flat_comment_envelope(value: &serde_json::Value) -> Result<Vec<Comment>> {
let resp = FlatCommentsResponse::deserialize(value)
.map_err(|e| crate::error::BzrError::Deserialize(format!("comments flat envelope: {e}")))?;
Ok(resp.comments)
}
impl BugzillaClient {
pub async fn get_comments_since(
&self,
bug_id: u64,
since: Option<&str>,
) -> Result<Vec<Comment>> {
match self.api_mode {
ApiMode::Rest => self.get_comments_since_rest(bug_id, since).await,
ApiMode::XmlRpc => {
self.xmlrpc_client()?
.get_comments_since(bug_id, since)
.await
}
ApiMode::Hybrid => {
match self
.xmlrpc_client()?
.get_comments_since(bug_id, since)
.await
{
Ok(comments) => Ok(comments),
Err(e) if e.is_transport_failure() => {
tracing::info!(
bug_id,
error = %e,
"XML-RPC comment list failed, retrying via REST"
);
self.get_comments_since_rest(bug_id, since).await
}
Err(e) => Err(e),
}
}
}
}
async fn get_comments_since_rest(
&self,
bug_id: u64,
since: Option<&str>,
) -> Result<Vec<Comment>> {
let path_str = format!("bug/{bug_id}/comment");
let value = if let Some(since) = since {
let req = self.apply_auth(
self.http
.get(self.url(&path_str))
.query(&[("new_since", since)]),
);
let resp = self.send(req).await?;
self.parse_json_value(resp).await?
} else {
self.get_json_value(&path_str).await?
};
Self::try_envelopes(
&value,
&[
("bugs", extract_bugs_comment_envelope),
("comments", extract_flat_comment_envelope),
],
)
}
pub async fn update_comment_tags(
&self,
comment_id: u64,
params: &UpdateCommentTagsParams,
) -> Result<Vec<String>> {
self.put_json_response(&format!("bug/comment/{comment_id}/tags"), params)
.await
}
pub async fn search_comment_tags(&self, query: &str) -> Result<Vec<String>> {
self.get_json(&format!("bug/comment/tags/{}", encode_path(query)))
.await
}
pub async fn add_comment(&self, bug_id: u64, text: &str, is_private: bool) -> Result<u64> {
self.post_json_id(
&format!("bug/{bug_id}/comment"),
&AddCommentBody {
comment: text,
is_private,
},
)
.await
}
}
#[cfg(test)]
impl BugzillaClient {
pub(crate) async fn get_comments_since_rest_for_test(
&self,
bug_id: u64,
since: Option<&str>,
) -> Result<Vec<crate::types::Comment>> {
self.get_comments_since_rest(bug_id, since).await
}
}
#[cfg(test)]
#[path = "comment_tests.rs"]
mod tests;