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}