lib.rs 6.8 KB

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