ghai/
types.rs

1use crate::http::{GitHubClient, UrlBuilder};
2use chrono::{DateTime, TimeZone};
3
4/// Trait for types that can fetch comments
5pub trait CommentFetcher: Sync {
6    fn comments_url(&self) -> &str;
7
8    fn fetch_comments<Tz: TimeZone>(
9        &self,
10        since: Option<DateTime<Tz>>,
11    ) -> impl std::future::Future<Output = Result<Vec<IssueComment>, Box<dyn std::error::Error>>> + Send
12    where
13        Tz::Offset: Send,
14    {
15        async {
16            let url = UrlBuilder::new(self.comments_url())
17                .param("since", since.map(|s| s.to_rfc3339()))
18                .build();
19
20            let client = GitHubClient::new()?;
21            let response = client
22                .get(&url)
23                .send()
24                .await?
25                .json::<Vec<IssueComment>>()
26                .await?;
27
28            Ok(response)
29        }
30    }
31}
32
33#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
34#[serde(deny_unknown_fields)]
35pub struct IssueDependenciesSummary {
36    pub blocked_by: i64,
37    pub blocking: i64,
38    pub total_blocked_by: i64,
39    pub total_blocking: i64,
40}
41
42#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
43#[serde(deny_unknown_fields)]
44pub struct IssueFieldValueSingleSelectOption {
45    pub id: i64,
46    pub name: String,
47    pub color: String,
48}
49
50#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
51#[serde(deny_unknown_fields)]
52pub struct IssueFieldValue {
53    pub issue_field_id: i64,
54    pub node_id: String,
55    pub data_type: String,
56    pub value: Option<serde_json::Value>,
57    pub single_select_option: Option<IssueFieldValueSingleSelectOption>,
58}
59
60#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
61#[serde(deny_unknown_fields)]
62pub struct IssueType {
63    pub id: i64,
64    pub node_id: String,
65    pub name: String,
66    pub description: Option<String>,
67    pub color: Option<String>,
68    pub created_at: String,
69    pub updated_at: String,
70    pub is_enabled: bool,
71}
72
73#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
74#[serde(deny_unknown_fields)]
75pub struct Commit {
76    pub id: String,
77    pub tree_id: String,
78    pub distinct: bool,
79    pub message: String,
80    pub timestamp: String,
81    pub url: String,
82    pub author: CommitUser,
83    pub committer: CommitUser,
84    pub added: Vec<String>,
85    pub removed: Vec<String>,
86    pub modified: Vec<String>,
87}
88
89#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
90#[serde(deny_unknown_fields)]
91pub struct CommitUser {
92    pub name: String,
93    pub email: String,
94    pub username: Option<String>,
95}
96
97#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
98#[serde(deny_unknown_fields)]
99pub struct Repository {
100    pub id: i64,
101    pub node_id: String,
102    pub name: String,
103    pub full_name: String,
104    pub private: bool,
105    pub owner: SimpleUser,
106    pub html_url: String,
107    pub description: Option<String>,
108    pub fork: bool,
109    pub url: String,
110    pub archive_url: String,
111    pub assignees_url: String,
112    pub blobs_url: String,
113    pub branches_url: String,
114    pub collaborators_url: String,
115    pub comments_url: String,
116    pub commits_url: String,
117    pub compare_url: String,
118    pub contents_url: String,
119    pub contributors_url: String,
120    pub deployments_url: String,
121    pub downloads_url: String,
122    pub events_url: String,
123    pub forks_url: String,
124    pub git_commits_url: String,
125    pub git_refs_url: String,
126    pub git_tags_url: String,
127    pub git_url: Option<String>,
128    pub issue_comment_url: String,
129    pub issue_events_url: String,
130    pub issues_url: String,
131    pub keys_url: String,
132    pub labels_url: String,
133    pub languages_url: String,
134    pub merges_url: String,
135    pub milestones_url: String,
136    pub notifications_url: String,
137    pub pulls_url: String,
138    pub releases_url: String,
139    pub ssh_url: Option<String>,
140    pub stargazers_url: String,
141    pub statuses_url: String,
142    pub subscribers_url: String,
143    pub subscription_url: String,
144    pub tags_url: String,
145    pub teams_url: String,
146    pub trees_url: String,
147    pub clone_url: Option<String>,
148    pub mirror_url: Option<String>,
149    pub hooks_url: String,
150    pub svn_url: Option<String>,
151    pub homepage: Option<String>,
152    pub language: Option<String>,
153    pub forks_count: Option<u64>,
154    pub stargazers_count: Option<u64>,
155    pub watchers_count: Option<u64>,
156    pub size: Option<u64>,
157    pub default_branch: Option<String>,
158    pub open_issues_count: Option<u64>,
159    pub is_template: Option<bool>,
160    pub topics: Option<Vec<String>>,
161    pub has_issues: Option<bool>,
162    pub has_projects: Option<bool>,
163    pub has_wiki: Option<bool>,
164    pub has_pages: Option<bool>,
165    pub has_downloads: Option<bool>,
166    pub has_discussions: Option<bool>,
167    pub archived: Option<bool>,
168    pub disabled: Option<bool>,
169    pub visibility: Option<String>,
170    pub pushed_at: Option<String>,
171    pub created_at: Option<String>,
172    pub updated_at: Option<String>,
173    pub permissions: Option<RepositoryPermissions>,
174    pub allow_rebase_merge: Option<bool>,
175    pub template_repository: Option<serde_json::Value>,
176    pub temp_clone_token: Option<String>,
177    pub allow_squash_merge: Option<bool>,
178    pub allow_auto_merge: Option<bool>,
179    pub delete_branch_on_merge: Option<bool>,
180    pub allow_merge_commit: Option<bool>,
181    pub allow_forking: Option<bool>,
182    pub web_commit_signoff_required: Option<bool>,
183    pub subscribers_count: Option<u64>,
184    pub network_count: Option<u64>,
185    pub license: Option<RepositoryLicense>,
186    pub forks: Option<u64>,
187    pub open_issues: Option<u64>,
188    pub watchers: Option<u64>,
189}
190
191#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
192#[serde(deny_unknown_fields)]
193pub struct RepositoryPermissions {
194    pub admin: bool,
195    pub maintain: Option<bool>,
196    pub push: bool,
197    pub pull: bool,
198    pub triage: Option<bool>,
199}
200
201#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
202#[serde(deny_unknown_fields)]
203pub struct RepositoryLicense {
204    pub key: String,
205    pub name: String,
206    pub spdx_id: Option<String>,
207    pub url: Option<String>,
208    pub node_id: String,
209}
210
211#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
212#[serde(deny_unknown_fields)]
213pub struct Milestone {
214    pub url: String,
215    pub html_url: String,
216    pub labels_url: String,
217    pub id: i64,
218    pub node_id: String,
219    pub number: u64,
220    pub title: String,
221    pub description: Option<String>,
222    pub creator: Option<SimpleUser>,
223    pub open_issues: u64,
224    pub closed_issues: u64,
225    pub state: String,
226    pub created_at: String,
227    pub updated_at: String,
228    pub due_on: Option<String>,
229    pub closed_at: Option<String>,
230}
231
232#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
233#[serde(deny_unknown_fields)]
234pub struct IssuePullRequest {
235    pub url: String,
236    pub html_url: String,
237    pub diff_url: String,
238    pub patch_url: String,
239    pub merged_at: Option<String>,
240}
241
242#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
243#[serde(deny_unknown_fields)]
244pub struct Integration {
245    pub id: i64,
246    pub slug: Option<String>,
247    pub node_id: String,
248    pub owner: SimpleUser,
249    pub name: String,
250    pub description: Option<String>,
251    pub external_url: String,
252    pub html_url: String,
253    pub created_at: String,
254    pub updated_at: String,
255    pub permissions: IntegrationPermissions,
256    pub events: Vec<String>,
257    pub installations_count: Option<u64>,
258    pub client_id: Option<String>,
259    pub client_secret: Option<String>,
260    pub webhook_secret: Option<String>,
261    pub pem: Option<String>,
262}
263
264#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
265#[serde(deny_unknown_fields)]
266pub struct IntegrationPermissions {
267    pub issues: Option<String>,
268    pub checks: Option<String>,
269    pub metadata: Option<String>,
270    pub contents: Option<String>,
271    pub deployments: Option<String>,
272}
273
274#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
275#[serde(deny_unknown_fields)]
276#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
277pub enum AuthorAssociation {
278    Owner,
279    Member,
280    Collaborator,
281    Contributor,
282    FirstTimeContributor,
283    FirstTimer,
284    Mannequin,
285    None,
286}
287
288#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
289#[serde(deny_unknown_fields)]
290pub struct ReactionRollup {
291    pub url: String,
292    pub total_count: u64,
293    #[serde(rename = "+1")]
294    pub plus_one: u64,
295    #[serde(rename = "-1")]
296    pub minus_one: u64,
297    pub laugh: u64,
298    pub hooray: u64,
299    pub confused: u64,
300    pub heart: u64,
301    pub rocket: u64,
302    pub eyes: u64,
303}
304
305#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
306#[serde(deny_unknown_fields)]
307pub struct SubIssuesSummary {
308    pub completed: u64,
309    pub percent_completed: u64,
310    pub total: u64,
311}
312
313#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
314#[serde(deny_unknown_fields)]
315pub struct Links {
316    #[serde(rename = "self")]
317    pub self_link: Link,
318    pub html: Link,
319    pub issue: Option<Link>,
320    pub comments: Option<Link>,
321    pub review_comments: Option<Link>,
322    pub review_comment: Option<Link>,
323    pub commits: Option<Link>,
324    pub statuses: Option<Link>,
325}
326
327#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
328#[serde(deny_unknown_fields)]
329pub struct Team {
330    pub id: i64,
331    pub node_id: String,
332    pub url: String,
333    pub html_url: String,
334    pub name: String,
335    pub slug: String,
336    pub description: Option<String>,
337    pub privacy: String,
338    pub permission: String,
339    pub members_url: String,
340    pub repositories_url: String,
341    pub parent: Option<Box<Team>>,
342}
343
344#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
345#[serde(deny_unknown_fields)]
346pub struct AutoMerge {
347    pub enabled_by: SimpleUser,
348    pub merge_method: String,
349    pub commit_title: Option<String>,
350    pub commit_message: Option<String>,
351}
352
353#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
354#[serde(deny_unknown_fields)]
355pub struct HeadCommit {
356    pub id: String,
357    pub tree_id: String,
358    pub message: String,
359    pub timestamp: String,
360    pub author: CommitUser,
361    pub committer: CommitUser,
362    pub added: Vec<String>,
363    pub removed: Vec<String>,
364    pub modified: Vec<String>,
365}
366
367#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
368#[serde(deny_unknown_fields)]
369pub struct SimpleUser {
370    pub name: Option<String>,
371    pub email: Option<String>,
372    pub login: String,
373    pub id: i64,
374    pub node_id: String,
375    pub avatar_url: String,
376    pub gravatar_id: Option<String>,
377    pub url: String,
378    pub html_url: String,
379    pub followers_url: String,
380    pub following_url: String,
381    pub gists_url: String,
382    pub starred_url: String,
383    pub subscriptions_url: String,
384    pub organizations_url: String,
385    pub repos_url: String,
386    pub events_url: String,
387    pub received_events_url: String,
388    pub r#type: String,
389    pub site_admin: bool,
390    pub starred_at: Option<String>,
391    pub user_view_type: Option<String>,
392}
393
394#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
395#[serde(deny_unknown_fields)]
396pub struct Actor {
397    pub id: i64,
398    pub login: String,
399    pub display_login: Option<String>,
400    pub gravatar_id: Option<String>,
401    pub url: String,
402    pub avatar_url: String,
403}
404
405#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
406#[serde(deny_unknown_fields)]
407pub struct RepoStub {
408    pub id: u64,
409    pub name: String,
410    pub url: String,
411}
412
413#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
414#[serde(deny_unknown_fields)]
415#[serde(tag = "type")]
416pub struct Event {
417    pub r#type: String,
418    pub id: String,
419    pub actor: Actor,
420    pub repo: RepoStub,
421    pub org: Option<Actor>,
422    pub payload: serde_json::Value,
423    pub public: bool,
424    pub created_at: Option<String>,
425}
426
427#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
428#[serde(deny_unknown_fields)]
429#[serde(untagged)]
430pub enum EventPayload {
431    IssueComment {
432        action: String,
433        issue: Box<Issue>,
434        comment: Box<IssueComment>,
435    },
436    Issue {
437        action: String,
438        issue: Box<Issue>,
439    },
440    Create {
441        description: String,
442        master_branch: String,
443        pusher_type: String,
444        r#ref: String,
445        ref_type: String,
446    },
447    PushEvent {
448        before: String,
449        commits: Vec<Commit>,
450        distinct_size: u64,
451        head: String,
452        push_id: u64,
453        r#ref: String,
454        repository_id: u64,
455        size: u64,
456    },
457    Delete {
458        pusher_type: String,
459        r#ref: String,
460        ref_type: String,
461    },
462    PullRequest {
463        action: String,
464        number: u64,
465        pull_request: Box<PullRequest>,
466    },
467    PullRequestReview {
468        action: String,
469        pull_request: Box<PullRequest>,
470        review: Box<PullRequestReview>,
471    },
472    PullRequestComment {
473        action: String,
474        pull_request: Box<PullRequest>,
475        comment: Box<PullRequestReviewComment>,
476    },
477    Forkee {
478        forkee: Box<Repository>,
479    },
480    Action {
481        action: String,
482    },
483}
484
485#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
486#[serde(deny_unknown_fields)]
487#[serde(untagged)]
488pub enum Label {
489    Detailed {
490        id: serde_json::Value,
491        node_id: Option<String>,
492        url: Option<String>,
493        name: String,
494        color: String,
495        default: bool,
496        description: Option<String>,
497    },
498    Simple(String),
499}
500
501impl Label {
502    pub fn name(&self) -> &str {
503        match self {
504            Self::Detailed { name, .. } => name.as_str(),
505            Self::Simple(name) => name.as_str(),
506        }
507    }
508}
509
510#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
511#[serde(deny_unknown_fields)]
512pub struct Link {
513    pub href: String,
514}
515
516#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
517#[serde(deny_unknown_fields)]
518pub struct Issue {
519    pub id: i64,
520    pub node_id: String,
521    pub url: String,
522    pub repository_url: String,
523    pub labels_url: String,
524    pub comments_url: String,
525    pub events_url: String,
526    pub html_url: String,
527    pub number: u64,
528    pub state: String,
529    pub state_reason: Option<String>,
530    pub title: String,
531    pub body: Option<String>,
532    pub user: Option<SimpleUser>,
533    pub labels: Vec<Label>,
534    pub assignee: Option<SimpleUser>,
535    pub assignees: Option<Vec<SimpleUser>>,
536    pub milestone: Option<Milestone>,
537    pub locked: bool,
538    pub active_lock_reason: Option<String>,
539    pub comments: u64,
540    pub pull_request: Option<IssuePullRequest>,
541    pub closed_at: Option<String>,
542    pub created_at: String,
543    pub updated_at: String,
544    pub draft: Option<bool>,
545    pub closed_by: Option<SimpleUser>,
546    pub body_html: Option<String>,
547    pub body_text: Option<String>,
548    pub timeline_url: Option<String>,
549    pub repository: Option<Repository>,
550    pub performed_via_github_app: Option<Integration>,
551    pub author_association: AuthorAssociation,
552    pub reactions: Option<ReactionRollup>,
553    pub sub_issues_summary: Option<SubIssuesSummary>,
554    pub issue_dependencies_summary: Option<IssueDependenciesSummary>,
555    pub issue_field_values: Option<Vec<IssueFieldValue>>,
556    pub parent_issue_url: Option<String>,
557    #[serde(rename = "type")]
558    pub r#type: Option<IssueType>,
559}
560
561impl Issue {
562    #[allow(clippy::too_many_arguments)]
563    pub async fn fetch_user_issues<Tz: TimeZone>(
564        filter: Option<String>,
565        state: Option<String>,
566        labels: Option<String>,
567        sort: Option<String>,
568        direction: Option<String>,
569        since: Option<DateTime<Tz>>,
570        per_page: Option<u64>,
571        page: Option<u64>,
572    ) -> Result<Vec<Issue>, Box<dyn std::error::Error>> {
573        let url = UrlBuilder::new("https://api.github.com/issues")
574            .param("filter", filter)
575            .param("state", state)
576            .param("labels", labels)
577            .param("sort", sort)
578            .param("direction", direction)
579            .param("since", since.map(|s| s.to_rfc3339()))
580            .param("per_page", per_page)
581            .param("page", page)
582            .build();
583
584        let client = GitHubClient::new()?;
585        let issues: Vec<Issue> = client.get(&url).send().await?.json().await?;
586
587        Ok(issues)
588    }
589}
590
591impl CommentFetcher for Issue {
592    fn comments_url(&self) -> &str {
593        &self.comments_url
594    }
595}
596
597#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
598#[serde(deny_unknown_fields)]
599pub struct IssueComment {
600    pub id: i64,
601    pub node_id: String,
602    pub url: String,
603    pub body: Option<String>,
604    pub body_text: Option<String>,
605    pub body_html: Option<String>,
606    pub html_url: String,
607    pub user: Option<SimpleUser>,
608    pub created_at: String,
609    pub updated_at: String,
610    pub issue_url: String,
611    pub author_association: AuthorAssociation,
612    pub performed_via_github_app: Option<Integration>,
613    pub reactions: Option<ReactionRollup>,
614}
615
616#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
617#[serde(deny_unknown_fields)]
618pub struct PullRequestReview {
619    pub _links: Option<Links>,
620    pub author_association: String,
621    pub body: Option<String>,
622    pub commit_id: String,
623    pub html_url: String,
624    pub id: i64,
625    pub node_id: String,
626    pub pull_request_url: String,
627    pub state: String,
628    pub submitted_at: String,
629    pub user: SimpleUser,
630}
631
632#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
633#[serde(deny_unknown_fields)]
634pub struct PullRequestReviewComment {
635    pub id: i64,
636    pub node_id: String,
637    pub url: String,
638    pub body: Option<String>,
639    pub body_text: Option<String>,
640    pub body_html: Option<String>,
641    pub html_url: String,
642    pub user: Option<SimpleUser>,
643    pub created_at: String,
644    pub updated_at: String,
645    pub issue_url: Option<String>,
646    pub author_association: AuthorAssociation,
647    pub performed_via_github_app: Option<Integration>,
648    pub reactions: Option<ReactionRollup>,
649    pub _links: Option<Links>,
650    pub commit_id: Option<String>,
651    pub diff_hunk: Option<String>,
652    pub line: Option<u64>,
653    pub original_commit_id: Option<String>,
654    pub original_line: Option<u64>,
655    pub original_position: Option<u64>,
656    pub original_start_line: Option<u64>,
657    pub path: Option<String>,
658    pub position: Option<u64>,
659    pub pull_request_review_id: Option<u64>,
660    pub pull_request_url: Option<String>,
661    pub side: Option<String>,
662    pub start_line: Option<u64>,
663    pub start_side: Option<String>,
664    pub subject_type: Option<String>,
665    pub in_reply_to_id: Option<serde_json::Value>,
666}
667
668#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
669#[serde(deny_unknown_fields)]
670pub struct PullRequestHead {
671    pub label: String,
672    #[serde(rename = "ref")]
673    pub r#ref: String,
674    pub sha: String,
675    pub user: SimpleUser,
676    pub repo: Repository,
677}
678
679#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
680#[serde(deny_unknown_fields)]
681pub struct PullRequest {
682    pub url: String,
683    pub id: i64,
684    pub node_id: String,
685    pub html_url: String,
686    pub diff_url: String,
687    pub patch_url: String,
688    pub issue_url: String,
689    pub commits_url: String,
690    pub review_comments_url: String,
691    pub review_comment_url: String,
692    pub comments_url: String,
693    pub statuses_url: String,
694    pub number: u64,
695    pub state: String,
696    pub locked: bool,
697    pub title: String,
698    pub user: SimpleUser,
699    pub body: Option<String>,
700    pub labels: Vec<Label>,
701    pub milestone: Option<Milestone>,
702    pub active_lock_reason: Option<String>,
703    pub created_at: String,
704    pub updated_at: String,
705    pub closed_at: Option<String>,
706    pub merged_at: Option<String>,
707    pub merge_commit_sha: Option<String>,
708    pub assignee: Option<SimpleUser>,
709    pub assignees: Option<Vec<SimpleUser>>,
710    pub requested_reviewers: Option<Vec<SimpleUser>>,
711    pub requested_teams: Option<Vec<Team>>,
712    pub head: PullRequestHead,
713    pub base: PullRequestHead,
714    pub _links: Links,
715    pub author_association: AuthorAssociation,
716    pub auto_merge: Option<AutoMerge>,
717    pub draft: Option<bool>,
718    pub merged: Option<bool>,
719    pub mergeable: Option<bool>,
720    pub rebaseable: Option<bool>,
721    pub mergeable_state: Option<String>,
722    pub merged_by: Option<SimpleUser>,
723    pub comments: Option<u64>,
724    pub review_comments: Option<u64>,
725    pub maintainer_can_modify: Option<bool>,
726    pub commits: Option<u64>,
727    pub additions: Option<u64>,
728    pub deletions: Option<u64>,
729    pub changed_files: Option<u64>,
730}
731
732impl CommentFetcher for PullRequest {
733    fn comments_url(&self) -> &str {
734        &self.comments_url
735    }
736}
737
738#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
739#[serde(deny_unknown_fields)]
740pub struct NotificationSubject {
741    pub title: String,
742    pub url: String,
743    pub latest_comment_url: Option<String>,
744    pub r#type: String,
745}
746
747#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
748#[serde(deny_unknown_fields)]
749pub struct Notification {
750    pub id: String,
751    pub unread: bool,
752    pub reason: String,
753    pub updated_at: String,
754    pub last_read_at: Option<String>,
755    pub subject: NotificationSubject,
756    pub repository: Repository,
757    pub url: String,
758    pub subscription_url: String,
759}
760
761impl Notification {
762    pub async fn fetch_all<Tz: chrono::TimeZone>(
763        all: bool,
764        participating: bool,
765        since: Option<DateTime<Tz>>,
766        before: Option<DateTime<Tz>>,
767    ) -> Result<Vec<Notification>, Box<dyn std::error::Error>> {
768        let url = UrlBuilder::new("https://api.github.com/notifications")
769            .param("all", if all { Some("true") } else { None })
770            .param(
771                "participating",
772                if participating { Some("true") } else { None },
773            )
774            .param("since", since.map(|s| s.to_rfc3339()))
775            .param("before", before.map(|s| s.to_rfc3339()))
776            .build();
777
778        let client = GitHubClient::new()?;
779        let mut notifications: Vec<Notification> = client.get(&url).send().await?.json().await?;
780
781        notifications.sort_by_key(|n| n.updated_at.clone());
782        Ok(notifications)
783    }
784
785    pub async fn fetch_pull_request(&self) -> Result<PullRequest, Box<dyn std::error::Error>> {
786        if self.subject.r#type != "PullRequest" {
787            return Err("not a pull request".into());
788        }
789
790        let client = GitHubClient::new()?;
791        let pull_request: PullRequest = client.get(&self.subject.url).send().await?.json().await?;
792
793        Ok(pull_request)
794    }
795
796    pub async fn fetch_issue(&self) -> Result<Issue, Box<dyn std::error::Error>> {
797        if self.subject.r#type != "Issue" {
798            return Err("not an issue".into());
799        }
800
801        let client = GitHubClient::new()?;
802        let issue: Issue = client.get(&self.subject.url).send().await?.json().await?;
803
804        Ok(issue)
805    }
806
807    pub async fn mark_as_read(&self) -> Result<(), Box<dyn std::error::Error>> {
808        let client = GitHubClient::new()?;
809        let url = format!("https://api.github.com/notifications/threads/{}", self.id);
810
811        client
812            .request(reqwest::Method::PATCH, &url)
813            .send()
814            .await?
815            .error_for_status()?;
816
817        Ok(())
818    }
819}
820
821#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
822#[serde(deny_unknown_fields)]
823pub struct ReferencedWorkflow {
824    path: String,
825    sha: String,
826    r#ref: Option<String>,
827}
828
829#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
830#[serde(deny_unknown_fields)]
831pub struct Action {
832    pub id: i64,
833    pub name: Option<String>,
834    pub node_id: String,
835    pub check_suite_id: Option<u64>,
836    pub check_suite_node_id: Option<String>,
837    pub head_branch: Option<String>,
838    pub head_sha: String,
839    pub path: String,
840    pub run_number: u64,
841    pub run_attempt: Option<u64>,
842    pub referenced_workflows: Vec<ReferencedWorkflow>,
843    pub event: String,
844    pub status: Option<String>,
845    pub conclusion: Option<String>,
846    pub workflow_id: u64,
847    pub url: String,
848    pub html_url: String,
849    pub pull_requests: Vec<IssuePullRequest>,
850    pub created_at: String,
851    pub updated_at: String,
852    pub actor: Option<SimpleUser>,
853    pub triggering_actor: Option<SimpleUser>,
854    pub run_started_at: Option<String>,
855    pub jobs_url: String,
856    pub logs_url: String,
857    pub check_suite_url: String,
858    pub artifacts_url: String,
859    pub cancel_url: String,
860    pub rerun_url: String,
861    pub previous_attempt_url: Option<String>,
862    pub workflow_url: String,
863    pub head_commit: HeadCommit,
864    pub repository: Repository,
865    pub head_repository: Repository,
866    pub head_repository_id: Option<u64>,
867    pub display_title: String,
868}
869
870#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
871#[serde(deny_unknown_fields)]
872pub struct Runs {
873    pub total_count: u64,
874    pub workflow_runs: Vec<Action>,
875}
876
877#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
878#[serde(deny_unknown_fields)]
879pub struct RunJobs {
880    pub total_count: u64,
881    pub jobs: Vec<Job>,
882}
883
884impl Action {
885    #[allow(clippy::too_many_arguments)]
886    pub async fn fetch_all(
887        owner: String,
888        repo: String,
889        actor: Option<String>,
890        workflow_run_branch: Option<String>,
891        event: Option<String>,
892        workflow_run_status: Option<String>,
893        per_page: Option<u64>,
894        page: Option<u64>,
895    ) -> Result<Vec<Action>, Box<dyn std::error::Error>> {
896        let url = UrlBuilder::new(format!(
897            "https://api.github.com/repos/{owner}/{repo}/actions/runs"
898        ))
899        .param("actor", actor)
900        .param("workflow_run_branch", workflow_run_branch)
901        .param("event", event)
902        .param("workflow_run_status", workflow_run_status)
903        .param("per_page", per_page)
904        .param("page", page)
905        .build();
906
907        let client = GitHubClient::new()?;
908        let mut runs: Runs = client.get(&url).send().await?.json().await?;
909
910        runs.workflow_runs.sort_by_key(|n| n.updated_at.clone());
911        Ok(runs.workflow_runs)
912    }
913
914    pub async fn fetch_jobs(&self) -> Result<Vec<Job>, Box<dyn std::error::Error>> {
915        let client = GitHubClient::new()?;
916        let run_jobs: RunJobs = client.get(&self.jobs_url).send().await?.json().await?;
917
918        Ok(run_jobs.jobs)
919    }
920}
921
922#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
923#[serde(deny_unknown_fields)]
924pub struct JobStep {
925    pub status: String,
926    pub conclusion: Option<String>,
927    pub name: String,
928    pub number: u64,
929    pub started_at: Option<String>,
930    pub completed_at: Option<String>,
931}
932
933#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
934#[serde(deny_unknown_fields)]
935pub struct Job {
936    pub id: i64,
937    pub run_id: u64,
938    pub run_url: String,
939    pub run_attempt: Option<u64>,
940    pub node_id: String,
941    pub head_sha: String,
942    pub url: String,
943    pub html_url: Option<String>,
944    pub status: String,
945    pub conclusion: Option<String>,
946    pub created_at: String,
947    pub started_at: String,
948    pub completed_at: Option<String>,
949    pub name: String,
950    pub steps: Vec<JobStep>,
951    pub check_run_url: String,
952    pub labels: Vec<String>,
953    pub runner_id: Option<u64>,
954    pub runner_name: Option<String>,
955    pub runner_group_id: Option<u64>,
956    pub runner_group_name: Option<String>,
957    pub workflow_name: Option<String>,
958    pub head_branch: Option<String>,
959}