Skip to main content

cac_webhook/
payload.rs

1use serde::Deserialize;
2
3#[derive(Debug, Clone, Deserialize)]
4pub struct PullRequestEvent {
5    pub action: String,
6    pub number: Option<u64>,
7    pub pull_request: Option<PullRequest>,
8    pub repository: Repository,
9}
10
11#[derive(Debug, Clone, Deserialize)]
12pub struct PullRequest {
13    pub number: u64,
14    pub head: GitRef,
15    pub base: GitRef,
16    pub html_url: Option<String>,
17    pub title: Option<String>,
18}
19
20#[derive(Debug, Clone, Deserialize)]
21pub struct GitRef {
22    pub sha: String,
23    pub r#ref: String,
24    #[serde(default)]
25    pub repo: Option<HeadRepo>,
26}
27
28#[derive(Debug, Clone, Deserialize)]
29pub struct HeadRepo {
30    pub clone_url: String,
31}
32
33#[derive(Debug, Clone, Deserialize)]
34pub struct Repository {
35    pub full_name: String,
36    pub clone_url: String,
37    pub html_url: Option<String>,
38    pub owner: Owner,
39    pub name: String,
40}
41
42#[derive(Debug, Clone, Deserialize)]
43pub struct Owner {
44    pub login: String,
45}
46
47#[derive(Debug, Clone)]
48pub struct PrContext {
49    pub provider: ProviderKind,
50    pub action: String,
51    pub owner: String,
52    pub repo: String,
53    pub pr_number: u64,
54    pub head_sha: String,
55    pub head_ref: String,
56    pub base_ref: String,
57    pub clone_url: String,
58    pub head_clone_url: String,
59    pub pr_url: Option<String>,
60    pub title: Option<String>,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum ProviderKind {
65    GitHub,
66    Gitea,
67}
68
69impl PullRequestEvent {
70    pub fn into_context(self, provider: ProviderKind) -> Option<PrContext> {
71        if !is_action_supported(&self.action) {
72            return None;
73        }
74        let pr = self.pull_request?;
75        let clone_url = self.repository.clone_url;
76        Some(PrContext {
77            provider,
78            action: self.action,
79            owner: self.repository.owner.login,
80            repo: self.repository.name,
81            pr_number: pr.number,
82            head_sha: pr.head.sha,
83            head_ref: pr.head.r#ref,
84            base_ref: pr.base.r#ref,
85            head_clone_url: pr
86                .head
87                .repo
88                .as_ref()
89                .map(|r| r.clone_url.clone())
90                .unwrap_or_else(|| clone_url.clone()),
91            clone_url,
92            pr_url: pr.html_url,
93            title: pr.title,
94        })
95    }
96}
97
98fn is_action_supported(action: &str) -> bool {
99    matches!(action, "opened" | "synchronize" | "reopened" | "edited")
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn parses_opened_pull_request() {
108        let body = r#"{
109            "action": "opened",
110            "pull_request": {
111                "number": 42,
112                "head": { "sha": "abc123", "ref": "feature" },
113                "base": { "sha": "def456", "ref": "main" },
114                "html_url": "https://github.com/org/repo/pull/42"
115            },
116            "repository": {
117                "full_name": "org/repo",
118                "name": "repo",
119                "clone_url": "https://github.com/org/repo.git",
120                "owner": { "login": "org" }
121            }
122        }"#;
123        let event: PullRequestEvent = serde_json::from_str(body).unwrap();
124        let ctx = event.into_context(ProviderKind::GitHub).unwrap();
125        assert_eq!(ctx.pr_number, 42);
126        assert_eq!(ctx.head_sha, "abc123");
127        assert_eq!(ctx.base_ref, "main");
128    }
129
130    #[test]
131    fn ignores_closed_action() {
132        let body = r#"{
133            "action": "closed",
134            "pull_request": {
135                "number": 1,
136                "head": { "sha": "a", "ref": "b" },
137                "base": { "sha": "c", "ref": "main" }
138            },
139            "repository": {
140                "full_name": "o/r",
141                "name": "r",
142                "clone_url": "https://example.com/o/r.git",
143                "owner": { "login": "o" }
144            }
145        }"#;
146        let event: PullRequestEvent = serde_json::from_str(body).unwrap();
147        assert!(event.into_context(ProviderKind::GitHub).is_none());
148    }
149}