lib.rs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. let mut deserializer = serde_json::Deserializer::from_str(&v);
  58. let res: Result<T, _> = serde_path_to_error::deserialize(&mut deserializer);
  59. match res {
  60. Ok(r) => Ok(r),
  61. Err(e) => {
  62. let ctx = format!("at {:?}", e.path());
  63. Err(e.into_inner()).context(ctx)
  64. }
  65. }
  66. }
  67. pub async fn webhook(
  68. event: EventName,
  69. payload: String,
  70. ctx: &handlers::Context,
  71. ) -> Result<(), WebhookError> {
  72. let event = match event {
  73. EventName::PullRequestReview => {
  74. let payload = deserialize_payload::<github::PullRequestReviewEvent>(&payload)
  75. .context("PullRequestReview failed to deserialize")
  76. .map_err(anyhow::Error::from)?;
  77. log::info!("handling pull request review comment {:?}", payload);
  78. // Treat pull request review comments exactly like pull request
  79. // review comments.
  80. github::Event::IssueComment(github::IssueCommentEvent {
  81. action: match payload.action {
  82. github::PullRequestReviewAction::Submitted => {
  83. github::IssueCommentAction::Created
  84. }
  85. github::PullRequestReviewAction::Edited => github::IssueCommentAction::Edited,
  86. github::PullRequestReviewAction::Dismissed => {
  87. github::IssueCommentAction::Deleted
  88. }
  89. },
  90. issue: payload.pull_request,
  91. comment: payload.review,
  92. repository: payload.repository,
  93. })
  94. }
  95. EventName::PullRequestReviewComment => {
  96. let payload = deserialize_payload::<github::PullRequestReviewComment>(&payload)
  97. .context("PullRequestReview(Comment) failed to deserialize")
  98. .map_err(anyhow::Error::from)?;
  99. log::info!("handling pull request review comment {:?}", payload);
  100. // Treat pull request review comments exactly like pull request
  101. // review comments.
  102. github::Event::IssueComment(github::IssueCommentEvent {
  103. action: payload.action,
  104. issue: payload.issue,
  105. comment: payload.comment,
  106. repository: payload.repository,
  107. })
  108. }
  109. EventName::IssueComment => {
  110. let payload = deserialize_payload::<github::IssueCommentEvent>(&payload)
  111. .context("IssueCommentEvent failed to deserialize")
  112. .map_err(anyhow::Error::from)?;
  113. log::info!("handling issue comment {:?}", payload);
  114. github::Event::IssueComment(payload)
  115. }
  116. EventName::Issue => {
  117. let payload = deserialize_payload::<github::IssuesEvent>(&payload)
  118. .context("IssuesEvent failed to deserialize")
  119. .map_err(anyhow::Error::from)?;
  120. log::info!("handling issue event {:?}", payload);
  121. github::Event::Issue(payload)
  122. }
  123. // Other events need not be handled
  124. EventName::Other => {
  125. return Ok(());
  126. }
  127. };
  128. if let Err(err) = handlers::handle(&ctx, &event).await {
  129. match err {
  130. HandlerError::Message(message) => {
  131. if let Some(issue) = event.issue() {
  132. let cmnt = ErrorComment::new(issue, message);
  133. cmnt.post(&ctx.github).await?;
  134. }
  135. }
  136. HandlerError::Other(err) => {
  137. log::error!("handling event failed: {:?}", err);
  138. return Err(WebhookError(anyhow::anyhow!(
  139. "handling failed, error logged",
  140. )));
  141. }
  142. }
  143. }
  144. Ok(())
  145. }