g1t/crates/contracts/src/webhooks.rs
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Webhooks: every event, to your own addresses, signed and retried | 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. | |
| Agents asked while not at work are woken to answer | 18 | pub const EVENT_TYPES: [&str; 21] = [ |
| Webhooks: every event, to your own addresses, signed and retried | 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", | |
| Agents asked while not at work are woken to answer | 34 | "agent.asked", |
| Webhooks: every event, to your own addresses, signed and retried | 35 | "checks.completed", |
| 36 | "review.completed", | |
| Actions: workflow_run, workflow.completed, artifacts on the run page, Node 24 | 37 | "workflow.completed", |
| Webhooks: every event, to your own addresses, signed and retried | 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 | } |