use std::collections::BTreeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value as JsonValue;
use time::OffsetDateTime;
use super::util::{parse_json_i64ish, parse_string_array};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubEventCommon {
pub event: String,
pub action: Option<String>,
pub delivery_id: Option<String>,
pub installation_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub topic: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repo: Option<JsonValue>,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubIssuesEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub issue: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubPullRequestEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub pull_request: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubIssueCommentEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub issue: JsonValue,
pub comment: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubPullRequestReviewEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub pull_request: JsonValue,
pub review: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubPushEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
#[serde(default)]
pub commits: Vec<JsonValue>,
pub distinct_size: Option<i64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubWorkflowRunEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub workflow_run: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubDeploymentStatusEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub deployment_status: JsonValue,
pub deployment: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubCheckRunEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub check_run: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubCheckSuiteEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub check_suite: JsonValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub check_suite_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pull_request_number: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_sha: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub conclusion: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubStatusEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub commit_status: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_sha: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_url: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubMergeGroupEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
pub merge_group: JsonValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub merge_group_id: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_sha: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_sha: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_ref: Option<String>,
#[serde(default)]
pub pull_requests: Vec<JsonValue>,
#[serde(default)]
pub pull_request_numbers: Vec<i64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubInstallationEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub installation: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub account: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub installation_state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suspended: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub revoked: Option<bool>,
#[serde(default)]
pub repositories: Vec<JsonValue>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GitHubInstallationRepositoriesEventPayload {
#[serde(flatten)]
pub common: GitHubEventCommon,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub installation: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub account: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub installation_state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suspended: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub revoked: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository_selection: Option<String>,
#[serde(default)]
pub repositories_added: Vec<JsonValue>,
#[serde(default)]
pub repositories_removed: Vec<JsonValue>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(untagged)]
pub enum GitHubEventPayload {
Issues(GitHubIssuesEventPayload),
PullRequest(GitHubPullRequestEventPayload),
IssueComment(GitHubIssueCommentEventPayload),
PullRequestReview(GitHubPullRequestReviewEventPayload),
Push(GitHubPushEventPayload),
WorkflowRun(GitHubWorkflowRunEventPayload),
DeploymentStatus(GitHubDeploymentStatusEventPayload),
CheckRun(GitHubCheckRunEventPayload),
CheckSuite(GitHubCheckSuiteEventPayload),
Status(GitHubStatusEventPayload),
MergeGroup(GitHubMergeGroupEventPayload),
Installation(GitHubInstallationEventPayload),
InstallationRepositories(GitHubInstallationRepositoriesEventPayload),
Other(GitHubEventCommon),
}
impl<'de> Deserialize<'de> for GitHubEventPayload {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = JsonValue::deserialize(deserializer)?;
let kind = value
.get("event")
.and_then(JsonValue::as_str)
.unwrap_or("")
.to_string();
let from_value = |v: JsonValue| -> Result<GitHubEventPayload, D::Error> {
let payload = match kind.as_str() {
"issues" => GitHubEventPayload::Issues(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"pull_request" => GitHubEventPayload::PullRequest(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"issue_comment" => GitHubEventPayload::IssueComment(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"pull_request_review" => GitHubEventPayload::PullRequestReview(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"push" => GitHubEventPayload::Push(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"workflow_run" => GitHubEventPayload::WorkflowRun(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"deployment_status" => GitHubEventPayload::DeploymentStatus(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"check_run" => GitHubEventPayload::CheckRun(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"check_suite" => GitHubEventPayload::CheckSuite(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"status" => GitHubEventPayload::Status(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"merge_group" => GitHubEventPayload::MergeGroup(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"installation" => GitHubEventPayload::Installation(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
"installation_repositories" => GitHubEventPayload::InstallationRepositories(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
_ => GitHubEventPayload::Other(
serde_json::from_value(v).map_err(serde::de::Error::custom)?,
),
};
Ok(payload)
};
from_value(value)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SlackEventCommon {
pub event: String,
pub event_id: Option<String>,
pub api_app_id: Option<String>,
pub team_id: Option<String>,
pub channel_id: Option<String>,
pub user_id: Option<String>,
pub event_ts: Option<String>,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SlackMessageEventPayload {
#[serde(flatten)]
pub common: SlackEventCommon,
pub subtype: Option<String>,
pub channel_type: Option<String>,
pub channel: Option<String>,
pub user: Option<String>,
pub text: Option<String>,
pub ts: Option<String>,
pub thread_ts: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SlackAppMentionEventPayload {
#[serde(flatten)]
pub common: SlackEventCommon,
pub channel: Option<String>,
pub user: Option<String>,
pub text: Option<String>,
pub ts: Option<String>,
pub thread_ts: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SlackReactionAddedEventPayload {
#[serde(flatten)]
pub common: SlackEventCommon,
pub reaction: Option<String>,
pub item_user: Option<String>,
pub item: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SlackAppHomeOpenedEventPayload {
#[serde(flatten)]
pub common: SlackEventCommon,
pub user: Option<String>,
pub channel: Option<String>,
pub tab: Option<String>,
pub view: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SlackAssistantThreadStartedEventPayload {
#[serde(flatten)]
pub common: SlackEventCommon,
pub assistant_thread: JsonValue,
pub thread_ts: Option<String>,
pub context: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SlackEventPayload {
Message(SlackMessageEventPayload),
AppMention(SlackAppMentionEventPayload),
ReactionAdded(SlackReactionAddedEventPayload),
AppHomeOpened(SlackAppHomeOpenedEventPayload),
AssistantThreadStarted(SlackAssistantThreadStartedEventPayload),
Other(SlackEventCommon),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearEventCommon {
pub event: String,
pub action: Option<String>,
pub delivery_id: Option<String>,
pub organization_id: Option<String>,
pub webhook_timestamp: Option<i64>,
pub webhook_id: Option<String>,
pub url: Option<String>,
pub created_at: Option<String>,
pub actor: JsonValue,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LinearIssueChange {
Title { previous: Option<String> },
Description { previous: Option<String> },
Priority { previous: Option<i64> },
Estimate { previous: Option<i64> },
StateId { previous: Option<String> },
TeamId { previous: Option<String> },
AssigneeId { previous: Option<String> },
ProjectId { previous: Option<String> },
CycleId { previous: Option<String> },
DueDate { previous: Option<String> },
ParentId { previous: Option<String> },
SortOrder { previous: Option<f64> },
LabelIds { previous: Vec<String> },
CompletedAt { previous: Option<String> },
Other { field: String, previous: JsonValue },
}
impl Serialize for LinearIssueChange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = match self {
Self::Title { previous } => {
serde_json::json!({ "field_name": "title", "previous": previous })
}
Self::Description { previous } => {
serde_json::json!({ "field_name": "description", "previous": previous })
}
Self::Priority { previous } => {
serde_json::json!({ "field_name": "priority", "previous": previous })
}
Self::Estimate { previous } => {
serde_json::json!({ "field_name": "estimate", "previous": previous })
}
Self::StateId { previous } => {
serde_json::json!({ "field_name": "state_id", "previous": previous })
}
Self::TeamId { previous } => {
serde_json::json!({ "field_name": "team_id", "previous": previous })
}
Self::AssigneeId { previous } => {
serde_json::json!({ "field_name": "assignee_id", "previous": previous })
}
Self::ProjectId { previous } => {
serde_json::json!({ "field_name": "project_id", "previous": previous })
}
Self::CycleId { previous } => {
serde_json::json!({ "field_name": "cycle_id", "previous": previous })
}
Self::DueDate { previous } => {
serde_json::json!({ "field_name": "due_date", "previous": previous })
}
Self::ParentId { previous } => {
serde_json::json!({ "field_name": "parent_id", "previous": previous })
}
Self::SortOrder { previous } => {
serde_json::json!({ "field_name": "sort_order", "previous": previous })
}
Self::LabelIds { previous } => {
serde_json::json!({ "field_name": "label_ids", "previous": previous })
}
Self::CompletedAt { previous } => {
serde_json::json!({ "field_name": "completed_at", "previous": previous })
}
Self::Other { field, previous } => {
serde_json::json!({ "field_name": "other", "field": field, "previous": previous })
}
};
value.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for LinearIssueChange {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = JsonValue::deserialize(deserializer)?;
let field_name = value
.get("field_name")
.and_then(JsonValue::as_str)
.ok_or_else(|| serde::de::Error::custom("linear issue change missing field_name"))?;
let previous = value.get("previous").cloned().unwrap_or(JsonValue::Null);
Ok(match field_name {
"title" => Self::Title {
previous: previous.as_str().map(ToString::to_string),
},
"description" => Self::Description {
previous: previous.as_str().map(ToString::to_string),
},
"priority" => Self::Priority {
previous: parse_json_i64ish(&previous),
},
"estimate" => Self::Estimate {
previous: parse_json_i64ish(&previous),
},
"state_id" => Self::StateId {
previous: previous.as_str().map(ToString::to_string),
},
"team_id" => Self::TeamId {
previous: previous.as_str().map(ToString::to_string),
},
"assignee_id" => Self::AssigneeId {
previous: previous.as_str().map(ToString::to_string),
},
"project_id" => Self::ProjectId {
previous: previous.as_str().map(ToString::to_string),
},
"cycle_id" => Self::CycleId {
previous: previous.as_str().map(ToString::to_string),
},
"due_date" => Self::DueDate {
previous: previous.as_str().map(ToString::to_string),
},
"parent_id" => Self::ParentId {
previous: previous.as_str().map(ToString::to_string),
},
"sort_order" => Self::SortOrder {
previous: previous.as_f64(),
},
"label_ids" => Self::LabelIds {
previous: parse_string_array(&previous),
},
"completed_at" => Self::CompletedAt {
previous: previous.as_str().map(ToString::to_string),
},
"other" => Self::Other {
field: value
.get("field")
.and_then(JsonValue::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| "unknown".to_string()),
previous,
},
other => Self::Other {
field: other.to_string(),
previous,
},
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearIssueEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub issue: JsonValue,
#[serde(default)]
pub changes: Vec<LinearIssueChange>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearIssueCommentEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub comment: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearIssueLabelEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub label: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearProjectEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub project: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearCycleEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub cycle: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearCustomerEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub customer: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LinearCustomerRequestEventPayload {
#[serde(flatten)]
pub common: LinearEventCommon,
pub customer_request: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LinearEventPayload {
Issue(LinearIssueEventPayload),
IssueComment(LinearIssueCommentEventPayload),
IssueLabel(LinearIssueLabelEventPayload),
Project(LinearProjectEventPayload),
Cycle(LinearCycleEventPayload),
Customer(LinearCustomerEventPayload),
CustomerRequest(LinearCustomerRequestEventPayload),
Other(LinearEventCommon),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NotionPolledChangeEvent {
pub resource: String,
pub source_id: String,
pub entity_id: String,
pub high_water_mark: String,
pub before: Option<JsonValue>,
pub after: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NotionEventPayload {
pub event: String,
pub workspace_id: Option<String>,
pub request_id: Option<String>,
pub subscription_id: Option<String>,
pub integration_id: Option<String>,
pub attempt_number: Option<u32>,
pub entity_id: Option<String>,
pub entity_type: Option<String>,
pub api_version: Option<String>,
pub verification_token: Option<String>,
pub polled: Option<NotionPolledChangeEvent>,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CronEventPayload {
pub cron_id: Option<String>,
pub schedule: Option<String>,
#[serde(with = "time::serde::rfc3339")]
pub tick_at: OffsetDateTime,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenericWebhookPayload {
pub source: Option<String>,
pub content_type: Option<String>,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct A2aPushPayload {
pub task_id: Option<String>,
pub task_state: Option<String>,
pub artifact: Option<JsonValue>,
pub sender: Option<String>,
pub raw: JsonValue,
pub kind: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StreamEventPayload {
pub event: String,
pub source: Option<String>,
pub stream: Option<String>,
pub partition: Option<String>,
pub offset: Option<String>,
pub key: Option<String>,
pub timestamp: Option<String>,
#[serde(default)]
pub headers: BTreeMap<String, String>,
pub raw: JsonValue,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExtensionProviderPayload {
pub provider: String,
pub schema_name: String,
pub raw: JsonValue,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProviderPayload {
Known(KnownProviderPayload),
Extension(ExtensionProviderPayload),
}
impl ProviderPayload {
pub fn provider(&self) -> &str {
match self {
Self::Known(known) => known.provider(),
Self::Extension(payload) => payload.provider.as_str(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "provider")]
pub enum KnownProviderPayload {
#[serde(rename = "github")]
GitHub(GitHubEventPayload),
#[serde(rename = "slack")]
Slack(Box<SlackEventPayload>),
#[serde(rename = "linear")]
Linear(LinearEventPayload),
#[serde(rename = "notion")]
Notion(Box<NotionEventPayload>),
#[serde(rename = "cron")]
Cron(CronEventPayload),
#[serde(rename = "webhook")]
Webhook(GenericWebhookPayload),
#[serde(rename = "a2a-push")]
A2aPush(A2aPushPayload),
#[serde(rename = "kafka")]
Kafka(StreamEventPayload),
#[serde(rename = "nats")]
Nats(StreamEventPayload),
#[serde(rename = "pulsar")]
Pulsar(StreamEventPayload),
#[serde(rename = "postgres-cdc")]
PostgresCdc(StreamEventPayload),
#[serde(rename = "email")]
Email(StreamEventPayload),
#[serde(rename = "websocket")]
Websocket(StreamEventPayload),
}
impl KnownProviderPayload {
pub fn provider(&self) -> &str {
match self {
Self::GitHub(_) => "github",
Self::Slack(_) => "slack",
Self::Linear(_) => "linear",
Self::Notion(_) => "notion",
Self::Cron(_) => "cron",
Self::Webhook(_) => "webhook",
Self::A2aPush(_) => "a2a-push",
Self::Kafka(_) => "kafka",
Self::Nats(_) => "nats",
Self::Pulsar(_) => "pulsar",
Self::PostgresCdc(_) => "postgres-cdc",
Self::Email(_) => "email",
Self::Websocket(_) => "websocket",
}
}
}