Skip to main content

harn_vm/triggers/event/
payloads.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use serde_json::Value as JsonValue;
5use time::OffsetDateTime;
6
7use super::util::{parse_json_i64ish, parse_string_array};
8
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub struct GitHubEventCommon {
11    pub event: String,
12    pub action: Option<String>,
13    pub delivery_id: Option<String>,
14    pub installation_id: Option<i64>,
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub topic: Option<String>,
17    #[serde(default)]
18    pub reaction_topics: Vec<String>,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub repository: Option<JsonValue>,
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub repo: Option<JsonValue>,
23    pub raw: JsonValue,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub struct GitHubIssuesEventPayload {
28    #[serde(flatten)]
29    pub common: GitHubEventCommon,
30    pub issue: JsonValue,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34pub struct GitHubPullRequestEventPayload {
35    #[serde(flatten)]
36    pub common: GitHubEventCommon,
37    pub pull_request: JsonValue,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct GitHubIssueCommentEventPayload {
42    #[serde(flatten)]
43    pub common: GitHubEventCommon,
44    pub issue: JsonValue,
45    pub comment: JsonValue,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
49pub struct GitHubPullRequestReviewEventPayload {
50    #[serde(flatten)]
51    pub common: GitHubEventCommon,
52    pub pull_request: JsonValue,
53    pub review: JsonValue,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
57pub struct GitHubPushEventPayload {
58    #[serde(flatten)]
59    pub common: GitHubEventCommon,
60    #[serde(default)]
61    pub commits: Vec<JsonValue>,
62    pub distinct_size: Option<i64>,
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66pub struct GitHubWorkflowRunEventPayload {
67    #[serde(flatten)]
68    pub common: GitHubEventCommon,
69    pub workflow_run: JsonValue,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
73pub struct GitHubDeploymentStatusEventPayload {
74    #[serde(flatten)]
75    pub common: GitHubEventCommon,
76    pub deployment_status: JsonValue,
77    pub deployment: JsonValue,
78}
79
80#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
81pub struct GitHubCheckRunEventPayload {
82    #[serde(flatten)]
83    pub common: GitHubEventCommon,
84    pub check_run: JsonValue,
85}
86
87#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
88pub struct GitHubCheckSuiteEventPayload {
89    #[serde(flatten)]
90    pub common: GitHubEventCommon,
91    pub check_suite: JsonValue,
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub check_suite_id: Option<i64>,
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub pull_request_number: Option<i64>,
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub head_sha: Option<String>,
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub head_ref: Option<String>,
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub base_ref: Option<String>,
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub status: Option<String>,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub conclusion: Option<String>,
106}
107
108#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
109pub struct GitHubStatusEventPayload {
110    #[serde(flatten)]
111    pub common: GitHubEventCommon,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub commit_status: Option<JsonValue>,
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub status_id: Option<i64>,
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub head_sha: Option<String>,
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub head_ref: Option<String>,
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub base_ref: Option<String>,
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub state: Option<String>,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub context: Option<String>,
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub target_url: Option<String>,
128}
129
130#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
131pub struct GitHubMergeGroupEventPayload {
132    #[serde(flatten)]
133    pub common: GitHubEventCommon,
134    pub merge_group: JsonValue,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub merge_group_id: Option<JsonValue>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub head_sha: Option<String>,
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub head_ref: Option<String>,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub base_sha: Option<String>,
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub base_ref: Option<String>,
145    #[serde(default)]
146    pub pull_requests: Vec<JsonValue>,
147    #[serde(default)]
148    pub pull_request_numbers: Vec<i64>,
149}
150
151#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
152pub struct GitHubInstallationEventPayload {
153    #[serde(flatten)]
154    pub common: GitHubEventCommon,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub installation: Option<JsonValue>,
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    pub account: Option<JsonValue>,
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub installation_state: Option<String>,
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub suspended: Option<bool>,
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub revoked: Option<bool>,
165    #[serde(default)]
166    pub repositories: Vec<JsonValue>,
167}
168
169#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
170pub struct GitHubInstallationRepositoriesEventPayload {
171    #[serde(flatten)]
172    pub common: GitHubEventCommon,
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub installation: Option<JsonValue>,
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub account: Option<JsonValue>,
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub installation_state: Option<String>,
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub suspended: Option<bool>,
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub revoked: Option<bool>,
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub repository_selection: Option<String>,
185    #[serde(default)]
186    pub repositories_added: Vec<JsonValue>,
187    #[serde(default)]
188    pub repositories_removed: Vec<JsonValue>,
189}
190
191#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
192#[serde(untagged)]
193pub enum GitHubEventPayload {
194    Issues(GitHubIssuesEventPayload),
195    PullRequest(GitHubPullRequestEventPayload),
196    IssueComment(GitHubIssueCommentEventPayload),
197    PullRequestReview(GitHubPullRequestReviewEventPayload),
198    Push(GitHubPushEventPayload),
199    WorkflowRun(GitHubWorkflowRunEventPayload),
200    DeploymentStatus(GitHubDeploymentStatusEventPayload),
201    CheckRun(GitHubCheckRunEventPayload),
202    CheckSuite(GitHubCheckSuiteEventPayload),
203    Status(GitHubStatusEventPayload),
204    MergeGroup(GitHubMergeGroupEventPayload),
205    Installation(GitHubInstallationEventPayload),
206    InstallationRepositories(GitHubInstallationRepositoriesEventPayload),
207    Other(GitHubEventCommon),
208}
209
210// Manual `Deserialize` that dispatches on the `event` field. An untagged
211// enum cannot reliably round-trip these variants because `Push` and the
212// all-optional installation/status variants will accept payloads that
213// belong to a different event kind.
214impl<'de> Deserialize<'de> for GitHubEventPayload {
215    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
216    where
217        D: Deserializer<'de>,
218    {
219        let value = JsonValue::deserialize(deserializer)?;
220        let kind = value
221            .get("event")
222            .and_then(JsonValue::as_str)
223            .unwrap_or("")
224            .to_string();
225        let from_value = |v: JsonValue| -> Result<GitHubEventPayload, D::Error> {
226            let payload = match kind.as_str() {
227                "issues" => GitHubEventPayload::Issues(
228                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
229                ),
230                "pull_request" => GitHubEventPayload::PullRequest(
231                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
232                ),
233                "issue_comment" => GitHubEventPayload::IssueComment(
234                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
235                ),
236                "pull_request_review" => GitHubEventPayload::PullRequestReview(
237                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
238                ),
239                "push" => GitHubEventPayload::Push(
240                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
241                ),
242                "workflow_run" => GitHubEventPayload::WorkflowRun(
243                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
244                ),
245                "deployment_status" => GitHubEventPayload::DeploymentStatus(
246                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
247                ),
248                "check_run" => GitHubEventPayload::CheckRun(
249                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
250                ),
251                "check_suite" => GitHubEventPayload::CheckSuite(
252                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
253                ),
254                "status" => GitHubEventPayload::Status(
255                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
256                ),
257                "merge_group" => GitHubEventPayload::MergeGroup(
258                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
259                ),
260                "installation" => GitHubEventPayload::Installation(
261                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
262                ),
263                "installation_repositories" => GitHubEventPayload::InstallationRepositories(
264                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
265                ),
266                _ => GitHubEventPayload::Other(
267                    serde_json::from_value(v).map_err(serde::de::Error::custom)?,
268                ),
269            };
270            Ok(payload)
271        };
272        from_value(value)
273    }
274}
275
276#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
277pub struct SlackEventCommon {
278    pub event: String,
279    pub event_id: Option<String>,
280    pub api_app_id: Option<String>,
281    pub team_id: Option<String>,
282    pub channel_id: Option<String>,
283    pub user_id: Option<String>,
284    pub event_ts: Option<String>,
285    pub raw: JsonValue,
286}
287
288#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
289pub struct SlackMessageEventPayload {
290    #[serde(flatten)]
291    pub common: SlackEventCommon,
292    pub subtype: Option<String>,
293    pub channel_type: Option<String>,
294    pub channel: Option<String>,
295    pub user: Option<String>,
296    pub text: Option<String>,
297    pub ts: Option<String>,
298    pub thread_ts: Option<String>,
299}
300
301#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
302pub struct SlackAppMentionEventPayload {
303    #[serde(flatten)]
304    pub common: SlackEventCommon,
305    pub channel: Option<String>,
306    pub user: Option<String>,
307    pub text: Option<String>,
308    pub ts: Option<String>,
309    pub thread_ts: Option<String>,
310}
311
312#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
313pub struct SlackReactionAddedEventPayload {
314    #[serde(flatten)]
315    pub common: SlackEventCommon,
316    pub reaction: Option<String>,
317    pub item_user: Option<String>,
318    pub item: JsonValue,
319}
320
321#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
322pub struct SlackAppHomeOpenedEventPayload {
323    #[serde(flatten)]
324    pub common: SlackEventCommon,
325    pub user: Option<String>,
326    pub channel: Option<String>,
327    pub tab: Option<String>,
328    pub view: JsonValue,
329}
330
331#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
332pub struct SlackAssistantThreadStartedEventPayload {
333    #[serde(flatten)]
334    pub common: SlackEventCommon,
335    pub assistant_thread: JsonValue,
336    pub thread_ts: Option<String>,
337    pub context: JsonValue,
338}
339
340#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
341#[serde(untagged)]
342pub enum SlackEventPayload {
343    Message(SlackMessageEventPayload),
344    AppMention(SlackAppMentionEventPayload),
345    ReactionAdded(SlackReactionAddedEventPayload),
346    AppHomeOpened(SlackAppHomeOpenedEventPayload),
347    AssistantThreadStarted(SlackAssistantThreadStartedEventPayload),
348    Other(SlackEventCommon),
349}
350
351#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
352pub struct LinearEventCommon {
353    pub event: String,
354    pub action: Option<String>,
355    pub delivery_id: Option<String>,
356    pub organization_id: Option<String>,
357    pub webhook_timestamp: Option<i64>,
358    pub webhook_id: Option<String>,
359    pub url: Option<String>,
360    pub created_at: Option<String>,
361    pub actor: JsonValue,
362    pub raw: JsonValue,
363}
364
365#[derive(Clone, Debug, PartialEq)]
366pub enum LinearIssueChange {
367    Title { previous: Option<String> },
368    Description { previous: Option<String> },
369    Priority { previous: Option<i64> },
370    Estimate { previous: Option<i64> },
371    StateId { previous: Option<String> },
372    TeamId { previous: Option<String> },
373    AssigneeId { previous: Option<String> },
374    ProjectId { previous: Option<String> },
375    CycleId { previous: Option<String> },
376    DueDate { previous: Option<String> },
377    ParentId { previous: Option<String> },
378    SortOrder { previous: Option<f64> },
379    LabelIds { previous: Vec<String> },
380    CompletedAt { previous: Option<String> },
381    Other { field: String, previous: JsonValue },
382}
383
384impl Serialize for LinearIssueChange {
385    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
386    where
387        S: Serializer,
388    {
389        let value = match self {
390            Self::Title { previous } => {
391                serde_json::json!({ "field_name": "title", "previous": previous })
392            }
393            Self::Description { previous } => {
394                serde_json::json!({ "field_name": "description", "previous": previous })
395            }
396            Self::Priority { previous } => {
397                serde_json::json!({ "field_name": "priority", "previous": previous })
398            }
399            Self::Estimate { previous } => {
400                serde_json::json!({ "field_name": "estimate", "previous": previous })
401            }
402            Self::StateId { previous } => {
403                serde_json::json!({ "field_name": "state_id", "previous": previous })
404            }
405            Self::TeamId { previous } => {
406                serde_json::json!({ "field_name": "team_id", "previous": previous })
407            }
408            Self::AssigneeId { previous } => {
409                serde_json::json!({ "field_name": "assignee_id", "previous": previous })
410            }
411            Self::ProjectId { previous } => {
412                serde_json::json!({ "field_name": "project_id", "previous": previous })
413            }
414            Self::CycleId { previous } => {
415                serde_json::json!({ "field_name": "cycle_id", "previous": previous })
416            }
417            Self::DueDate { previous } => {
418                serde_json::json!({ "field_name": "due_date", "previous": previous })
419            }
420            Self::ParentId { previous } => {
421                serde_json::json!({ "field_name": "parent_id", "previous": previous })
422            }
423            Self::SortOrder { previous } => {
424                serde_json::json!({ "field_name": "sort_order", "previous": previous })
425            }
426            Self::LabelIds { previous } => {
427                serde_json::json!({ "field_name": "label_ids", "previous": previous })
428            }
429            Self::CompletedAt { previous } => {
430                serde_json::json!({ "field_name": "completed_at", "previous": previous })
431            }
432            Self::Other { field, previous } => {
433                serde_json::json!({ "field_name": "other", "field": field, "previous": previous })
434            }
435        };
436        value.serialize(serializer)
437    }
438}
439
440impl<'de> Deserialize<'de> for LinearIssueChange {
441    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442    where
443        D: Deserializer<'de>,
444    {
445        let value = JsonValue::deserialize(deserializer)?;
446        let field_name = value
447            .get("field_name")
448            .and_then(JsonValue::as_str)
449            .ok_or_else(|| serde::de::Error::custom("linear issue change missing field_name"))?;
450        let previous = value.get("previous").cloned().unwrap_or(JsonValue::Null);
451        Ok(match field_name {
452            "title" => Self::Title {
453                previous: previous.as_str().map(ToString::to_string),
454            },
455            "description" => Self::Description {
456                previous: previous.as_str().map(ToString::to_string),
457            },
458            "priority" => Self::Priority {
459                previous: parse_json_i64ish(&previous),
460            },
461            "estimate" => Self::Estimate {
462                previous: parse_json_i64ish(&previous),
463            },
464            "state_id" => Self::StateId {
465                previous: previous.as_str().map(ToString::to_string),
466            },
467            "team_id" => Self::TeamId {
468                previous: previous.as_str().map(ToString::to_string),
469            },
470            "assignee_id" => Self::AssigneeId {
471                previous: previous.as_str().map(ToString::to_string),
472            },
473            "project_id" => Self::ProjectId {
474                previous: previous.as_str().map(ToString::to_string),
475            },
476            "cycle_id" => Self::CycleId {
477                previous: previous.as_str().map(ToString::to_string),
478            },
479            "due_date" => Self::DueDate {
480                previous: previous.as_str().map(ToString::to_string),
481            },
482            "parent_id" => Self::ParentId {
483                previous: previous.as_str().map(ToString::to_string),
484            },
485            "sort_order" => Self::SortOrder {
486                previous: previous.as_f64(),
487            },
488            "label_ids" => Self::LabelIds {
489                previous: parse_string_array(&previous),
490            },
491            "completed_at" => Self::CompletedAt {
492                previous: previous.as_str().map(ToString::to_string),
493            },
494            "other" => Self::Other {
495                field: value
496                    .get("field")
497                    .and_then(JsonValue::as_str)
498                    .map(ToString::to_string)
499                    .unwrap_or_else(|| "unknown".to_string()),
500                previous,
501            },
502            other => Self::Other {
503                field: other.to_string(),
504                previous,
505            },
506        })
507    }
508}
509
510#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
511pub struct LinearIssueEventPayload {
512    #[serde(flatten)]
513    pub common: LinearEventCommon,
514    pub issue: JsonValue,
515    #[serde(default)]
516    pub changes: Vec<LinearIssueChange>,
517}
518
519#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
520pub struct LinearIssueCommentEventPayload {
521    #[serde(flatten)]
522    pub common: LinearEventCommon,
523    pub comment: JsonValue,
524}
525
526#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
527pub struct LinearIssueLabelEventPayload {
528    #[serde(flatten)]
529    pub common: LinearEventCommon,
530    pub label: JsonValue,
531}
532
533#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
534pub struct LinearProjectEventPayload {
535    #[serde(flatten)]
536    pub common: LinearEventCommon,
537    pub project: JsonValue,
538}
539
540#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
541pub struct LinearCycleEventPayload {
542    #[serde(flatten)]
543    pub common: LinearEventCommon,
544    pub cycle: JsonValue,
545}
546
547#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
548pub struct LinearCustomerEventPayload {
549    #[serde(flatten)]
550    pub common: LinearEventCommon,
551    pub customer: JsonValue,
552}
553
554#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
555pub struct LinearCustomerRequestEventPayload {
556    #[serde(flatten)]
557    pub common: LinearEventCommon,
558    pub customer_request: JsonValue,
559}
560
561#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
562#[serde(untagged)]
563pub enum LinearEventPayload {
564    Issue(LinearIssueEventPayload),
565    IssueComment(LinearIssueCommentEventPayload),
566    IssueLabel(LinearIssueLabelEventPayload),
567    Project(LinearProjectEventPayload),
568    Cycle(LinearCycleEventPayload),
569    Customer(LinearCustomerEventPayload),
570    CustomerRequest(LinearCustomerRequestEventPayload),
571    Other(LinearEventCommon),
572}
573
574#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
575pub struct NotionPolledChangeEvent {
576    pub resource: String,
577    pub source_id: String,
578    pub entity_id: String,
579    pub high_water_mark: String,
580    pub before: Option<JsonValue>,
581    pub after: JsonValue,
582}
583
584#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
585pub struct NotionEventPayload {
586    pub event: String,
587    pub workspace_id: Option<String>,
588    pub request_id: Option<String>,
589    pub subscription_id: Option<String>,
590    pub integration_id: Option<String>,
591    pub attempt_number: Option<u32>,
592    pub entity_id: Option<String>,
593    pub entity_type: Option<String>,
594    pub api_version: Option<String>,
595    pub verification_token: Option<String>,
596    pub polled: Option<NotionPolledChangeEvent>,
597    pub raw: JsonValue,
598}
599
600#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
601pub struct CronEventPayload {
602    pub cron_id: Option<String>,
603    pub schedule: Option<String>,
604    #[serde(with = "time::serde::rfc3339")]
605    pub tick_at: OffsetDateTime,
606    pub raw: JsonValue,
607}
608
609#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
610pub struct GenericWebhookPayload {
611    pub source: Option<String>,
612    pub content_type: Option<String>,
613    pub raw: JsonValue,
614}
615
616#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
617pub struct A2aPushPayload {
618    pub task_id: Option<String>,
619    pub task_state: Option<String>,
620    pub artifact: Option<JsonValue>,
621    pub sender: Option<String>,
622    #[serde(default, skip_serializing_if = "Option::is_none")]
623    pub actor_chain: Option<JsonValue>,
624    pub raw: JsonValue,
625    pub kind: String,
626}
627
628#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
629pub struct StreamEventPayload {
630    pub event: String,
631    pub source: Option<String>,
632    pub stream: Option<String>,
633    pub partition: Option<String>,
634    pub offset: Option<String>,
635    pub key: Option<String>,
636    pub timestamp: Option<String>,
637    #[serde(default)]
638    pub headers: BTreeMap<String, String>,
639    pub raw: JsonValue,
640}
641
642/// Payload emitted by `emit_channel(...)` to channel-source triggers.
643///
644/// The dispatcher fan-out path (CH-02 / #1872) synthesizes one
645/// [`TriggerEvent`](crate::triggers::TriggerEvent) per matching channel
646/// trigger using `provider = "channel"` and `kind = "channel.emit"`. The
647/// resolved channel coordinates (`scope`, `scope_id`, `name`, fully
648/// qualified `name_resolved`) and the user-supplied `payload` ride along
649/// here so handlers can read them off the trigger event without re-parsing
650/// the resolved name string.
651#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
652pub struct ChannelEventPayload {
653    pub id: String,
654    pub name: String,
655    pub name_resolved: String,
656    pub scope: String,
657    pub scope_id: String,
658    pub payload: JsonValue,
659    pub emitted_by: String,
660    #[serde(skip_serializing_if = "Option::is_none")]
661    pub tenant_id: Option<String>,
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub session_id: Option<String>,
664    #[serde(skip_serializing_if = "Option::is_none")]
665    pub pipeline_id: Option<String>,
666}
667
668#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
669pub struct ExtensionProviderPayload {
670    pub provider: String,
671    pub schema_name: String,
672    pub raw: JsonValue,
673}
674
675#[allow(clippy::large_enum_variant)]
676#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
677#[serde(untagged)]
678pub enum ProviderPayload {
679    Known(KnownProviderPayload),
680    Extension(ExtensionProviderPayload),
681}
682
683impl ProviderPayload {
684    pub fn provider(&self) -> &str {
685        match self {
686            Self::Known(known) => known.provider(),
687            Self::Extension(payload) => payload.provider.as_str(),
688        }
689    }
690}
691
692#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
693#[serde(tag = "provider")]
694pub enum KnownProviderPayload {
695    #[serde(rename = "github")]
696    GitHub(Box<GitHubEventPayload>),
697    #[serde(rename = "slack")]
698    Slack(Box<SlackEventPayload>),
699    #[serde(rename = "linear")]
700    Linear(LinearEventPayload),
701    #[serde(rename = "notion")]
702    Notion(Box<NotionEventPayload>),
703    #[serde(rename = "cron")]
704    Cron(CronEventPayload),
705    #[serde(rename = "webhook")]
706    Webhook(GenericWebhookPayload),
707    #[serde(rename = "a2a-push")]
708    A2aPush(A2aPushPayload),
709    #[serde(rename = "kafka")]
710    Kafka(StreamEventPayload),
711    #[serde(rename = "nats")]
712    Nats(StreamEventPayload),
713    #[serde(rename = "pulsar")]
714    Pulsar(StreamEventPayload),
715    #[serde(rename = "postgres-cdc")]
716    PostgresCdc(StreamEventPayload),
717    #[serde(rename = "email")]
718    Email(StreamEventPayload),
719    #[serde(rename = "websocket")]
720    Websocket(StreamEventPayload),
721    #[serde(rename = "channel")]
722    Channel(ChannelEventPayload),
723}
724
725impl KnownProviderPayload {
726    pub fn provider(&self) -> &str {
727        match self {
728            Self::GitHub(_) => "github",
729            Self::Slack(_) => "slack",
730            Self::Linear(_) => "linear",
731            Self::Notion(_) => "notion",
732            Self::Cron(_) => "cron",
733            Self::Webhook(_) => "webhook",
734            Self::A2aPush(_) => "a2a-push",
735            Self::Kafka(_) => "kafka",
736            Self::Nats(_) => "nats",
737            Self::Pulsar(_) => "pulsar",
738            Self::PostgresCdc(_) => "postgres-cdc",
739            Self::Email(_) => "email",
740            Self::Websocket(_) => "websocket",
741            Self::Channel(_) => "channel",
742        }
743    }
744}