summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoïs Postula <lois@postu.la>2024-07-26 16:06:10 +0200
committerGitHub <noreply@github.com>2024-07-26 16:06:10 +0200
commit4162be4a6e8cd4464208eaa7106f79c038630cf7 (patch)
tree8bf6aadf96952a5c04e5f6d126189fa8848dc52c
parent6ef99b8e21d6ef8f696f243af9bc4202797f3503 (diff)
feat: implement hook deliveries (#668)
-rw-r--r--src/api.rs1
-rw-r--r--src/api/hooks.rs70
-rw-r--r--src/api/hooks/list_deliveries.rs55
-rw-r--r--src/api/hooks/retry_delivery.rs54
-rw-r--r--src/lib.rs7
-rw-r--r--src/models.rs1
-rw-r--r--src/models/hooks.rs15
-rw-r--r--tests/hooks_delivery_list.rs89
-rw-r--r--tests/resources/hooks_delivery_list.json32
9 files changed, 323 insertions, 1 deletions
diff --git a/src/api.rs b/src/api.rs
index 7c21b00..01e9e53 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 0000000..d849d76
--- /dev/null
+++ b/src/api/hooks.rs
@@ -0,0 +1,70 @@
+//! The hooks API.
+use crate::models::{HookDeliveryId, HookId};
+use crate::Octocrab;
+
+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<String>,
+}
+
+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 0000000..3c2d63d
--- /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<u8>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ page: Option<u32>,
+}
+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<u8>) -> Self {
+ self.per_page = Some(per_page.into());
+ self
+ }
+
+ /// Page number of the results to fetch.
+ pub fn page(mut self, page: impl Into<u32>) -> Self {
+ self.page = Some(page.into());
+ self
+ }
+
+ /// Send the actual request.
+ pub async fn send(self) -> crate::Result<crate::Page<crate::models::hooks::Delivery>> {
+ 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 0000000..760403c
--- /dev/null
+++ b/src/api/hooks/retry_delivery.rs
@@ -0,0 +1,54 @@
+use super::*;
+use crate::error::HttpSnafu;
+use http::Uri;
+use snafu::ResultExt;
+
+/// 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)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 820c6de..fd9788a 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},
@@ -1148,6 +1148,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<String>) -> hooks::HooksHandler {
+ hooks::HooksHandler::new(self, owner.into())
+ }
}
/// # GraphQL API.
diff --git a/src/models.rs b/src/models.rs
index 4cddd57..bca2e3f 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 5c97b8d..30bb407 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<Utc>,
+ pub duration: f64,
+ pub status: String,
+ pub status_code: usize,
+ pub event: Option<WebhookEventType>,
+ pub action: Option<String>,
+ pub installation_id: Option<InstallationId>,
+ pub repository_id: Option<InstallationId>,
+}
diff --git a/tests/hooks_delivery_list.rs b/tests/hooks_delivery_list.rs
new file mode 100644
index 0000000..0824369
--- /dev/null
+++ b/tests/hooks_delivery_list.rs
@@ -0,0 +1,89 @@
+/// Tests API calls related to check runs of a specific commit.
+mod mock_error;
+
+use mock_error::setup_error_handler;
+use octocrab::models::hooks::Delivery;
+use octocrab::models::HookId;
+use octocrab::{Error, Octocrab};
+use serde::{Deserialize, Serialize};
+use serde_json::{json, Value};
+use wiremock::{
+ matchers::{method, path},
+ Mock, MockServer, ResponseTemplate,
+};
+
+#[derive(Serialize, Deserialize)]
+struct FakePage<T> {
+ items: Vec<T>,
+}
+
+const OWNER: &str = "XAMPPRocky";
+
+async fn setup_get_api(template: ResponseTemplate, number: u64) -> MockServer {
+ let mock_server = MockServer::start().await;
+
+ Mock::given(method("GET"))
+ .and(path(format!("/orgs/{OWNER}/hooks/{number}/deliveries")))
+ .respond_with(template)
+ .mount(&mock_server)
+ .await;
+ setup_error_handler(
+ &mock_server,
+ &format!("GET on /orgs/{OWNER}/hooks/{number}/deliveries was not received"),
+ )
+ .await;
+ mock_server
+}
+
+fn setup_octocrab(uri: &str) -> Octocrab {
+ Octocrab::builder().base_uri(uri).unwrap().build().unwrap()
+}
+
+#[tokio::test]
+async fn should_return_deliveries_for_org_by_id() {
+ let number: u64 = 148681297;
+ let mocked_response: Vec<Delivery> =
+ serde_json::from_str(include_str!("resources/hooks_delivery_list.json")).unwrap();
+ let template = ResponseTemplate::new(200).set_body_json(&mocked_response);
+ let mock_server = setup_get_api(template, number).await;
+ let client = setup_octocrab(&mock_server.uri());
+ let result = client
+ .hooks(OWNER)
+ .list_deliveries(HookId(number))
+ .send()
+ .await;
+
+ assert!(
+ result.is_ok(),
+ "expected successful result, got error: {:#?}",
+ result
+ );
+
+ let hooks = result.unwrap().items;
+ assert_eq!(hooks.len(), 2);
+}
+
+#[tokio::test]
+async fn should_fail_when_no_deliveries_found() {
+ let mocked_response = json!({
+ "documentation_url": json!("rtm"),
+ "errors": Value::Null,
+ "message": json!("Its gone")
+ });
+
+ let template = ResponseTemplate::new(404).set_body_json(&mocked_response);
+ let mock_server = setup_get_api(template, 404).await;
+ let client = setup_octocrab(&mock_server.uri());
+ let result = client
+ .hooks(OWNER)
+ .list_deliveries(HookId(404))
+ .send()
+ .await;
+
+ match result.unwrap_err() {
+ Error::GitHub { source, .. } => {
+ assert_eq!("Its gone", source.message)
+ }
+ other => panic!("Unexpected error: {:?}", other),
+ }
+}
diff --git a/tests/resources/hooks_delivery_list.json b/tests/resources/hooks_delivery_list.json
new file mode 100644
index 0000000..1bde6b2
--- /dev/null
+++ b/tests/resources/hooks_delivery_list.json
@@ -0,0 +1,32 @@
+[
+ {
+ "id": 93676014012,
+ "guid": "180a8f00-4a7c-11ef-8350-d1961bccd09f",
+ "delivered_at": "2024-07-25T11:50:32Z",
+ "redelivery": false,
+ "duration": 0.32,
+ "status": "Invalid HTTP Response: 503",
+ "status_code": 503,
+ "event": "workflow_job",
+ "action": "completed",
+ "installation_id": null,
+ "repository_id": 1,
+ "url": "",
+ "throttled_at": null
+ },
+ {
+ "id": 93676002432,
+ "guid": "14d5f0e0-4a7c-11ef-8465-fadda1832ea4",
+ "delivered_at": "2024-07-25T11:50:26Z",
+ "redelivery": false,
+ "duration": 0.4,
+ "status": "OK",
+ "status_code": 200,
+ "event": "workflow_job",
+ "action": "in_progress",
+ "installation_id": null,
+ "repository_id": 1,
+ "url": "",
+ "throttled_at": null
+ }
+]