use serde::{Deserialize, Serialize};
use crate::cob::{thread::Edit, ActorId, Embed, Label, Timestamp, Uri};
use super::{lookup, Error, Patch, ReviewId, Verdict};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ReviewEdit {
#[serde(rename = "review.edit")]
V1(ReviewEditV1),
#[serde(rename = "review.edit.v2")]
V2(ReviewEditV2),
}
impl ReviewEdit {
pub fn new(
review: ReviewId,
summary: String,
verdict: Option<Verdict>,
labels: Vec<Label>,
embeds: Vec<Embed<Uri>>,
) -> Self {
Self::V2(ReviewEditV2 {
review,
summary,
verdict,
labels,
embeds,
})
}
pub fn review_id(&self) -> &ReviewId {
match self {
ReviewEdit::V1(ReviewEditV1 { review, .. }) => review,
ReviewEdit::V2(ReviewEditV2 { review, .. }) => review,
}
}
pub fn summary(&self) -> Option<&String> {
match self {
ReviewEdit::V1(ReviewEditV1 { summary, .. }) => summary.as_ref(),
ReviewEdit::V2(ReviewEditV2 { summary, .. }) => Some(summary),
}
}
pub fn verdict(&self) -> Option<&Verdict> {
match self {
ReviewEdit::V1(ReviewEditV1 { verdict, .. }) => verdict.as_ref(),
ReviewEdit::V2(ReviewEditV2 { verdict, .. }) => verdict.as_ref(),
}
}
pub fn labels(&self) -> &[Label] {
match self {
ReviewEdit::V1(ReviewEditV1 { labels, .. }) => labels,
ReviewEdit::V2(ReviewEditV2 { labels, .. }) => labels,
}
}
pub fn embeds(&self) -> Option<&Vec<Embed<Uri>>> {
match self {
ReviewEdit::V1(_) => None,
ReviewEdit::V2(ReviewEditV2 { embeds, .. }) => Some(embeds),
}
}
pub fn run(
self,
author: ActorId,
timestamp: Timestamp,
patch: &mut Patch,
) -> Result<(), Error> {
match self {
ReviewEdit::V1(ReviewEditV1 {
review,
summary,
verdict,
labels,
}) => {
if summary.is_none() && verdict.is_none() {
return Err(Error::EmptyReview);
}
let Some(review) = lookup::review_mut(patch, &review)? else {
return Ok(());
};
if let Some(body) = summary {
review
.summary
.push(Edit::new(author, body, timestamp, vec![]));
}
review.verdict = verdict;
review.labels = labels;
Ok(())
}
ReviewEdit::V2(ReviewEditV2 {
review,
summary,
verdict,
labels,
embeds,
}) => {
if summary.is_empty() && verdict.is_none() {
return Err(Error::EmptyReview);
}
let Some(review) = lookup::review_mut(patch, &review)? else {
return Ok(());
};
review
.summary
.push(Edit::new(author, summary, timestamp, embeds));
review.verdict = verdict;
review.labels = labels;
Ok(())
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReviewEditV2 {
review: ReviewId,
#[serde(default, skip_serializing_if = "String::is_empty")]
summary: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
verdict: Option<Verdict>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
labels: Vec<Label>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
embeds: Vec<Embed<Uri>>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReviewEditV1 {
review: ReviewId,
#[serde(default, skip_serializing_if = "Option::is_none")]
summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
verdict: Option<Verdict>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
labels: Vec<Label>,
}
#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod test {
use serde_json::json;
use crate::patch;
use super::ReviewEdit;
#[test]
fn test_review_edit() {
let v1 = json!({
"type": "review.edit",
"review": "89d45fb371eb2622ba88188d474347cc526d80bb",
"summary": "lgtm",
"verdict": "accept",
"labels": [],
});
let v2 = json!({
"type": "review.edit.v2",
"review": "89d45fb371eb2622ba88188d474347cc526d80bb",
"summary": "lgtm",
"verdict": "accept",
"labels": [],
"embeds": [],
});
serde_json::from_value::<ReviewEdit>(v1.clone()).unwrap();
serde_json::from_value::<ReviewEdit>(v2.clone()).unwrap();
assert!(matches!(
serde_json::from_value::<patch::Action>(v1).unwrap(),
patch::Action::ReviewEdit { .. }
));
assert!(matches!(
serde_json::from_value::<patch::Action>(v2).unwrap(),
patch::Action::ReviewEdit { .. }
));
}
}