lib.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #![allow(clippy::new_without_default)]
  2. #[macro_use]
  3. extern crate lazy_static;
  4. use anyhow::Context;
  5. use handlers::HandlerError;
  6. use interactions::ErrorComment;
  7. use std::fmt;
  8. pub mod actions;
  9. pub mod agenda;
  10. pub mod config;
  11. pub mod db;
  12. pub mod github;
  13. pub mod handlers;
  14. pub mod interactions;
  15. pub mod logger;
  16. pub mod notification_listing;
  17. pub mod payload;
  18. pub mod team;
  19. mod team_data;
  20. pub mod zulip;
  21. #[derive(Debug)]
  22. pub enum EventName {
  23. PullRequest,
  24. PullRequestReview,
  25. PullRequestReviewComment,
  26. IssueComment,
  27. Issue,
  28. Other,
  29. }
  30. impl std::str::FromStr for EventName {
  31. type Err = std::convert::Infallible;
  32. fn from_str(s: &str) -> Result<EventName, Self::Err> {
  33. Ok(match s {
  34. "pull_request_review" => EventName::PullRequestReview,
  35. "pull_request_review_comment" => EventName::PullRequestReviewComment,
  36. "issue_comment" => EventName::IssueComment,
  37. "pull_request" => EventName::PullRequest,
  38. "issues" => EventName::Issue,
  39. _ => EventName::Other,
  40. })
  41. }
  42. }
  43. impl fmt::Display for EventName {
  44. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  45. write!(
  46. f,
  47. "{}",
  48. match self {
  49. EventName::PullRequestReview => "pull_request_review",
  50. EventName::PullRequestReviewComment => "pull_request_review_comment",
  51. EventName::IssueComment => "issue_comment",
  52. EventName::Issue => "issues",
  53. EventName::PullRequest => "pull_request",
  54. EventName::Other => "other",
  55. }
  56. )
  57. }
  58. }
  59. #[derive(Debug)]
  60. pub struct WebhookError(anyhow::Error);
  61. impl From<anyhow::Error> for WebhookError {
  62. fn from(e: anyhow::Error) -> WebhookError {
  63. WebhookError(e)
  64. }
  65. }
  66. pub fn deserialize_payload<T: serde::de::DeserializeOwned>(v: &str) -> anyhow::Result<T> {
  67. let mut deserializer = serde_json::Deserializer::from_str(&v);
  68. let res: Result<T, _> = serde_path_to_error::deserialize(&mut deserializer);
  69. match res {
  70. Ok(r) => Ok(r),
  71. Err(e) => {
  72. let ctx = format!("at {:?}", e.path());
  73. Err(e.into_inner()).context(ctx)
  74. }
  75. }
  76. }
  77. pub async fn webhook(
  78. event: EventName,
  79. payload: String,
  80. ctx: &handlers::Context,
  81. ) -> Result<bool, WebhookError> {
  82. let event = match event {
  83. EventName::PullRequestReview => {
  84. let payload = deserialize_payload::<github::PullRequestReviewEvent>(&payload)
  85. .context("PullRequestReview failed to deserialize")
  86. .map_err(anyhow::Error::from)?;
  87. log::info!("handling pull request review comment {:?}", payload);
  88. // Treat pull request review comments exactly like pull request
  89. // review comments.
  90. github::Event::IssueComment(github::IssueCommentEvent {
  91. action: match payload.action {
  92. github::PullRequestReviewAction::Submitted => {
  93. github::IssueCommentAction::Created
  94. }
  95. github::PullRequestReviewAction::Edited => github::IssueCommentAction::Edited,
  96. github::PullRequestReviewAction::Dismissed => {
  97. github::IssueCommentAction::Deleted
  98. }
  99. },
  100. changes: payload.changes,
  101. issue: payload.pull_request,
  102. comment: payload.review,
  103. repository: payload.repository,
  104. })
  105. }
  106. EventName::PullRequestReviewComment => {
  107. let payload = deserialize_payload::<github::PullRequestReviewComment>(&payload)
  108. .context("PullRequestReview(Comment) failed to deserialize")
  109. .map_err(anyhow::Error::from)?;
  110. log::info!("handling pull request review comment {:?}", payload);
  111. // Treat pull request review comments exactly like pull request
  112. // review comments.
  113. github::Event::IssueComment(github::IssueCommentEvent {
  114. action: payload.action,
  115. changes: payload.changes,
  116. issue: payload.issue,
  117. comment: payload.comment,
  118. repository: payload.repository,
  119. })
  120. }
  121. EventName::IssueComment => {
  122. let payload = deserialize_payload::<github::IssueCommentEvent>(&payload)
  123. .context("IssueCommentEvent failed to deserialize")
  124. .map_err(anyhow::Error::from)?;
  125. log::info!("handling issue comment {:?}", payload);
  126. github::Event::IssueComment(payload)
  127. }
  128. EventName::Issue | EventName::PullRequest => {
  129. let payload = deserialize_payload::<github::IssuesEvent>(&payload)
  130. .context(format!("{:?} failed to deserialize", event))
  131. .map_err(anyhow::Error::from)?;
  132. log::info!("handling issue event {:?}", payload);
  133. github::Event::Issue(payload)
  134. }
  135. // Other events need not be handled
  136. EventName::Other => {
  137. return Ok(false);
  138. }
  139. };
  140. let errors = handlers::handle(&ctx, &event).await;
  141. let mut other_error = false;
  142. let mut message = String::new();
  143. for err in errors {
  144. match err {
  145. HandlerError::Message(msg) => {
  146. if !message.is_empty() {
  147. message.push_str("\n\n");
  148. }
  149. message.push_str(&msg);
  150. }
  151. HandlerError::Other(err) => {
  152. log::error!("handling event failed: {:?}", err);
  153. other_error = true;
  154. }
  155. }
  156. }
  157. if !message.is_empty() {
  158. if let Some(issue) = event.issue() {
  159. let cmnt = ErrorComment::new(issue, message);
  160. cmnt.post(&ctx.github).await?;
  161. }
  162. }
  163. if other_error {
  164. Err(WebhookError(anyhow::anyhow!(
  165. "handling failed, error logged",
  166. )))
  167. } else {
  168. Ok(true)
  169. }
  170. }