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