diff --git a/src/api/code_scannings.rs b/src/api/code_scannings.rs index b390eee5..c606a03d 100644 --- a/src/api/code_scannings.rs +++ b/src/api/code_scannings.rs @@ -2,6 +2,7 @@ use crate::{models, params, Octocrab, Result}; mod list; +mod update; /// Handler for GitHub's code scanning API. /// @@ -40,4 +41,25 @@ impl<'octo> CodeScanningHandler<'octo> { pub fn list(&self) -> list::ListCodeScanningsBuilder<'_, '_> { list::ListCodeScanningsBuilder::new(self) } + + /// Update a code scanning alert + /// ```no_run + /// # use octocrab::params; + /// + /// async fn run() -> octocrab::Result<()> { + /// # let octocrab = octocrab::Octocrab::default(); + /// use octocrab::models; + /// + /// let issue = octocrab.code_scannings("owner", "repo") + /// .update(1234u64) + /// .state(params::AlertState::Dismissed) + /// // Send the request + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn update(&self, number: u64) -> update::UpdateCodeScanningBuilder<'_, '_> { + update::UpdateCodeScanningBuilder::new(self, number) + } } diff --git a/src/api/code_scannings/update.rs b/src/api/code_scannings/update.rs new file mode 100644 index 00000000..aab4f256 --- /dev/null +++ b/src/api/code_scannings/update.rs @@ -0,0 +1,54 @@ +use super::*; + +#[derive(serde::Serialize)] +pub struct UpdateCodeScanningBuilder<'octo, 'a> { + #[serde(skip)] + handler: &'a CodeScanningHandler<'octo>, + #[serde(skip)] + number: u64, + #[serde(skip_serializing_if = "Option::is_none")] + state: Option, + #[serde(skip_serializing_if = "Option::is_none")] + dismissed_reason: Option, + #[serde(skip_serializing_if = "Option::is_none")] + dismissed_comment: Option, +} + +impl<'octo, 'a, 'b, 'c> UpdateCodeScanningBuilder<'octo, 'a> { + pub(crate) fn new(handler: &'a CodeScanningHandler<'octo>, number: u64) -> Self { + Self { + handler, + number, + state: None, + dismissed_reason: None, + dismissed_comment: None, + } + } + + /// The title of the code scanning. + pub fn state(mut self, state: impl Into) -> Self { + self.state = Some(state.into()); + self + } + + pub fn dismissed_reason(mut self, dismissed_reason: impl Into) -> Self { + self.dismissed_reason = Some(dismissed_reason.into()); + self + } + + pub fn dismissed_comment(mut self, dismissed_comment: impl Into) -> Self { + self.dismissed_comment = Some(dismissed_comment.into()); + self + } + + pub async fn send(self) -> Result { + let route = format!( + "/repos/{owner}/{repo}/code-scanning/alerts/{code_scanning}", + owner = self.handler.owner, + repo = self.handler.repo.as_ref().expect("Repository is required"), + code_scanning = self.number, + ); + + self.handler.crab.patch(route, Some(&self)).await + } +} diff --git a/src/params.rs b/src/params.rs index 06096610..b4538775 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,5 +1,14 @@ //! # Common GitHub Parameter Types +/// The status of a issue or pull request. +#[derive(Debug, Clone, Copy, serde::Serialize)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +pub enum AlertState { + Dismissed, + Open, +} + /// The status of a issue or pull request. #[derive(Debug, Clone, Copy, serde::Serialize)] #[serde(rename_all = "lowercase")] diff --git a/tests/code_scanning_alert_update_test.rs b/tests/code_scanning_alert_update_test.rs new file mode 100644 index 00000000..a7152c04 --- /dev/null +++ b/tests/code_scanning_alert_update_test.rs @@ -0,0 +1,65 @@ +use wiremock::{ + matchers::{method, path}, + Mock, MockServer, ResponseTemplate, +}; + +use mock_error::setup_error_handler; +use octocrab::models::code_scannings::CodeScanningAlert; +use octocrab::params::AlertState; +use octocrab::Octocrab; + +mod mock_error; + +async fn setup_issue_check_assignee_api(template: ResponseTemplate) -> MockServer { + let owner: &str = "org"; + let repo: &str = "some-repo"; + let number: &str = "1"; + + let mock_server = MockServer::start().await; + + Mock::given(method("PATCH")) + .and(path(format!( + "/repos/{owner}/{repo}/code-scanning/alerts/{number}", + owner = owner, + repo = repo + ))) + .respond_with(template.clone()) + .mount(&mock_server) + .await; + + setup_error_handler( + &mock_server, + &format!("GET on /repos/{owner}/{repo}/code-scanning/alerts was not received"), + ) + .await; + mock_server +} + +fn setup_octocrab(uri: &str) -> Octocrab { + Octocrab::builder().base_uri(uri).unwrap().build().unwrap() +} + +const OWNER: &str = "org"; +const REPO: &str = "some-repo"; + +#[tokio::test] +async fn check_patch_200() { + let s = include_str!("resources/codescanning_alert_single.json"); + let alert: CodeScanningAlert = serde_json::from_str(s).unwrap(); + let template = ResponseTemplate::new(200).set_body_json(&alert); + let mock_server = setup_issue_check_assignee_api(template).await; + let client = setup_octocrab(&mock_server.uri()); + + let result = client + .code_scannings(OWNER.to_owned(), REPO.to_owned()) + .update(1) + .state(AlertState::Open) + .send() + .await; + + assert!( + result.is_ok(), + "expected successful result, got error: {:#?}", + result + ); +}