flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/services/actions/src/payload.rs

178 lines6,997 bytesCodeBlame
1//! `github.event`: the webhook-shaped payload GitHub gives a workflow,
2//! built from g1t's own records so `github.event.pull_request.number`,
3//! `github.event.issue.labels.*.name` and the like read as they do there.
4
5use g1t_contracts::User;
6use g1t_contracts::repos::{Commit, Repo};
7use g1t_contracts::work::{Comment, Issue, Pull, PullStatus, State};
8use serde_json::{Value, json};
9
10use crate::{API, SITE};
11
12pub fn repository(repo: &Repo) -> Value {
13 let full_name = format!("{}/{}", repo.namespace, repo.name);
14 json!({
15 "id": repo.id,
16 "node_id": repo.id,
17 "name": repo.name,
18 "full_name": full_name,
19 "private": repo.is_private,
20 "owner": { "login": repo.namespace, "type": "Organization" },
21 "html_url": format!("{SITE}/{full_name}"),
22 "url": format!("{API}/repos/{full_name}"),
23 "clone_url": format!("{SITE}/{full_name}.git"),
24 "description": repo.description,
25 "default_branch": repo.default_branch,
26 "fork": false,
27 "visibility": if repo.is_private { "private" } else { "public" },
28 })
29}
30
31pub fn user(login: &str) -> Value {
32 json!({ "login": login, "type": "User", "html_url": format!("{SITE}/{login}") })
33}
34
35fn person(user: &User) -> Value {
36 self::user(&user.username)
37}
38
39fn labels(names: &[String]) -> Value {
40 Value::Array(names.iter().map(|name| json!({ "name": name })).collect())
41}
42
43pub fn commit(repo: &Repo, commit: &Commit) -> Value {
44 json!({
45 "id": commit.hash,
46 "tree_id": commit.tree_hash,
47 "message": commit.message,
48 "timestamp": commit.authored_at,
49 "url": format!("{SITE}/{}/{}/commit/{}", repo.namespace, repo.name, commit.hash),
50 "author": { "name": commit.author.name, "email": commit.author.email },
51 "committer": { "name": commit.author.name, "email": commit.author.email },
52 "distinct": true,
53 })
54}
55
56pub fn push(repo: &Repo, git_ref: &str, before: Option<&str>, after: &str, commits: &[Commit], pusher: &str) -> Value {
57 let zero = "0000000000000000000000000000000000000000";
58 let commits: Vec<Value> = commits.iter().map(|c| commit(repo, c)).collect();
59 json!({
60 "ref": git_ref,
61 "before": before.unwrap_or(zero),
62 "after": after,
63 "created": before.is_none(),
64 "deleted": false,
65 "forced": false,
66 "base_ref": null,
67 "compare": format!("{SITE}/{}/{}/commit/{after}", repo.namespace, repo.name),
68 "head_commit": commits.first().cloned().unwrap_or(Value::Null),
69 "commits": commits,
70 "pusher": { "name": pusher, "email": null },
71 "repository": repository(repo),
72 "sender": user(pusher),
73 })
74}
75
76pub fn issue(repo: &Repo, issue: &Issue) -> Value {
77 let full_name = format!("{}/{}", repo.namespace, repo.name);
78 json!({
79 "id": issue.id,
80 "number": issue.number,
81 "title": issue.title,
82 "body": issue.body,
83 "state": if issue.state == State::Open { "open" } else { "closed" },
84 "state_reason": issue.reason,
85 "labels": labels(&issue.labels),
86 "user": person(&issue.author),
87 "assignees": issue.assignees.iter().map(|a| user(a)).collect::<Vec<_>>(),
88 "comments": issue.comment_count,
89 "created_at": issue.created_at,
90 "updated_at": issue.updated_at,
91 "closed_at": issue.closed_at,
92 "html_url": format!("{SITE}/{full_name}/issues/{}", issue.number),
93 "url": format!("{API}/repos/{full_name}/issues/{}", issue.number),
94 })
95}
96
97/// A pull request. `labels` are its issue's, since g1t labels issues.
98pub fn pull(repo: &Repo, pull: &Pull, labels_of: &[String]) -> Value {
99 let full_name = format!("{}/{}", repo.namespace, repo.name);
100 let head_ref = head_ref(pull);
101 let head_repo = match &pull.fork {
102 Some(fork) => json!({ "full_name": format!("{}/{}", fork.namespace, fork.name), "fork": true }),
103 None => json!({ "full_name": full_name, "fork": false }),
104 };
105 json!({
106 "id": pull.id,
107 "number": pull.number,
108 "title": pull.title,
109 "body": pull.body,
110 "state": if pull.status.is_active() { "open" } else { "closed" },
111 "draft": pull.status == PullStatus::Draft,
112 "merged": pull.status == PullStatus::Merged,
113 "merged_at": pull.merged_at,
114 "merged_by": pull.merged_by.as_deref().map(user),
115 "merge_commit_sha": if pull.status == PullStatus::Merged { pull.head_commit.clone() } else { None },
116 "labels": labels(labels_of),
117 "user": person(&pull.author),
118 "assignees": pull.assignees.iter().map(|a| user(a)).collect::<Vec<_>>(),
119 "requested_reviewers": pull.reviewers.iter().map(|r| user(r)).collect::<Vec<_>>(),
120 "head": {
121 "ref": head_ref,
122 "sha": pull.head_commit,
123 "label": format!("{}:{head_ref}", repo.namespace),
124 "repo": head_repo,
125 },
126 "base": {
127 "ref": repo.default_branch,
128 "sha": pull.merge_base,
129 "label": format!("{}:{}", repo.namespace, repo.default_branch),
130 "repo": repository(repo),
131 },
132 "html_url": format!("{SITE}/{full_name}/pull/{}", pull.number),
133 "url": format!("{API}/repos/{full_name}/pulls/{}", pull.number),
134 "issue_url": pull.issue.map(|n| format!("{API}/repos/{full_name}/issues/{n}")),
135 })
136}
137
138/// The branch a pull request comes from, or a name for its fork.
139pub fn head_ref(pull: &Pull) -> String {
140 pull.branch.clone().unwrap_or_else(|| format!("pull/{}", pull.number))
141}
142
143pub fn comment(repo: &Repo, number: u32, comment: &Comment, on_pull: bool) -> Value {
144 let full_name = format!("{}/{}", repo.namespace, repo.name);
145 let page = if on_pull { "pull" } else { "issues" };
146 json!({
147 "id": comment.id,
148 "body": comment.body,
149 "user": person(&comment.author),
150 "created_at": comment.created_at,
151 "updated_at": comment.created_at,
152 "path": comment.path,
153 "line": comment.line,
154 "html_url": format!("{SITE}/{full_name}/{page}/{number}#{}", comment.id),
155 })
156}
157
158/// An issue as `issue_comment` gives it for a pull request: the pull
159/// request's number and title, with `pull_request` set.
160pub fn pull_as_issue(repo: &Repo, pull: &Pull, labels_of: &[String]) -> Value {
161 let full_name = format!("{}/{}", repo.namespace, repo.name);
162 json!({
163 "id": pull.id,
164 "number": pull.number,
165 "title": pull.title,
166 "body": pull.body,
167 "state": if pull.status.is_active() { "open" } else { "closed" },
168 "labels": labels(labels_of),
169 "user": person(&pull.author),
170 "html_url": format!("{SITE}/{full_name}/pull/{}", pull.number),
171 "pull_request": {
172 "url": format!("{API}/repos/{full_name}/pulls/{}", pull.number),
173 "html_url": format!("{SITE}/{full_name}/pull/{}", pull.number),
174 "merged_at": pull.merged_at,
175 },
176 })
177}
178