handlers.rs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. use crate::config::{self, Config, ConfigurationError};
  2. use crate::github::{Event, GithubClient, IssueCommentAction, IssuesAction, IssuesEvent};
  3. use octocrab::Octocrab;
  4. use parser::command::{Command, Input};
  5. use std::fmt;
  6. use std::sync::Arc;
  7. use tokio_postgres::Client as DbClient;
  8. #[derive(Debug)]
  9. pub enum HandlerError {
  10. Message(String),
  11. Other(anyhow::Error),
  12. }
  13. impl std::error::Error for HandlerError {}
  14. impl fmt::Display for HandlerError {
  15. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  16. match self {
  17. HandlerError::Message(msg) => write!(f, "{}", msg),
  18. HandlerError::Other(_) => write!(f, "An internal error occurred."),
  19. }
  20. }
  21. }
  22. mod assign;
  23. mod autolabel;
  24. mod glacier;
  25. mod major_change;
  26. mod nominate;
  27. mod notification;
  28. mod notify_zulip;
  29. mod ping;
  30. mod prioritize;
  31. mod relabel;
  32. mod rustc_commits;
  33. pub async fn handle(ctx: &Context, event: &Event) -> Vec<HandlerError> {
  34. let config = config::get(&ctx.github, event.repo_name()).await;
  35. let mut errors = Vec::new();
  36. if let (Ok(config), Event::Issue(event)) = (config.as_ref(), event) {
  37. handle_issue(ctx, event, config, &mut errors).await;
  38. }
  39. if let Some(body) = event.comment_body() {
  40. handle_command(ctx, event, &config, body, &mut errors).await;
  41. }
  42. if let Err(e) = notification::handle(ctx, event).await {
  43. log::error!(
  44. "failed to process event {:?} with notification handler: {:?}",
  45. event,
  46. e
  47. );
  48. }
  49. if let Err(e) = rustc_commits::handle(ctx, event).await {
  50. log::error!(
  51. "failed to process event {:?} with rustc_commits handler: {:?}",
  52. event,
  53. e
  54. );
  55. }
  56. errors
  57. }
  58. macro_rules! issue_handlers {
  59. ($($name:ident,)*) => {
  60. async fn handle_issue(
  61. ctx: &Context,
  62. event: &IssuesEvent,
  63. config: &Arc<Config>,
  64. errors: &mut Vec<HandlerError>,
  65. ) {
  66. $(
  67. match $name::parse_input(ctx, event, config.$name.as_ref()) {
  68. Err(err) => errors.push(HandlerError::Message(err)),
  69. Ok(Some(input)) => {
  70. if let Some(config) = &config.$name {
  71. $name::handle_input(ctx, config, event, input).await.unwrap_or_else(|err| errors.push(HandlerError::Other(err)));
  72. } else {
  73. errors.push(HandlerError::Message(format!(
  74. "The feature `{}` is not enabled in this repository.\n\
  75. To enable it add its section in the `triagebot.toml` \
  76. in the root of the repository.",
  77. stringify!($name)
  78. )));
  79. }
  80. }
  81. Ok(None) => {}
  82. })*
  83. }
  84. }
  85. }
  86. // Handle events that happend on issues
  87. //
  88. // This is for events that happends only on issues (e.g. label changes).
  89. // Each module in the list must contain the functions `parse_input` and `handle_input`.
  90. issue_handlers! {
  91. autolabel,
  92. major_change,
  93. notify_zulip,
  94. }
  95. macro_rules! command_handlers {
  96. ($($name:ident: $enum:ident,)*) => {
  97. async fn handle_command(
  98. ctx: &Context,
  99. event: &Event,
  100. config: &Result<Arc<Config>, ConfigurationError>,
  101. body: &str,
  102. errors: &mut Vec<HandlerError>,
  103. ) {
  104. match event {
  105. Event::Issue(e) => if !matches!(e.action, IssuesAction::Opened | IssuesAction::Edited) {
  106. // no change in issue's body for these events, so skip
  107. log::debug!("skipping event, issue was {:?}", e.action);
  108. return;
  109. }
  110. Event::IssueComment(e) => if e.action == IssueCommentAction::Deleted {
  111. // don't execute commands again when comment is deleted
  112. log::debug!("skipping event, comment was {:?}", e.action);
  113. return;
  114. }
  115. }
  116. let input = Input::new(&body, &ctx.username);
  117. let commands = if let Some(previous) = event.comment_from() {
  118. let prev_commands = Input::new(&previous, &ctx.username).collect::<Vec<_>>();
  119. input.filter(|cmd| !prev_commands.contains(cmd)).collect::<Vec<_>>()
  120. } else {
  121. input.collect()
  122. };
  123. if commands.is_empty() {
  124. return;
  125. }
  126. let config = match config {
  127. Ok(config) => config,
  128. Err(e @ ConfigurationError::Missing) => {
  129. return errors.push(HandlerError::Message(e.to_string()));
  130. }
  131. Err(e @ ConfigurationError::Toml(_)) => {
  132. return errors.push(HandlerError::Message(e.to_string()));
  133. }
  134. Err(e @ ConfigurationError::Http(_)) => {
  135. return errors.push(HandlerError::Other(e.clone().into()));
  136. }
  137. };
  138. for command in commands {
  139. match command {
  140. $(
  141. Command::$enum(Ok(command)) => {
  142. if let Some(config) = &config.$name {
  143. $name::handle_command(ctx, config, event, command)
  144. .await
  145. .unwrap_or_else(|err| errors.push(HandlerError::Other(err)));
  146. } else {
  147. errors.push(HandlerError::Message(format!(
  148. "The feature `{}` is not enabled in this repository.\n\
  149. To enable it add its section in the `triagebot.toml` \
  150. in the root of the repository.",
  151. stringify!($name)
  152. )));
  153. }
  154. }
  155. Command::$enum(Err(err)) => {
  156. errors.push(HandlerError::Message(format!(
  157. "Parsing {} command in [comment]({}) failed: {}",
  158. stringify!($name),
  159. event.html_url().expect("has html url"),
  160. err
  161. )));
  162. })*
  163. }
  164. }
  165. }
  166. }
  167. }
  168. // Handle commands in comments/issues body
  169. //
  170. // This is for handlers for commands parsed by the `parser` crate.
  171. // Each variant of `parser::command::Command` must be in this list,
  172. // preceded by the module containing the coresponding `handle_command` function
  173. command_handlers! {
  174. assign: Assign,
  175. glacier: Glacier,
  176. nominate: Nominate,
  177. ping: Ping,
  178. prioritize: Prioritize,
  179. relabel: Relabel,
  180. major_change: Second,
  181. }
  182. pub struct Context {
  183. pub github: GithubClient,
  184. pub db: DbClient,
  185. pub username: String,
  186. pub octocrab: Octocrab,
  187. }