g1t/crates/contracts/src/webhooks.rs

184 lines5,383 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; 19] = [
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 "queue.changed",
37 "session.appended",
38];
39
40/// What a webhook belongs to.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum HookScope {
44 /// One repository's events.
45 Repo,
46 /// The events of every repository in a workspace.
47 Workspace,
48}
49
50#[derive(Clone, Debug, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct Hook {
53 pub id: String,
54 pub scope: HookScope,
55 pub workspace: String,
56 /// For a repository's webhook: `owner/name`.
57 pub repo: Option<String>,
58 pub url: String,
59 /// The event types it is sent, or `["*"]` for all.
60 pub events: Vec<String>,
61 pub active: bool,
62 /// The last four characters of its secret.
63 pub secret_hint: String,
64 pub created_by: String,
65 /// RFC 3339.
66 pub created_at: String,
67 /// How its latest delivery went: `delivered`, `pending` or `failed`.
68 pub last_status: Option<String>,
69 pub last_delivered_at: Option<String>,
70}
71
72/// One event sent, or being sent, to a webhook.
73#[derive(Clone, Debug, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct HookDelivery {
76 pub id: String,
77 pub hook_id: String,
78 /// The event's id, or empty for a ping.
79 pub event_id: String,
80 pub event: String,
81 /// `pending` while it will be tried again, `delivered`, or `failed` once
82 /// it has been tried as often as it will be.
83 pub status: String,
84 pub attempts: u32,
85 /// The receiver's HTTP status, the last time it answered.
86 pub response_status: Option<u16>,
87 /// The start of what it answered.
88 pub response_body: Option<String>,
89 /// Why the last attempt failed, when the receiver could not be reached.
90 pub error: Option<String>,
91 pub duration_ms: Option<u32>,
92 /// The JSON that was sent.
93 pub payload: String,
94 /// RFC 3339.
95 pub created_at: String,
96 pub delivered_at: Option<String>,
97 pub next_attempt_at: Option<String>,
98}
99
100/// Which webhooks a call is about: a repository's, or with `repo` left out,
101/// the workspace's own.
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct HookOwner {
104 pub workspace: String,
105 #[serde(default)]
106 pub repo: Option<RepoPath>,
107}
108
109/// `list`. Returns `Outcome<Vec<Hook>>`. Members of the workspace only.
110#[derive(Debug, Serialize, Deserialize)]
111pub struct ListArgs {
112 pub viewer: Viewer,
113 #[serde(flatten)]
114 pub owner: HookOwner,
115}
116
117/// `create`. Returns `Outcome<CreatedHook>`. Members, for a repository's
118/// webhooks; owners, for the workspace's.
119#[derive(Debug, Serialize, Deserialize)]
120pub struct CreateArgs {
121 pub actor: User,
122 #[serde(flatten)]
123 pub owner: HookOwner,
124 pub url: String,
125 /// Event types, or `["*"]` for all. All when empty.
126 #[serde(default)]
127 pub events: Vec<String>,
128 /// Made by g1t when left out.
129 #[serde(default)]
130 pub secret: Option<String>,
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
134pub struct CreatedHook {
135 pub hook: Hook,
136 /// The secret, when g1t made it: shown this once.
137 pub secret: Option<String>,
138}
139
140/// `update`: only the fields given change. Returns `Outcome<Hook>`.
141#[derive(Debug, Serialize, Deserialize)]
142pub struct UpdateArgs {
143 pub actor: User,
144 #[serde(flatten)]
145 pub owner: HookOwner,
146 pub id: String,
147 #[serde(default)]
148 pub url: Option<String>,
149 #[serde(default)]
150 pub events: Option<Vec<String>>,
151 #[serde(default)]
152 pub active: Option<bool>,
153}
154
155/// `delete` (returns `Outcome<bool>`) and `ping` (sends a `ping` event and
156/// returns `Outcome<HookDelivery>`).
157#[derive(Debug, Serialize, Deserialize)]
158pub struct HookArgs {
159 pub actor: User,
160 #[serde(flatten)]
161 pub owner: HookOwner,
162 pub id: String,
163}
164
165/// `deliveries`: a webhook's latest deliveries, newest first. Returns
166/// `Outcome<Vec<HookDelivery>>`.
167#[derive(Debug, Serialize, Deserialize)]
168pub struct DeliveriesArgs {
169 pub viewer: Viewer,
170 #[serde(flatten)]
171 pub owner: HookOwner,
172 pub id: String,
173}
174
175/// `redeliver`: sends a delivery's payload again, as a new delivery.
176/// Returns `Outcome<HookDelivery>`.
177#[derive(Debug, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct RedeliverArgs {
180 pub actor: User,
181 #[serde(flatten)]
182 pub owner: HookOwner,
183 pub delivery_id: String,
184}