Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialize installation_* webhook events #420

Merged
merged 1 commit into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <xampprocky@gmail.com>"]
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
Loading