1#![allow(clippy::doc_markdown)]
2#![allow(clippy::missing_errors_doc)]
3#![allow(clippy::single_match_else)]
4pub mod actions;
5pub mod app;
6use std::error::Error;
7
8use log::info;
9use serde::{Deserialize, Serialize};
10
11pub(crate) const DEFAULT_GITHUB_API_URL: &str = "https://api.github.com";
12
13#[derive(Debug)]
14pub enum GithubError {
15 JsonWebTokenCreation(jsonwebtoken::errors::Error),
16 HttpError(reqwest::Error),
17 CommentTooLarge(String),
18}
19
20impl Error for GithubError {
21 fn source(&self) -> Option<&(dyn Error + 'static)> {
22 match self {
23 GithubError::JsonWebTokenCreation(err) => Some(err),
24 GithubError::HttpError(err) => Some(err),
25 GithubError::CommentTooLarge(_) => None,
26 }
27 }
28}
29
30#[derive(Debug, Deserialize, Serialize)]
31pub struct Comment {
32 pub id: i64,
33 pub url: String,
34 pub html_url: String,
35 pub body: String,
36 pub user: User,
37}
38
39#[derive(Debug, Deserialize, Serialize)]
40pub struct User {
41 pub id: i64,
42 pub login: String,
43 pub r#type: String,
44}
45
46pub trait GitHubApi {
47 fn app_slug(&self) -> String;
48 fn create_issue_comment(
49 &self,
50 owner: &str,
51 repo: &str,
52 issue_id: i64,
53 body: &str,
54 ) -> Result<(), GithubError>;
55 fn list_issue_comments(
56 &self,
57 owner: &str,
58 repo: &str,
59 issue_id: i64,
60 ) -> Result<Vec<Comment>, GithubError>;
61 fn update_issue_comment(
62 &self,
63 owner: &str,
64 repo: &str,
65 comment_id: i64,
66 body: &str,
67 ) -> Result<(), GithubError>;
68}
69
70pub fn comment_on_pr(
71 gh: &dyn GitHubApi,
72 owner: &str,
73 repo: &str,
74 issue: i64,
75 body: &str,
76 existing_comment_text_includes: &str,
77) -> Result<(), GithubError> {
78 let comments = gh.list_issue_comments(owner, repo, issue)?;
79
80 let bot_name = gh.app_slug();
81
82 info!("checking for existing comment");
83 match comments.iter().find(|x| {
84 x.user.r#type == "Bot"
85 && x.user.login == bot_name
86 && x.body.contains(existing_comment_text_includes)
93 }) {
94 Some(prev_comment) => {
95 info!("updating comment");
96 gh.update_issue_comment(owner, repo, prev_comment.id, body)
97 }
98 None => {
99 info!("creating comment");
100 gh.create_issue_comment(owner, repo, issue, body)
101 }
102 }
103}