git_bot_feedback/comments/thread_comments.rs
1use super::DEFAULT_MARKER;
2
3/// An enumeration of possible type of comments being posted.
4///
5/// The default is [`CommentKind::Concerns`].
6#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
7pub enum CommentKind {
8 /// A comment that admonishes concerns for end-users' attention.
9 #[default]
10 Concerns,
11
12 /// A comment that basically says "Looks Good To Me".
13 Lgtm,
14}
15
16/// An enumeration of supported behaviors about posting comments.
17///
18/// See [`ThreadCommentOptions::policy`](crate::ThreadCommentOptions::policy).
19#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
20pub enum CommentPolicy {
21 /// Each thread comment is posted as a new comment.
22 ///
23 /// This may result in perceivable spam because
24 /// every new comment may cause notification emails.
25 Anew,
26
27 /// Like [`CommentPolicy::Anew`], but updates a single comment.
28 ///
29 /// Typically, this is the desirable option when posting thread comments.
30 #[default]
31 Update,
32}
33
34/// Options that control posting comments on a thread.
35///
36/// Used as a parameter value to [`RestApiClient::post_thread_comment()`](fn@crate::client::RestApiClient::post_thread_comment).
37#[derive(Debug)]
38pub struct ThreadCommentOptions {
39 /// Controls posting comments on a thread that concerns a Pull Request or Push event.
40 ///
41 /// Typically, this is only desirable for Pull Requests.
42 pub policy: CommentPolicy,
43
44 /// The comment to post.
45 ///
46 /// This can be a blank string if [`Self::no_lgtm`] is true and the
47 /// [`Self::kind`] is [`CommentKind::Lgtm`].
48 pub comment: String,
49
50 /// The [`CommentKind`] that describes the comment's purpose.
51 pub kind: CommentKind,
52
53 /// A string used to mark/identify the thread's comment as a comment submitted by this software.
54 ///
55 /// User comments may be indistinguishable from bot/generated comments if
56 /// this value is not unique enough.
57 ///
58 /// If the git server employs Markdown syntax for comments, then
59 /// it is recommended to set this to an HTML comment that is unique to
60 /// your CI application:
61 ///
62 /// ```markdown
63 /// <!-- my-cool-CI-app-name -->
64 /// ```
65 ///
66 /// The default value for this is an HTML comment generated from
67 /// this crate's name and version along with the compile-tome's datetime.
68 /// For example:
69 ///
70 /// ```markdown
71 /// <!-- git-bot-feedback/0.1.0/Jul-14-2025_17-00 -->
72 /// ```
73 pub marker: String,
74
75 /// Disallow posting "Looks Good To Me" comments.
76 ///
77 /// Setting this option to `true` may instigate the deletion of old bot comment(s),
78 /// if any exist.
79 pub no_lgtm: bool,
80}
81
82impl Default for ThreadCommentOptions {
83 fn default() -> Self {
84 Self {
85 policy: Default::default(),
86 comment: Default::default(),
87 kind: Default::default(),
88 marker: DEFAULT_MARKER.to_string(),
89 no_lgtm: Default::default(),
90 }
91 }
92}
93
94impl ThreadCommentOptions {
95 /// Ensure that the [`ThreadCommentOptions::comment`] is marked with
96 /// the [`ThreadCommentOptions::marker`].
97 ///
98 /// Typically only used by implementations of
99 /// [`RestApiClient::post_thread_comment`](crate::client::RestApiClient::post_thread_comment)
100 /// and [`RestApiClient::append_step_summary`](crate::client::RestApiClient::append_step_summary).
101 pub fn mark_comment(&self) -> String {
102 if !self.comment.starts_with(&self.marker) {
103 return format!("{}{}", self.marker, self.comment);
104 }
105 self.comment.clone()
106 }
107}
108
109#[cfg(test)]
110mod test {
111 #![allow(clippy::unwrap_used)]
112
113 use super::{DEFAULT_MARKER, ThreadCommentOptions};
114 use chrono::NaiveDateTime;
115
116 #[test]
117 fn default_marker() {
118 let mut opts = ThreadCommentOptions::default();
119 assert_eq!(opts.marker, DEFAULT_MARKER);
120 let datetime_start = concat!(
121 "<!-- ",
122 env!("CARGO_CRATE_NAME"),
123 "/",
124 env!("CARGO_PKG_VERSION"),
125 "/",
126 )
127 .len();
128 let datetime_end = DEFAULT_MARKER.len() - 5;
129 let datetime_str = &DEFAULT_MARKER[datetime_start..datetime_end];
130 NaiveDateTime::parse_from_str(datetime_str, "%b-%d-%Y_%H-%M").unwrap();
131 assert_eq!(opts.mark_comment(), DEFAULT_MARKER);
132 let comment = format!("{DEFAULT_MARKER}Some text data.");
133 opts.comment = comment.clone();
134 assert_eq!(opts.mark_comment(), comment);
135 }
136}