lib.rs 5.5 KB

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