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