lib.rs 5.5 KB

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