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