lib.rs 5.0 KB

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