1use crate::PrCommentEnvelope;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct ExistingPrComment {
7 pub id: String,
8 pub body: String,
9}
10
11pub struct PrCommentPostPlanInput<'a> {
12 pub envelope: &'a PrCommentEnvelope,
13 pub existing: Option<&'a ExistingPrComment>,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
17#[serde(rename_all = "snake_case")]
18pub enum PrCommentPostAction {
19 Create,
20 Update,
21 Skip,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
25#[serde(rename_all = "snake_case")]
26pub enum PrCommentPostSkipReason {
27 CleanNoExistingComment,
28 Unchanged,
29}
30
31#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
32pub struct PrCommentPostPlan {
33 pub action: PrCommentPostAction,
34 pub marker_id: String,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub comment_id: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub skip_reason: Option<PrCommentPostSkipReason>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub body: Option<String>,
41}
42
43#[must_use]
44pub fn plan_pr_comment_post(input: &PrCommentPostPlanInput<'_>) -> PrCommentPostPlan {
45 match input.existing {
46 Some(existing) if existing.body == input.envelope.body => skip_unchanged(input.envelope),
47 Some(existing) => update_existing(input.envelope, existing),
48 None if input.envelope.is_clean => skip_clean(input.envelope),
49 None => create_comment(input.envelope),
50 }
51}
52
53fn create_comment(envelope: &PrCommentEnvelope) -> PrCommentPostPlan {
54 PrCommentPostPlan {
55 action: PrCommentPostAction::Create,
56 marker_id: envelope.marker_id.clone(),
57 comment_id: None,
58 skip_reason: None,
59 body: Some(envelope.body.clone()),
60 }
61}
62
63fn update_existing(
64 envelope: &PrCommentEnvelope,
65 existing: &ExistingPrComment,
66) -> PrCommentPostPlan {
67 PrCommentPostPlan {
68 action: PrCommentPostAction::Update,
69 marker_id: envelope.marker_id.clone(),
70 comment_id: Some(existing.id.clone()),
71 skip_reason: None,
72 body: Some(envelope.body.clone()),
73 }
74}
75
76fn skip_clean(envelope: &PrCommentEnvelope) -> PrCommentPostPlan {
77 PrCommentPostPlan {
78 action: PrCommentPostAction::Skip,
79 marker_id: envelope.marker_id.clone(),
80 comment_id: None,
81 skip_reason: Some(PrCommentPostSkipReason::CleanNoExistingComment),
82 body: None,
83 }
84}
85
86fn skip_unchanged(envelope: &PrCommentEnvelope) -> PrCommentPostPlan {
87 PrCommentPostPlan {
88 action: PrCommentPostAction::Skip,
89 marker_id: envelope.marker_id.clone(),
90 comment_id: None,
91 skip_reason: Some(PrCommentPostSkipReason::Unchanged),
92 body: None,
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn envelope(is_clean: bool, body: &str) -> PrCommentEnvelope {
101 PrCommentEnvelope {
102 marker_id: "fallow-results".to_owned(),
103 body: body.to_owned(),
104 is_clean,
105 details_url: None,
106 check_summary: None,
107 truncation: crate::PrCommentTruncation::default(),
108 }
109 }
110
111 #[test]
112 fn clean_without_existing_comment_skips_create() {
113 let plan = plan_pr_comment_post(&PrCommentPostPlanInput {
114 envelope: &envelope(true, "clean"),
115 existing: None,
116 });
117
118 assert_eq!(plan.action, PrCommentPostAction::Skip);
119 assert_eq!(
120 plan.skip_reason,
121 Some(PrCommentPostSkipReason::CleanNoExistingComment)
122 );
123 assert_eq!(plan.body, None);
124 }
125
126 #[test]
127 fn clean_with_existing_comment_updates_existing_body() {
128 let current = envelope(true, "clean");
129 let existing = ExistingPrComment {
130 id: "42".to_owned(),
131 body: "old".to_owned(),
132 };
133
134 let plan = plan_pr_comment_post(&PrCommentPostPlanInput {
135 envelope: ¤t,
136 existing: Some(&existing),
137 });
138
139 assert_eq!(plan.action, PrCommentPostAction::Update);
140 assert_eq!(plan.comment_id.as_deref(), Some("42"));
141 assert_eq!(plan.body.as_deref(), Some("clean"));
142 }
143
144 #[test]
145 fn dirty_without_existing_comment_creates_comment() {
146 let current = envelope(false, "dirty");
147
148 let plan = plan_pr_comment_post(&PrCommentPostPlanInput {
149 envelope: ¤t,
150 existing: None,
151 });
152
153 assert_eq!(plan.action, PrCommentPostAction::Create);
154 assert_eq!(plan.body.as_deref(), Some("dirty"));
155 }
156
157 #[test]
158 fn identical_existing_comment_skips_update() {
159 let current = envelope(false, "same");
160 let existing = ExistingPrComment {
161 id: "42".to_owned(),
162 body: "same".to_owned(),
163 };
164
165 let plan = plan_pr_comment_post(&PrCommentPostPlanInput {
166 envelope: ¤t,
167 existing: Some(&existing),
168 });
169
170 assert_eq!(plan.action, PrCommentPostAction::Skip);
171 assert_eq!(plan.skip_reason, Some(PrCommentPostSkipReason::Unchanged));
172 }
173}