flows_connector_dsi/
github.rs

1use std::collections::HashMap;
2
3use serde::{de::DeserializeOwned, Deserialize};
4use serde_json::Value;
5
6#[derive(Deserialize, Debug)]
7#[serde(untagged)]
8pub enum License {
9    Object {
10        key: String,
11        name: String,
12        spdx_id: String,
13        url: String,
14    },
15    Name(String),
16}
17
18#[derive(Deserialize, Debug)]
19pub struct Repository {
20    pub name: String,
21    pub full_name: String,
22    pub private: bool,
23    pub owner: User,
24    pub html_url: String,
25    pub description: Option<String>,
26    pub fork: bool,
27    pub created_at: Value,
28    pub updated_at: String,
29    pub pushed_at: Value,
30    pub git_url: String,
31    pub ssh_url: String,
32    pub clone_url: String,
33    pub svn_url: String,
34    pub homepage: Option<String>,
35    pub stargazers_count: u32,
36    pub watchers_count: u32,
37    pub language: Option<String>,
38    pub has_issues: bool,
39    pub has_projects: bool,
40    pub has_downloads: bool,
41    pub has_wiki: bool,
42    pub has_pages: bool,
43    pub forks_count: u32,
44    pub mirror_url: Option<String>,
45    pub archived: bool,
46    pub disabled: bool,
47    pub open_issues_count: u32,
48    pub license: Option<License>,
49    pub visibility: String,
50    pub forks: u32,
51    pub open_issues: u32,
52    pub watchers: u32,
53    pub default_branch: String,
54}
55
56#[derive(Deserialize, Debug)]
57pub struct Organization {
58    pub login: String,
59    pub avatar_url: String,
60    pub description: Option<String>,
61}
62
63#[derive(Deserialize, Debug)]
64pub struct User {
65    pub login: String,
66    pub email: Option<String>,
67    pub avatar_url: String,
68    pub html_url: String,
69    pub r#type: String,
70    pub site_admin: bool,
71}
72
73#[derive(Deserialize, Debug)]
74pub struct DiscussionCategory {
75    pub name: String,
76    pub description: String,
77    pub emoji: Option<String>,
78    pub created_at: String,
79    pub updated_at: String,
80    pub slug: Option<String>,
81    pub is_answerable: bool,
82}
83
84#[derive(Deserialize, Debug)]
85pub struct Comment {
86    pub html_url: String,
87    pub user: User,
88    pub position: Option<u32>,
89    pub path: Option<String>,
90    pub created_at: String,
91    pub updated_at: String,
92    pub author_association: String,
93    pub body: String,
94}
95
96#[derive(Deserialize, Debug)]
97pub struct Discussion {
98    pub category: DiscussionCategory,
99    pub answer_html_url: Option<String>,
100    pub answer_chosen_at: Option<String>,
101    pub answer_chosen_by: Option<String>,
102    pub html_url: String,
103    pub user: User,
104    pub number: u32,
105    pub title: String,
106    pub state: String,
107    pub locked: bool,
108    pub comments: u32,
109    pub created_at: String,
110    pub updated_at: String,
111    pub author_association: String,
112    pub active_lock_reason: Option<String>,
113    pub body: String,
114}
115
116type Fork = Repository;
117
118#[derive(Deserialize, Debug)]
119pub struct Label {
120    pub name: String,
121    pub color: String,
122    pub default: bool,
123}
124
125#[derive(Deserialize, Debug)]
126pub struct Issue {
127    pub active_lock_reason: Option<String>,
128    pub html_url: String,
129    pub number: u32,
130    pub title: String,
131    pub user: User,
132    pub labels: Vec<Label>,
133    pub state: String,
134    pub state_reason: Option<String>,
135    pub locked: bool,
136    pub assignees: Vec<User>,
137    pub comments: u32,
138    pub created_at: String,
139    pub updated_at: String,
140    pub closed_at: Option<String>,
141    pub author_association: String,
142    pub body: Option<String>,
143}
144
145#[derive(Deserialize, Debug)]
146pub struct Plan {
147    pub name: String,
148    pub description: String,
149    pub monthly_price_in_cents: u32,
150    pub yearly_price_in_cents: u32,
151    pub price_model: String,
152    pub has_free_trial: bool,
153    pub unit_name: String,
154    pub bullets: Vec<String>,
155}
156
157#[derive(Deserialize, Debug)]
158pub struct MarketplacePurchase {
159    pub billing_cycle: String,
160    pub unit_count: u32,
161    pub on_free_trial: bool,
162    pub free_trial_ends_on: Option<String>,
163    pub next_billing_date: String,
164    pub plan: Plan,
165}
166
167#[derive(Deserialize, Debug)]
168pub struct PrPoint {
169    pub label: String,
170    pub r#ref: String,
171    pub sha: String,
172    pub user: User,
173    pub repo: Repository,
174}
175
176#[derive(Deserialize, Debug)]
177pub struct PullRequest {
178    pub html_url: String,
179    pub diff_url: String,
180    pub patch_url: String,
181    pub number: u32,
182    pub state: String,
183    pub locked: bool,
184    pub title: String,
185    pub user: User,
186    pub body: Option<String>,
187    pub created_at: String,
188    pub updated_at: String,
189    pub closed_at: Option<String>,
190    pub merged_at: Option<String>,
191    pub merge_commit_sha: Option<String>,
192    pub assignees: Vec<User>,
193    pub requested_reviewers: Vec<User>,
194    pub labels: Vec<Label>,
195    pub head: PrPoint,
196    pub base: PrPoint,
197    pub author_association: String,
198    pub draft: Option<bool>,
199    pub merged: Option<bool>,
200    pub mergeable: Option<bool>,
201    pub mergeable_state: Option<String>,
202    pub merged_by: Option<User>,
203    pub comments: Option<u32>,
204    pub review_comments: Option<u32>,
205    pub maintainer_can_modify: Option<bool>,
206    pub commits: Option<u32>,
207    pub additions: Option<u32>,
208    pub deletions: Option<u32>,
209    pub changed_files: Option<u32>,
210}
211
212#[derive(Deserialize, Debug)]
213pub struct Review {
214    pub user: User,
215    pub body: Option<String>,
216    pub commit_id: String,
217    pub submitted_at: String,
218    pub state: String,
219    pub html_url: String,
220    pub author_association: String,
221}
222
223#[derive(Deserialize, Debug)]
224pub struct ReleaseAsset {
225    pub browser_download_url: String,
226    pub name: String,
227    pub label: Option<String>,
228    pub state: String,
229    pub content_type: String,
230    pub size: i64,
231    pub download_count: i64,
232    pub created_at: String,
233    pub updated_at: String,
234    pub uploader: User,
235}
236
237#[derive(Deserialize, Debug)]
238pub struct Release {
239    pub html_url: String,
240    pub tag_name: String,
241    pub target_commitish: String,
242    pub name: Option<String>,
243    pub draft: bool,
244    pub author: User,
245    pub assets: Vec<ReleaseAsset>,
246    pub prerelease: bool,
247    pub created_at: String,
248    pub published_at: String,
249    pub body: Option<String>,
250}
251
252#[derive(Deserialize, Debug)]
253pub struct WorkflowStep {
254    pub name: String,
255    pub status: String,
256    pub conclusion: Option<String>,
257    pub number: u32,
258    pub started_at: Option<String>,
259    pub completed_at: Option<String>,
260}
261
262#[derive(Deserialize, Debug)]
263pub struct WorkflowJob {
264    pub html_url: String,
265    pub status: String,
266    pub conclusion: Option<String>,
267    pub started_at: String,
268    pub completed_at: Option<String>,
269    pub name: String,
270    pub steps: Vec<WorkflowStep>,
271    pub labels: Vec<String>,
272    pub runner_id: Option<u32>,
273    pub runner_name: Option<String>,
274    pub runner_group_id: Option<u32>,
275    pub runner_group_name: Option<String>,
276}
277
278#[derive(Deserialize, Debug)]
279pub struct GitUser {
280    pub name: String,
281    pub email: String,
282    pub username: Option<String>,
283}
284
285#[derive(Deserialize, Debug)]
286pub struct Commit {
287    pub distinct: bool,
288    #[serde(default)]
289    pub message: String,
290    pub timestamp: String,
291    pub url: String,
292    pub author: GitUser,
293    pub committer: GitUser,
294    pub added: Vec<String>,
295    pub removed: Vec<String>,
296    pub modified: Vec<String>,
297}
298
299#[derive(Deserialize, Debug)]
300pub struct InboundData {
301    pub sender: User,
302
303    pub action: Option<String>,
304    pub repository: Option<Repository>,
305    pub organization: Option<Organization>,
306    pub comment: Option<Comment>,
307    pub discussion: Option<Discussion>,
308    pub forkee: Option<Fork>,
309    pub issue: Option<Issue>,
310    pub label: Option<Label>,
311    pub marketplace_purchase: Option<MarketplacePurchase>,
312    pub pull_request: Option<PullRequest>,
313    pub review: Option<Review>,
314    pub release: Option<Release>,
315    pub starred_at: Option<String>,
316    pub workflow_job: Option<WorkflowJob>,
317    pub head_commit: Option<Commit>,
318    pub commits: Option<Vec<Commit>>,
319
320    #[serde(flatten)]
321    pub extra: HashMap<String, Value>,
322}
323
324impl InboundData {
325    /// Gets the value from `extra` and deserializes into `T`.
326    ///
327    /// eg.
328    /// ```rust
329    /// let r#ref: String = payload.get("ref").unwrap();
330    /// ```
331    #[inline]
332    pub fn get<T: DeserializeOwned, I: ToString>(&self, index: &I) -> Result<T, String> {
333        serde_json::from_value(
334            self.extra
335                .get(&index.to_string())
336                .ok_or(format!("Missing {}", index.to_string()))?
337                .clone(),
338        )
339        .map_err(|e| e.to_string())
340    }
341
342    #[inline]
343    pub fn get_action(&self) -> Result<&String, String> {
344        self.action.as_ref().ok_or("Missing action".to_string())
345    }
346
347    #[inline]
348    pub fn get_repository(&self) -> Result<&Repository, String> {
349        self.repository
350            .as_ref()
351            .ok_or("Missing repository".to_string())
352    }
353
354    #[inline]
355    pub fn get_comment(&self) -> Result<&Comment, String> {
356        self.comment.as_ref().ok_or("Missing comment".to_string())
357    }
358
359    #[inline]
360    pub fn get_discussion(&self) -> Result<&Discussion, String> {
361        self.discussion
362            .as_ref()
363            .ok_or("Missing discussion".to_string())
364    }
365
366    #[inline]
367    pub fn get_fork(&self) -> Result<&Fork, String> {
368        self.forkee.as_ref().ok_or("Missing forkee".to_string())
369    }
370
371    #[inline]
372    pub fn get_issue(&self) -> Result<&Issue, String> {
373        self.issue.as_ref().ok_or("Missing issue".to_string())
374    }
375
376    #[inline]
377    pub fn get_label(&self) -> Result<&Label, String> {
378        self.label.as_ref().ok_or("Missing label".to_string())
379    }
380
381    #[inline]
382    pub fn get_marketplace_purchase(&self) -> Result<&MarketplacePurchase, String> {
383        self.marketplace_purchase
384            .as_ref()
385            .ok_or("Missing marketplace_purchase".to_string())
386    }
387
388    #[inline]
389    pub fn get_pull_request(&self) -> Result<&PullRequest, String> {
390        self.pull_request
391            .as_ref()
392            .ok_or("Missing pull_request".to_string())
393    }
394
395    #[inline]
396    pub fn get_review(&self) -> Result<&Review, String> {
397        self.review.as_ref().ok_or("Missing review".to_string())
398    }
399
400    #[inline]
401    pub fn get_release(&self) -> Result<&Release, String> {
402        self.release.as_ref().ok_or("Missing release".to_string())
403    }
404
405    #[inline]
406    pub fn get_starred_at(&self) -> Result<&String, String> {
407        self.starred_at
408            .as_ref()
409            .ok_or("Missing starred_at".to_string())
410    }
411
412    #[inline]
413    pub fn get_workflow_job(&self) -> Result<&WorkflowJob, String> {
414        self.workflow_job
415            .as_ref()
416            .ok_or("Missing workflow_job".to_string())
417    }
418
419    pub fn get_head_commit(&self) -> Result<&Commit, String> {
420        self.head_commit
421            .as_ref()
422            .ok_or("Missing head_commit".to_string())
423    }
424
425    pub fn get_commits(&self) -> Result<&Vec<Commit>, String> {
426        self.commits.as_ref().ok_or("Missing commits".to_string())
427    }
428}
429
430/// Deserialize the GitHub Webhook Payload into `InboundData`.
431pub fn inbound(s: String) -> Result<InboundData, String> {
432    #[cfg(debug_assertions)]
433    return serde_json::from_str::<InboundData>(&s)
434        .map_err(|e| format!("Parsing GitHub Webhook payload failed: {}", e.to_string()));
435
436    #[cfg(not(debug_assertions))]
437    serde_json::from_str::<InboundData>(&s)
438        .map_err(|_| format!("Parsing GitHub Webhook payload failed: {}", s))
439}
440
441pub mod outbound {
442    use std::collections::HashMap;
443
444    use serde::Serialize;
445    use serde_json::{json, Value};
446
447    #[derive(Serialize)]
448    pub struct OutboundData<'a> {
449        #[serde(flatten)]
450        inner: HashMap<&'a str, Value>,
451    }
452
453    impl<'a> OutboundData<'a> {
454        /// Set the issue or issue comment body.
455        pub fn body<S: ToString + Serialize>(mut self, body: S) -> OutboundData<'a> {
456            self.inner.insert("body", json!(body));
457            self
458        }
459
460        /// Set Milestone.
461        pub fn milestone(mut self, milestone: Value) -> OutboundData<'a> {
462            self.inner.insert("milestone", milestone);
463            self
464        }
465
466        /// Set labels.
467        pub fn labels<S: ToString + Serialize>(mut self, labels: Vec<S>) -> OutboundData<'a> {
468            self.inner.insert("labels", json!(labels));
469            self
470        }
471
472        /// Set assignees.
473        pub fn assignees<S: ToString + Serialize>(mut self, assignees: Vec<S>) -> OutboundData<'a> {
474            self.inner.insert("assignees", json!(assignees));
475            self
476        }
477
478        /// Build outbound JSON data.
479        pub fn build(self) -> Result<String, String> {
480            if self.inner.len() < 2 {
481                return Err("OutboundData build failed: Too few fields".to_string());
482            }
483
484            serde_json::to_string(&self)
485                .map_err(|e| format!("OutboundData build failed: {}", e.to_string()))
486        }
487    }
488
489    /// Create an issue with the title.
490    pub fn create_issue<'a, S: ToString + Serialize>(title: S) -> OutboundData<'a> {
491        OutboundData {
492            inner: [("title", json!(title))]
493                .into_iter()
494                .collect::<HashMap<&str, Value>>(),
495        }
496    }
497
498    /// Modified an issue like change the assignees or labels,
499    /// create a comment, etc.
500    pub fn modify_issue<'a>(issue_number: u32) -> OutboundData<'a> {
501        OutboundData {
502            inner: [("issue_number", json!(issue_number))]
503                .into_iter()
504                .collect::<HashMap<&str, Value>>(),
505        }
506    }
507
508    /// Merge a pull request.
509    pub fn merge_pull<'a>(pull_number: u32) -> OutboundData<'a> {
510        OutboundData {
511            inner: [("pull_number", json!(pull_number))]
512                .into_iter()
513                .collect::<HashMap<&str, Value>>(),
514        }
515    }
516}