Skip to content

Commit

Permalink
Add installation related webhook app events (#420)
Browse files Browse the repository at this point in the history
Breaking: the installation stored in WrappedEventPayload now contains
2 variants, depending on whether the event comes from an installation_
related webhook event, or something else.
  • Loading branch information
gagbo authored Jul 19, 2023
1 parent cb585d8 commit 01e8024
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "octocrab"
version = "0.27.0"
version = "0.28.0"
authors = ["XAMPPRocky <[email protected]>"]
edition = "2018"
readme = "README.md"
Expand Down
17 changes: 13 additions & 4 deletions src/models/events.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub mod payload;

use crate::models::events::payload::EventInstallationPayload;
use crate::models::events::payload::EventInstallation;

use self::payload::{
CommitCommentEventPayload, CreateEventPayload, DeleteEventPayload, EventPayload,
Expand All @@ -15,6 +15,10 @@ use serde::{de::Error, Deserialize, Serialize};
use url::Url;

/// A GitHub event.
///
/// If you want to deserialize a webhook payload received in a Github Application, you
/// must directly deserialize the body into a [`WrappedEventPayload`](WrappedEventPayload).
/// For webhooks, the event type is stored in the `X-GitHub-Event` header.
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct Event {
Expand Down Expand Up @@ -139,7 +143,7 @@ impl<'de> Deserialize<'de> for Event {
}
#[derive(Deserialize)]
struct IntermediatePayload {
installation: Option<EventInstallationPayload>,
installation: Option<EventInstallation>,
organization: Option<crate::models::orgs::Organization>,
repository: Option<crate::models::Repository>,
sender: Option<crate::models::Author>,
Expand Down Expand Up @@ -230,8 +234,13 @@ mod test {
let event: Event = serde_json::from_str(json).unwrap();
assert_eq!(event.r#type, EventType::WorkflowRunEvent);
assert_eq!(
event.payload.unwrap().installation.unwrap().id,
crate::models::InstallationId(18995746)
event.payload.unwrap().installation.unwrap(),
crate::models::events::payload::EventInstallation::Minimal(Box::new(
crate::models::events::payload::EventInstallationId {
id: 18995746.into(),
node_id: "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTg5OTU3NDY=".to_string()
}
))
)
}

Expand Down
102 changes: 98 additions & 4 deletions src/models/events/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ mod create;
mod delete;
mod fork;
mod gollum;
mod installation;
mod installation_repositories;
mod installation_target;
mod issue_comment;
mod issues;
mod member;
Expand All @@ -12,12 +15,14 @@ mod pull_request_review_comment;
mod push;
mod workflow_run;

use crate::models::{repos::CommitAuthor, InstallationId};
pub use commit_comment::*;
pub use create::*;
pub use delete::*;
pub use fork::*;
pub use gollum::*;
pub use installation::*;
pub use installation_repositories::*;
pub use installation_target::*;
pub use issue_comment::*;
pub use issues::*;
pub use member::*;
Expand All @@ -30,16 +35,29 @@ pub use workflow_run::*;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::models::{orgs::Organization, Author, Repository};
use crate::models::{
orgs::Organization, repos::CommitAuthor, Author, Installation, InstallationId, Repository,
RepositoryId,
};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EventInstallationPayload {
#[serde(untagged)]
pub enum EventInstallation {
/// A full installation object which is present for `Installation*` related webhook events.
Full(Box<Installation>),
/// The minimal installation object is present for all other event types.
Minimal(Box<EventInstallationId>),
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EventInstallationId {
pub id: InstallationId,
pub node_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WrappedEventPayload {
pub installation: Option<EventInstallationPayload>,
pub installation: Option<EventInstallation>,
pub organization: Option<Organization>,
pub repository: Option<Repository>,
pub sender: Option<Author>,
Expand All @@ -59,6 +77,9 @@ pub enum EventPayload {
PushEvent(Box<PushEventPayload>),
CreateEvent(Box<CreateEventPayload>),
DeleteEvent(Box<DeleteEventPayload>),
InstallationEvent(Box<InstallationEventPayload>),
InstallationRepositoriesEvent(Box<InstallationRepositoriesEventPayload>),
InstallationTargetEvent(Box<InstallationTargetEventPayload>),
IssuesEvent(Box<IssuesEventPayload>),
IssueCommentEvent(Box<IssueCommentEventPayload>),
CommitCommentEvent(Box<CommitCommentEventPayload>),
Expand All @@ -82,3 +103,76 @@ pub struct Commit {
pub distinct: bool,
pub url: Url,
}

/// A repository in installation related webhook events.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationEventRepository {
pub id: RepositoryId,
pub node_id: String,
pub name: String,
pub full_name: String,
pub private: bool,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn should_deserialize_installation_event() {
// The payload has been extracted as the `payload` key from a webhook installation event.
let json = include_str!("../../../tests/resources/installation_event.json");
let event: WrappedEventPayload = serde_json::from_str(json).unwrap();

let installation = event.installation.unwrap();
let specific = event.specific.unwrap();

match installation {
EventInstallation::Full(install) => {
assert_eq!(install.id, 7777777.into());
assert_eq!(install.repository_selection.unwrap(), "all");
}
EventInstallation::Minimal(_) => {
panic!("expected a Full installation payload for the event.")
}
};

match specific {
EventPayload::InstallationEvent(install) => {
let repos = install.repositories;
assert_eq!(repos.len(), 3);
assert!(
repos.iter().any(|repo| repo.name == "ViscoElRebound"),
"ViscoElRebound should be in the list of repositories"
);
assert!(
repos.iter().any(|repo| repo.name == "OSSU"),
"OSSU should be in the list of repositories"
);
assert!(
repos.iter().any(|repo| repo.name == "octocrab"),
"octocrab should be in the list of repositories"
);
}
EventPayload::PushEvent(_)
| EventPayload::CreateEvent(_)
| EventPayload::DeleteEvent(_)
| EventPayload::InstallationRepositoriesEvent(_)
| EventPayload::InstallationTargetEvent(_)
| EventPayload::IssuesEvent(_)
| EventPayload::IssueCommentEvent(_)
| EventPayload::CommitCommentEvent(_)
| EventPayload::ForkEvent(_)
| EventPayload::GollumEvent(_)
| EventPayload::MemberEvent(_)
| EventPayload::PullRequestEvent(_)
| EventPayload::PullRequestReviewEvent(_)
| EventPayload::PullRequestReviewCommentEvent(_)
| EventPayload::WorkflowRunEvent(_)
| EventPayload::UnknownEvent(_) => {
panic!("Expected an installation event, got {:?}", specific)
}
}
}
}
39 changes: 39 additions & 0 deletions src/models/events/payload/installation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! This event occurs when there is activity relating to a GitHub App
//! installation. All GitHub Apps receive this event by default. You cannot
//! manually subscribe to this event.

use serde::{Deserialize, Serialize};

use super::InstallationEventRepository;
use crate::models::Author;

/// The payload in a webhook installation event type.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationEventPayload {
/// The action this event represents.
pub action: InstallationEventAction,
/// An enterprise on GitHub
pub enterprise: Option<serde_json::Value>,
/// An array of repositories that the installation can access
pub repositories: Vec<InstallationEventRepository>,
/// The initiator of the request, mainly for the [`created`](InstallationAction::Created) action
pub requester: Option<Author>,
}

/// The action on an installation this event corresponds to.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum InstallationEventAction {
/// Someone installed a GitHub App on a user or organization account.
Created,
/// Someone uninstalled a GitHub App on a user or organization account.
Deleted,
/// Someone granted new permissions to a GitHub App.
NewPermissionsAccepted,
/// Someone blocked access by a GitHub App to their user or organization account.
Suspend,
/// A GitHub App that was blocked from accessing a user or organization account was given access the account again.
Unsuspend,
}
45 changes: 45 additions & 0 deletions src/models/events/payload/installation_repositories.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! This event occurs when there is activity relating to which repositories a
//! GitHub App installation can access. All GitHub Apps receive this event by
//! default. You cannot manually subscribe to this event.

use serde::{Deserialize, Serialize};

use super::InstallationEventRepository;
use crate::models::Author;

/// The payload in a webhook installation_repositories event type.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationRepositoriesEventPayload {
/// The action this event represents.
pub action: InstallationRepositoriesEventAction,
/// An enterprise on GitHub
pub enterprise: Option<serde_json::Value>,
/// An array of repositories, which were added to the installation
pub repositories_added: Vec<InstallationEventRepository>,
/// An array of repositories, which were removed from the installation
pub repositories_removed: Vec<InstallationEventRepository>,
/// Describe whether all repositories have been selected or there's a selection involved
pub repository_selection: InstallationRepositoriesEventSelection,
/// The initiator of the request, mainly for the [`created`](InstallationAction::Created) action
pub requester: Option<Author>,
}

/// The action on an installation this event corresponds to.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum InstallationRepositoriesEventAction {
/// A GitHub App installation was granted access to one or more repositories.
Added,
/// Access to one or more repositories was revoked for a GitHub App installation.
Removed,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum InstallationRepositoriesEventSelection {
All,
Selected,
}
36 changes: 36 additions & 0 deletions src/models/events/payload/installation_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! This event occurs when there is activity relating to the user or
//! organization account that a GitHub App is installed on.

use serde::{Deserialize, Serialize};

use crate::models::orgs::Organization;

/// The payload in a webhook installation_target event type.
///
/// Somebody renamed the user or organization account that a GitHub App is installed on.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationTargetEventPayload {
pub account: Organization,
pub changes: InstallationTargetChanges,
pub target_type: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationTargetChanges {
pub login: InstallationTargetLoginChanges,
pub slug: InstallationTargetSlugChanges,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationTargetLoginChanges {
pub from: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstallationTargetSlugChanges {
pub from: String,
}
Loading

0 comments on commit 01e8024

Please sign in to comment.