forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
5.1 KiB
1//! Repo webhooks: the event names, the hook and delivery shapes. The stored shape is in
2//! migrations/0011_webhooks.sql; the store functions live in store/webhooks.rs.
3//!
4//! A hook never carries its secret. The secret is sealed with the instance key (see `secret.rs`)
5//! by the caller and stored as `secret_sealed`; it is read back only through
6//! `Store::webhook_secret`.
7
8/// Every event a hook can subscribe to. The `as_str` names are stored and sent in `X-Forge-Event`.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum WebhookEvent {
11 Push,
12 PullRequest,
13 Issues,
14 IssueComment,
15 PullRequestReview,
16 Create,
17 Delete,
18 Ping,
19 Release,
20}
21
22impl WebhookEvent {
23 pub const ALL: [WebhookEvent; 9] = [
24 WebhookEvent::Push,
25 WebhookEvent::PullRequest,
26 WebhookEvent::Issues,
27 WebhookEvent::IssueComment,
28 WebhookEvent::PullRequestReview,
29 WebhookEvent::Create,
30 WebhookEvent::Delete,
31 WebhookEvent::Ping,
32 WebhookEvent::Release,
33 ];
34
35 pub fn as_str(self) -> &'static str {
36 match self {
37 WebhookEvent::Push => "push",
38 WebhookEvent::PullRequest => "pull_request",
39 WebhookEvent::Issues => "issues",
40 WebhookEvent::IssueComment => "issue_comment",
41 WebhookEvent::PullRequestReview => "pull_request_review",
42 WebhookEvent::Create => "create",
43 WebhookEvent::Delete => "delete",
44 WebhookEvent::Ping => "ping",
45 WebhookEvent::Release => "release",
46 }
47 }
48
49 pub fn parse(s: &str) -> Option<WebhookEvent> {
50 WebhookEvent::ALL.into_iter().find(|e| e.as_str() == s)
51 }
52}
53
54/// Delivery state. Stored as the `as_str` name.
55/// `Pending`: queued, never attempted. `Failed`: an attempt failed and a retry is scheduled.
56/// `Ok`: a 2xx response was received. `GaveUp`: no further attempts.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum DeliveryState {
59 Pending,
60 Ok,
61 Failed,
62 GaveUp,
63}
64
65impl DeliveryState {
66 pub fn as_str(self) -> &'static str {
67 match self {
68 DeliveryState::Pending => "pending",
69 DeliveryState::Ok => "ok",
70 DeliveryState::Failed => "failed",
71 DeliveryState::GaveUp => "gave_up",
72 }
73 }
74
75 pub fn parse(s: &str) -> Option<DeliveryState> {
76 match s {
77 "pending" => Some(DeliveryState::Pending),
78 "ok" => Some(DeliveryState::Ok),
79 "failed" => Some(DeliveryState::Failed),
80 "gave_up" => Some(DeliveryState::GaveUp),
81 _ => None,
82 }
83 }
84}
85
86/// A repo webhook. Has no secret field; see `Store::webhook_secret`.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Webhook {
89 pub id: String,
90 pub repo_id: String,
91 pub url: String,
92 pub events: Vec<WebhookEvent>,
93 pub active: bool,
94 pub created_at: String,
95 pub updated_at: String,
96}
97
98/// One delivery attempt chain for one event. `request_*` is immutable; the rest is updated as
99/// attempts happen.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct Delivery {
102 pub id: String,
103 pub hook_id: String,
104 /// The `X-Forge-Delivery` value.
105 pub guid: String,
106 pub event: WebhookEvent,
107 /// The payload's action, or empty when the event has none.
108 pub action: String,
109 /// JSON object of the request headers that were sent.
110 pub request_headers: String,
111 pub request_body: String,
112 pub state: DeliveryState,
113 /// Attempts started. Incremented when the delivery is claimed.
114 pub attempt: u32,
115 /// When the next attempt is due. `None` once the delivery is `Ok` or `GaveUp`.
116 pub next_attempt_at: Option<String>,
117 pub response_status: Option<u16>,
118 /// JSON object of the response headers.
119 pub response_headers: Option<String>,
120 /// The first 64 KiB of the response body.
121 pub response_body: Option<String>,
122 /// Transport error text, such as a refused connection or a blocked address.
123 pub error: Option<String>,
124 pub duration_ms: Option<u64>,
125 pub created_at: String,
126 pub delivered_at: Option<String>,
127}
128
129/// The outcome of one attempt, recorded by `Store::record_delivery_result`.
130///
131/// `ok` true sets the state to `Ok`. `ok` false with `next_attempt_at` set sets it to `Failed`
132/// and schedules a retry. `ok` false with `next_attempt_at` `None` sets it to `GaveUp`.
133#[derive(Debug, Clone, Default, PartialEq, Eq)]
134pub struct AttemptOutcome {
135 pub ok: bool,
136 pub status: Option<u16>,
137 pub response_headers: Option<String>,
138 /// Truncated to 64 KiB by the store, at a character boundary.
139 pub response_body: Option<String>,
140 pub error: Option<String>,
141 pub duration_ms: Option<u64>,
142 pub next_attempt_at: Option<String>,
143 /// The headers actually sent on this attempt, as a JSON object. When `Some`, it replaces the
144 /// stored request headers, so the delivery log shows what the receiver got (including the
145 /// GUID and signature the worker adds). `None` keeps what was stored at enqueue time.
146 pub request_headers: Option<String>,
147}
148
149/// Largest response body kept, in bytes.
150pub const MAX_RESPONSE_BODY_BYTES: usize = 64 * 1024;