g1t/crates/contracts/src/webhooks.rs

185 lines5,409 bytesCodeBlame
1//! The webhooks service: events, delivered to the addresses a repository or
2//! a workspace registers.
3//!
4//! Every event g1t publishes can be delivered: an HTTPS `POST` of JSON,
5//! signed with the webhook's secret in `X-G1t-Signature-256`, and retried
6//! with growing waits when the receiver does not answer with a 2xx. Each
7//! delivery is kept, with what was sent and what came back, and can be sent
8//! again.
9//!
10//! Mirrors `packages/contracts/src/webhooks.ts`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::RepoPath;
15use crate::{User, Viewer};
16
17/// Every event a webhook can be sent, in the order people are shown them.
18pub const EVENT_TYPES: [&str; 20] = [
19 "git.push",
20 "repo.created",
21 "repo.forked",
22 "issue.opened",
23 "issue.updated",
24 "issue.assigned",
25 "issue.closed",
26 "issue.reopened",
27 "comment.created",
28 "pull.opened",
29 "pull.ready",
30 "pull.updated",
31 "pull.merge_requested",
32 "pull.merged",
33 "pull.closed",
34 "checks.completed",
35 "review.completed",
36 "workflow.completed",
37 "queue.changed",
38 "session.appended",
39];
40
41/// What a webhook belongs to.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum HookScope {
45 /// One repository's events.
46 Repo,
47 /// The events of every repository in a workspace.
48 Workspace,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct Hook {
54 pub id: String,
55 pub scope: HookScope,
56 pub workspace: String,
57 /// For a repository's webhook: `owner/name`.
58 pub repo: Option<String>,
59 pub url: String,
60 /// The event types it is sent, or `["*"]` for all.
61 pub events: Vec<String>,
62 pub active: bool,
63 /// The last four characters of its secret.
64 pub secret_hint: String,
65 pub created_by: String,
66 /// RFC 3339.
67 pub created_at: String,
68 /// How its latest delivery went: `delivered`, `pending` or `failed`.
69 pub last_status: Option<String>,
70 pub last_delivered_at: Option<String>,
71}
72
73/// One event sent, or being sent, to a webhook.
74#[derive(Clone, Debug, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct HookDelivery {
77 pub id: String,
78 pub hook_id: String,
79 /// The event's id, or empty for a ping.
80 pub event_id: String,
81 pub event: String,
82 /// `pending` while it will be tried again, `delivered`, or `failed` once
83 /// it has been tried as often as it will be.
84 pub status: String,
85 pub attempts: u32,
86 /// The receiver's HTTP status, the last time it answered.
87 pub response_status: Option<u16>,
88 /// The start of what it answered.
89 pub response_body: Option<String>,
90 /// Why the last attempt failed, when the receiver could not be reached.
91 pub error: Option<String>,
92 pub duration_ms: Option<u32>,
93 /// The JSON that was sent.
94 pub payload: String,
95 /// RFC 3339.
96 pub created_at: String,
97 pub delivered_at: Option<String>,
98 pub next_attempt_at: Option<String>,
99}
100
101/// Which webhooks a call is about: a repository's, or with `repo` left out,
102/// the workspace's own.
103#[derive(Clone, Debug, Serialize, Deserialize)]
104pub struct HookOwner {
105 pub workspace: String,
106 #[serde(default)]
107 pub repo: Option<RepoPath>,
108}
109
110/// `list`. Returns `Outcome<Vec<Hook>>`. Members of the workspace only.
111#[derive(Debug, Serialize, Deserialize)]
112pub struct ListArgs {
113 pub viewer: Viewer,
114 #[serde(flatten)]
115 pub owner: HookOwner,
116}
117
118/// `create`. Returns `Outcome<CreatedHook>`. Members, for a repository's
119/// webhooks; owners, for the workspace's.
120#[derive(Debug, Serialize, Deserialize)]
121pub struct CreateArgs {
122 pub actor: User,
123 #[serde(flatten)]
124 pub owner: HookOwner,
125 pub url: String,
126 /// Event types, or `["*"]` for all. All when empty.
127 #[serde(default)]
128 pub events: Vec<String>,
129 /// Made by g1t when left out.
130 #[serde(default)]
131 pub secret: Option<String>,
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize)]
135pub struct CreatedHook {
136 pub hook: Hook,
137 /// The secret, when g1t made it: shown this once.
138 pub secret: Option<String>,
139}
140
141/// `update`: only the fields given change. Returns `Outcome<Hook>`.
142#[derive(Debug, Serialize, Deserialize)]
143pub struct UpdateArgs {
144 pub actor: User,
145 #[serde(flatten)]
146 pub owner: HookOwner,
147 pub id: String,
148 #[serde(default)]
149 pub url: Option<String>,
150 #[serde(default)]
151 pub events: Option<Vec<String>>,
152 #[serde(default)]
153 pub active: Option<bool>,
154}
155
156/// `delete` (returns `Outcome<bool>`) and `ping` (sends a `ping` event and
157/// returns `Outcome<HookDelivery>`).
158#[derive(Debug, Serialize, Deserialize)]
159pub struct HookArgs {
160 pub actor: User,
161 #[serde(flatten)]
162 pub owner: HookOwner,
163 pub id: String,
164}
165
166/// `deliveries`: a webhook's latest deliveries, newest first. Returns
167/// `Outcome<Vec<HookDelivery>>`.
168#[derive(Debug, Serialize, Deserialize)]
169pub struct DeliveriesArgs {
170 pub viewer: Viewer,
171 #[serde(flatten)]
172 pub owner: HookOwner,
173 pub id: String,
174}
175
176/// `redeliver`: sends a delivery's payload again, as a new delivery.
177/// Returns `Outcome<HookDelivery>`.
178#[derive(Debug, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct RedeliverArgs {
181 pub actor: User,
182 #[serde(flatten)]
183 pub owner: HookOwner,
184 pub delivery_id: String,
185}