From 337daec9477c3d8fa968dc9be353cc8ba9062c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20Postula?= Date: Thu, 25 Jul 2024 09:40:48 +0200 Subject: [PATCH] feat: implement hook deliveries --- src/api.rs | 1 + src/api/hooks.rs | 69 ++++++++++++++++++++++++++++++ src/api/hooks/list_deliveries.rs | 55 ++++++++++++++++++++++++ src/api/hooks/retry_delivery.rs | 73 ++++++++++++++++++++++++++++++++ src/lib.rs | 7 ++- src/models.rs | 1 + src/models/hooks.rs | 15 +++++++ 7 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/api/hooks.rs create mode 100644 src/api/hooks/list_deliveries.rs create mode 100644 src/api/hooks/retry_delivery.rs diff --git a/src/api.rs b/src/api.rs index 7c21b00f..01e9e530 100644 --- a/src/api.rs +++ b/src/api.rs @@ -7,6 +7,7 @@ pub mod current; pub mod events; pub mod gists; pub mod gitignore; +pub mod hooks; pub mod issues; pub mod licenses; pub mod markdown; diff --git a/src/api/hooks.rs b/src/api/hooks.rs new file mode 100644 index 00000000..5ae6ba84 --- /dev/null +++ b/src/api/hooks.rs @@ -0,0 +1,69 @@ +//! The hooks API. +use crate::{Octocrab}; +use crate::models::{HookDeliveryId, HookId}; + +mod list_deliveries; +mod retry_delivery; + +pub use self::{ + list_deliveries::ListHooksDeliveriesBuilder, + retry_delivery::RetryDeliveryBuilder, +}; + +/// A client to GitHub's webhooks API. +/// +/// Created with [`Octocrab::hooks`]. +pub struct HooksHandler<'octo> { + crab: &'octo Octocrab, + owner: String, + repo: Option, +} + +impl<'octo> HooksHandler<'octo> { + pub(crate) fn new(crab: &'octo Octocrab, owner: String) -> Self { + Self { + crab, + owner, + repo: None, + } + } + + pub fn repo(mut self, repo: String) -> Self { + self.repo = Some(repo); + self + } + + /// Lists all of the `Delivery`s associated with the hook. + /// ```no_run + /// # async fn run() -> octocrab::Result<()> { + /// let reviews = octocrab::instance() + /// .hooks("owner") + /// //.repo("repo") + /// .list_deliveries(21u64.into()) + /// .per_page(100) + /// .page(2u32) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn list_deliveries(&self, hook_id: HookId) -> ListHooksDeliveriesBuilder<'_, '_> { + ListHooksDeliveriesBuilder::new(self, hook_id) + } + + /// Retry a delivery. + /// ```no_run + /// # async fn run() -> octocrab::Result<()> { + /// let reviews = octocrab::instance() + /// .hooks("owner") + /// //.repo("repo") + /// .retry_delivery(20u64.into(), 21u64.into()) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn retry_delivery(&self, hook_id: HookId, delivery_id: HookDeliveryId) -> RetryDeliveryBuilder<'_, '_> { + RetryDeliveryBuilder::new(self, hook_id, delivery_id) + } +} diff --git a/src/api/hooks/list_deliveries.rs b/src/api/hooks/list_deliveries.rs new file mode 100644 index 00000000..3c2d63d0 --- /dev/null +++ b/src/api/hooks/list_deliveries.rs @@ -0,0 +1,55 @@ +use super::*; + +/// A builder pattern struct for listing hooks deliveries. +/// +/// created by [`HooksHandler::list_deliveries`] +/// +/// [`HooksHandler::list_deliveries`]: ./struct.HooksHandler.html#method.list_deliveries +#[derive(serde::Serialize)] +pub struct ListHooksDeliveriesBuilder<'octo, 'r> { + #[serde(skip)] + handler: &'r HooksHandler<'octo>, + #[serde(skip)] + hook_id: HookId, + #[serde(skip_serializing_if = "Option::is_none")] + per_page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + page: Option, +} +impl<'octo, 'r> ListHooksDeliveriesBuilder<'octo, 'r> { + pub(crate) fn new(handler: &'r HooksHandler<'octo>, hook_id: HookId) -> Self { + Self { + handler, + hook_id, + per_page: None, + page: None, + } + } + + /// Results per page (max 100). + pub fn per_page(mut self, per_page: impl Into) -> Self { + self.per_page = Some(per_page.into()); + self + } + + /// Page number of the results to fetch. + pub fn page(mut self, page: impl Into) -> Self { + self.page = Some(page.into()); + self + } + + /// Send the actual request. + pub async fn send(self) -> crate::Result> { + let route = match self.handler.repo.clone() { + Some(repo) => format!( + "/repos/{}/{}/hooks/{}/deliveries", + self.handler.owner, repo, self.hook_id + ), + None => format!( + "/orgs/{}/hooks/{}/deliveries", + self.handler.owner, self.hook_id + ), + }; + self.handler.crab.get(route, Some(&self)).await + } +} diff --git a/src/api/hooks/retry_delivery.rs b/src/api/hooks/retry_delivery.rs new file mode 100644 index 00000000..1d8b53de --- /dev/null +++ b/src/api/hooks/retry_delivery.rs @@ -0,0 +1,73 @@ +use http::Uri; +use snafu::ResultExt; +use crate::error::HttpSnafu; +use super::*; + +/// A builder pattern struct for listing hooks deliveries. +/// +/// created by [`HooksHandler::retry_delivery`] +/// +/// [`HooksHandler::retry_delivery`]: ./struct.HooksHandler.html#method.retry_delivery +#[derive(serde::Serialize)] +pub struct RetryDeliveryBuilder<'octo, 'r> { + #[serde(skip)] + handler: &'r HooksHandler<'octo>, + #[serde(skip)] + hook_id: HookId, + #[serde(skip)] + delivery_id: HookDeliveryId, +} +impl<'octo, 'r> RetryDeliveryBuilder<'octo, 'r> { + pub(crate) fn new(handler: &'r HooksHandler<'octo>, hook_id: HookId, delivery_id: HookDeliveryId) -> Self { + Self { + handler, + hook_id, + delivery_id + } + } + + /// Send the actual request. + pub async fn send(self) -> crate::Result<()> { + let route= match self.handler.repo.clone() { + Some(repo) => format!("/repos/{}/{}/hooks/{}/deliveries/{}/attempts", self.handler.owner, repo, self.hook_id, self.delivery_id), + None => format!("/orgs/{}/hooks/{}/deliveries/{}/attempts", self.handler.owner, self.hook_id, self.delivery_id), + }; + + let uri = Uri::builder() + .path_and_query(route) + .build() + .context(HttpSnafu)?; + crate::map_github_error(self.handler.crab._post(uri, None::<&()>).await?) + .await + .map(drop) + + } +} + +#[cfg(test)] +mod tests { + // use crate::models::HookId; + + // #[tokio::test] + // async fn serialize() { + // let octocrab = crate::Octocrab::default(); + // let handler = octocrab.hooks("rust-lang"); + // let list = handler + // .list_deliveries(HookId::from(21u64)) + // .per_page(100) + // .page(1u8); + // + // assert_eq!( + // serde_json::to_value(list).unwrap(), + // serde_json::json!({ + // "state": "open", + // "head": "master", + // "base": "branch", + // "sort": "popularity", + // "direction": "asc", + // "per_page": 100, + // "page": 1, + // }) + // ) + // } +} diff --git a/src/lib.rs b/src/lib.rs index 2ff84c24..8fe286ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,7 +252,7 @@ use models::{AppId, InstallationId, InstallationToken}; pub use self::{ api::{ - actions, activity, apps, checks, commits, current, events, gists, gitignore, issues, + actions, activity, apps, checks, commits, current, events, gists, gitignore, hooks, issues, licenses, markdown, orgs, projects, pulls, ratelimit, repos, search, teams, workflows, }, error::{Error, GitHubError}, @@ -1168,6 +1168,11 @@ impl Octocrab { pub fn ratelimit(&self) -> ratelimit::RateLimitHandler { ratelimit::RateLimitHandler::new(self) } + + /// Creates a [`hooks::HooksHandler`] that returns the API hooks + pub fn hooks(&self, owner: impl Into) -> hooks::HooksHandler { + hooks::HooksHandler::new(self, owner.into()) + } } /// # GraphQL API. diff --git a/src/models.rs b/src/models.rs index 4cddd571..bca2e3f6 100644 --- a/src/models.rs +++ b/src/models.rs @@ -111,6 +111,7 @@ id_type!( IssueId, JobId, HookId, + HookDeliveryId, LabelId, MilestoneId, NotificationId, diff --git a/src/models/hooks.rs b/src/models/hooks.rs index 5c97b8d2..30bb407a 100644 --- a/src/models/hooks.rs +++ b/src/models/hooks.rs @@ -63,3 +63,18 @@ pub enum ContentType { #[serde(untagged)] Other(String), } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Delivery { + pub id: HookDeliveryId, + pub guid: String, + pub delivered_at: DateTime, + pub duration: f64, + pub status: String, + pub status_code: usize, + pub event: Option, + pub action: Option, + pub installation_id: Option, + pub repository_id: Option, +}