1use crate::github::error::{GitHubError, GitHubResult};
28use jsonwebtoken::EncodingKey;
29use octocrab::{Octocrab, models::AppId};
30use std::sync::Arc;
31
32#[derive(Clone, Debug)]
37pub struct GitHubClient {
38 inner: Arc<Octocrab>,
39}
40
41impl GitHubClient {
42 #[must_use]
44 pub fn builder() -> GitHubClientBuilder {
45 GitHubClientBuilder::new()
46 }
47
48 pub fn with_token(token: impl Into<String>) -> GitHubResult<Self> {
50 Self::builder().personal_token(token).build()
51 }
52
53 #[must_use]
55 pub fn inner(&self) -> &Arc<Octocrab> {
56 &self.inner
57 }
58
59 pub fn get_issue(
65 &self,
66 owner: impl Into<String>,
67 repo: impl Into<String>,
68 issue_number: u64,
69 ) -> crate::runtime::AsyncTask<Result<octocrab::models::issues::Issue, GitHubError>> {
70 crate::github::get_issue::get_issue(self.inner.clone(), owner, repo, issue_number)
71 }
72
73 pub fn create_issue(
75 &self,
76 owner: impl Into<String>,
77 repo: impl Into<String>,
78 title: impl Into<String>,
79 body: Option<String>,
80 assignees: Option<Vec<String>>,
81 labels: Option<Vec<String>>,
82 ) -> crate::runtime::AsyncTask<Result<octocrab::models::issues::Issue, GitHubError>> {
83 crate::github::create_issue::create_issue(
84 self.inner.clone(),
85 owner,
86 repo,
87 title,
88 body,
89 assignees,
90 labels,
91 )
92 }
93
94 pub fn add_issue_comment(
96 &self,
97 owner: impl Into<String>,
98 repo: impl Into<String>,
99 issue_number: u64,
100 body: impl Into<String>,
101 ) -> crate::runtime::AsyncTask<Result<octocrab::models::issues::Comment, GitHubError>> {
102 crate::github::add_issue_comment::add_issue_comment(
103 self.inner.clone(),
104 owner,
105 repo,
106 issue_number,
107 body,
108 )
109 }
110
111 pub fn get_issue_comments(
113 &self,
114 owner: impl Into<String>,
115 repo: impl Into<String>,
116 issue_number: u64,
117 ) -> crate::runtime::AsyncStream<Result<octocrab::models::issues::Comment, GitHubError>> {
118 crate::github::get_issue_comments::get_issue_comments(
119 self.inner.clone(),
120 owner,
121 repo,
122 issue_number,
123 )
124 }
125
126 #[must_use]
128 pub fn list_issues(
129 &self,
130 request: crate::github::ListIssuesRequest,
131 ) -> crate::runtime::AsyncStream<Result<octocrab::models::issues::Issue, GitHubError>> {
132 crate::github::list_issues::list_issues(self.inner.clone(), request)
133 }
134
135 #[must_use]
137 pub fn update_issue(
138 &self,
139 request: crate::github::UpdateIssueRequest,
140 ) -> crate::runtime::AsyncTask<Result<octocrab::models::issues::Issue, GitHubError>> {
141 crate::github::update_issue::update_issue(self.inner.clone(), request)
142 }
143
144 pub fn search_issues(
146 &self,
147 query: impl Into<String>,
148 sort: Option<String>,
149 order: Option<String>,
150 page: Option<u32>,
151 per_page: Option<u8>,
152 ) -> crate::runtime::AsyncStream<Result<octocrab::models::issues::Issue, GitHubError>> {
153 crate::github::search_issues::search_issues(
154 self.inner.clone(),
155 query,
156 sort,
157 order,
158 page,
159 per_page,
160 )
161 }
162
163 #[must_use]
169 pub fn create_pull_request(
170 &self,
171 request: crate::github::CreatePullRequestRequest,
172 ) -> crate::runtime::AsyncTask<Result<octocrab::models::pulls::PullRequest, GitHubError>> {
173 crate::github::create_pull_request::create_pull_request(self.inner.clone(), request)
174 }
175
176 pub fn get_pull_request_status(
178 &self,
179 owner: impl Into<String>,
180 repo: impl Into<String>,
181 pr_number: u64,
182 ) -> crate::runtime::AsyncTask<Result<octocrab::models::CombinedStatus, GitHubError>> {
183 crate::github::get_pull_request_status::get_pull_request_status(
184 self.inner.clone(),
185 owner,
186 repo,
187 pr_number,
188 )
189 }
190
191 pub fn get_pull_request_comments(
193 &self,
194 owner: impl Into<String>,
195 repo: impl Into<String>,
196 pr_number: u64,
197 ) -> crate::runtime::AsyncStream<Result<octocrab::models::pulls::Comment, GitHubError>> {
198 crate::github::get_pull_request_comments::get_pull_request_comments(
199 self.inner.clone(),
200 owner,
201 repo,
202 pr_number,
203 )
204 }
205
206 pub fn get_pull_request_files(
208 &self,
209 owner: impl Into<String>,
210 repo: impl Into<String>,
211 pr_number: u64,
212 ) -> crate::runtime::AsyncStream<Result<octocrab::models::repos::DiffEntry, GitHubError>> {
213 crate::github::get_pull_request_files::get_pull_request_files(
214 self.inner.clone(),
215 owner,
216 repo,
217 pr_number,
218 )
219 }
220
221 pub fn get_pull_request_reviews(
223 &self,
224 owner: impl Into<String>,
225 repo: impl Into<String>,
226 pr_number: u64,
227 ) -> crate::runtime::AsyncStream<Result<octocrab::models::pulls::Review, GitHubError>> {
228 crate::github::get_pull_request_reviews::get_pull_request_reviews(
229 self.inner.clone(),
230 owner,
231 repo,
232 pr_number,
233 )
234 }
235
236 pub fn create_pull_request_review(
238 &self,
239 owner: impl Into<String>,
240 repo: impl Into<String>,
241 pr_number: u64,
242 options: crate::github::CreatePullRequestReviewOptions,
243 ) -> crate::runtime::AsyncTask<Result<octocrab::models::pulls::Review, GitHubError>> {
244 crate::github::create_pull_request_review::create_pull_request_review(
245 self.inner.clone(),
246 owner,
247 repo,
248 pr_number,
249 options,
250 )
251 }
252
253 #[must_use]
255 pub fn add_pull_request_review_comment(
256 &self,
257 request: crate::github::AddPullRequestReviewCommentRequest,
258 ) -> crate::runtime::AsyncTask<Result<octocrab::models::pulls::ReviewComment, GitHubError>>
259 {
260 crate::github::add_pull_request_review_comment::add_pull_request_review_comment(
261 self.inner.clone(),
262 request,
263 )
264 }
265
266 pub fn update_pull_request(
268 &self,
269 owner: impl Into<String>,
270 repo: impl Into<String>,
271 pr_number: u64,
272 options: crate::github::UpdatePullRequestOptions,
273 ) -> crate::runtime::AsyncTask<Result<octocrab::models::pulls::PullRequest, GitHubError>> {
274 crate::github::update_pull_request::update_pull_request(
275 self.inner.clone(),
276 owner,
277 repo,
278 pr_number,
279 options,
280 )
281 }
282
283 pub fn merge_pull_request(
285 &self,
286 owner: impl Into<String>,
287 repo: impl Into<String>,
288 pr_number: u64,
289 options: crate::github::MergePullRequestOptions,
290 ) -> crate::runtime::AsyncTask<Result<serde_json::Value, GitHubError>> {
291 crate::github::merge_pull_request::merge_pull_request(
292 self.inner.clone(),
293 owner,
294 repo,
295 pr_number,
296 options,
297 )
298 }
299
300 pub fn get_file_contents(
306 &self,
307 owner: impl Into<String>,
308 repo: impl Into<String>,
309 path: impl Into<String>,
310 ref_name: Option<String>,
311 ) -> crate::runtime::AsyncTask<Result<Vec<octocrab::models::repos::Content>, GitHubError>> {
312 crate::github::get_file_contents::get_file_contents(
313 self.inner.clone(),
314 owner,
315 repo,
316 path,
317 ref_name,
318 )
319 }
320
321 #[must_use]
323 pub fn create_or_update_file(
324 &self,
325 request: crate::github::CreateOrUpdateFileRequest,
326 ) -> crate::runtime::AsyncTask<Result<octocrab::models::repos::FileUpdate, GitHubError>> {
327 crate::github::create_or_update_file::create_or_update_file(self.inner.clone(), request)
328 }
329
330 pub fn list_branches(
332 &self,
333 owner: impl Into<String>,
334 repo: impl Into<String>,
335 page: Option<u32>,
336 per_page: Option<u8>,
337 ) -> crate::runtime::AsyncTask<Result<Vec<octocrab::models::repos::Branch>, GitHubError>> {
338 crate::github::list_branches::list_branches(self.inner.clone(), owner, repo, page, per_page)
339 }
340
341 pub fn create_branch(
343 &self,
344 owner: impl Into<String>,
345 repo: impl Into<String>,
346 branch_name: impl Into<String>,
347 sha: impl Into<String>,
348 ) -> crate::runtime::AsyncTask<Result<octocrab::models::repos::Ref, GitHubError>> {
349 crate::github::create_branch::create_branch(
350 self.inner.clone(),
351 owner,
352 repo,
353 branch_name,
354 sha,
355 )
356 }
357
358 pub fn list_commits(
360 &self,
361 owner: impl Into<String>,
362 repo: impl Into<String>,
363 options: crate::github::ListCommitsOptions,
364 ) -> crate::runtime::AsyncTask<Result<Vec<octocrab::models::repos::RepoCommit>, GitHubError>>
365 {
366 crate::github::list_commits::list_commits(self.inner.clone(), owner, repo, options)
367 }
368
369 pub fn get_commit(
371 &self,
372 owner: impl Into<String>,
373 repo: impl Into<String>,
374 commit_sha: impl Into<String>,
375 page: Option<u32>,
376 per_page: Option<u8>,
377 ) -> crate::runtime::AsyncTask<Result<octocrab::models::repos::RepoCommit, GitHubError>> {
378 crate::github::get_commit::get_commit(
379 self.inner.clone(),
380 owner,
381 repo,
382 commit_sha,
383 page,
384 per_page,
385 )
386 }
387
388 pub fn search_code(
390 &self,
391 query: impl Into<String>,
392 sort: Option<String>,
393 order: Option<String>,
394 page: Option<u32>,
395 per_page: Option<u8>,
396 enrich_stars: bool,
397 ) -> crate::runtime::AsyncTask<Result<octocrab::Page<octocrab::models::Code>, GitHubError>>
398 {
399 crate::github::search_code::search_code(
400 self.inner.clone(),
401 query,
402 sort,
403 order,
404 page,
405 per_page,
406 enrich_stars,
407 )
408 }
409
410 pub fn create_repository(
412 &self,
413 name: impl Into<String>,
414 description: Option<String>,
415 private: Option<bool>,
416 auto_init: Option<bool>,
417 ) -> crate::runtime::AsyncTask<Result<octocrab::models::Repository, GitHubError>> {
418 crate::github::create_repository::create_repository(
419 self.inner.clone(),
420 name,
421 description,
422 private,
423 auto_init,
424 )
425 }
426
427 pub fn fork_repository(
429 &self,
430 owner: impl Into<String>,
431 repo: impl Into<String>,
432 organization: Option<String>,
433 ) -> crate::runtime::AsyncTask<Result<octocrab::models::Repository, GitHubError>> {
434 crate::github::fork_repository::fork_repository(
435 self.inner.clone(),
436 owner,
437 repo,
438 organization,
439 )
440 }
441
442 pub fn push_files(
444 &self,
445 owner: impl Into<String>,
446 repo: impl Into<String>,
447 branch: impl Into<String>,
448 files: std::collections::HashMap<String, String>,
449 commit_message: impl Into<String>,
450 ) -> crate::runtime::AsyncTask<Result<octocrab::models::repos::Commit, GitHubError>> {
451 crate::github::push_files::push_files(
452 self.inner.clone(),
453 owner,
454 repo,
455 branch,
456 files,
457 commit_message,
458 )
459 }
460
461 #[must_use]
467 pub fn get_me(
468 &self,
469 ) -> crate::runtime::AsyncTask<Result<octocrab::models::Author, GitHubError>> {
470 crate::github::get_me::get_me(self.inner.clone())
471 }
472
473 pub fn search_users(
475 &self,
476 query: impl Into<String>,
477 sort: Option<crate::github::search_users::UserSearchSort>,
478 order: Option<crate::github::search_users::SearchOrder>,
479 page: Option<u32>,
480 per_page: Option<u8>,
481 ) -> crate::runtime::AsyncTask<Result<octocrab::Page<octocrab::models::Author>, GitHubError>>
482 {
483 crate::github::search_users::search_users(
484 self.inner.clone(),
485 query,
486 sort,
487 order,
488 page,
489 per_page,
490 )
491 }
492
493 pub fn list_code_scanning_alerts(
499 &self,
500 owner: impl Into<String>,
501 repo: impl Into<String>,
502 state: Option<String>,
503 ref_name: Option<String>,
504 tool_name: Option<String>,
505 severity: Option<String>,
506 ) -> crate::runtime::AsyncTask<Result<Vec<serde_json::Value>, GitHubError>> {
507 crate::github::code_scanning_alerts::list_code_scanning_alerts(
508 self.inner.clone(),
509 owner,
510 repo,
511 state,
512 ref_name,
513 tool_name,
514 severity,
515 )
516 }
517
518 pub fn get_code_scanning_alert(
520 &self,
521 owner: impl Into<String>,
522 repo: impl Into<String>,
523 alert_number: u64,
524 ) -> crate::runtime::AsyncTask<Result<serde_json::Value, GitHubError>> {
525 crate::github::code_scanning_alerts::get_code_scanning_alert(
526 self.inner.clone(),
527 owner,
528 repo,
529 alert_number,
530 )
531 }
532
533 pub fn list_secret_scanning_alerts(
535 &self,
536 owner: impl Into<String>,
537 repo: impl Into<String>,
538 state: Option<String>,
539 secret_type: Option<String>,
540 resolution: Option<String>,
541 ) -> crate::runtime::AsyncTask<
542 Result<
543 Vec<octocrab::models::repos::secret_scanning_alert::SecretScanningAlert>,
544 GitHubError,
545 >,
546 > {
547 crate::github::secret_scanning_alerts::list_secret_scanning_alerts(
548 self.inner.clone(),
549 owner,
550 repo,
551 state,
552 secret_type,
553 resolution,
554 )
555 }
556
557 pub fn get_secret_scanning_alert(
559 &self,
560 owner: impl Into<String>,
561 repo: impl Into<String>,
562 alert_number: u32,
563 ) -> crate::runtime::AsyncTask<
564 Result<octocrab::models::repos::secret_scanning_alert::SecretScanningAlert, GitHubError>,
565 > {
566 crate::github::secret_scanning_alerts::get_secret_scanning_alert(
567 self.inner.clone(),
568 owner,
569 repo,
570 alert_number,
571 )
572 }
573
574 pub async fn upload_release_asset(
583 &self,
584 owner: impl Into<String>,
585 repo: impl Into<String>,
586 options: crate::github::upload_release_asset::UploadAssetOptions,
587 ) -> Result<octocrab::models::repos::Asset, crate::github::error::GitHubError> {
588 crate::github::upload_release_asset::upload_release_asset(
589 self.inner.clone(),
590 &owner.into(),
591 &repo.into(),
592 options,
593 )
594 .await
595 .map_err(crate::github::error::GitHubError::from)
596 }
597
598 pub async fn delete_release_asset(
600 &self,
601 owner: impl Into<String>,
602 repo: impl Into<String>,
603 asset_id: u64,
604 ) -> Result<(), crate::github::error::GitHubError> {
605 crate::github::upload_release_asset::delete_release_asset(
606 self.inner.clone(),
607 &owner.into(),
608 &repo.into(),
609 asset_id,
610 )
611 .await
612 .map_err(crate::github::error::GitHubError::from)
613 }
614
615 pub fn request_copilot_review(
621 &self,
622 owner: impl Into<String>,
623 repo: impl Into<String>,
624 pr_number: u64,
625 ) -> crate::runtime::AsyncTask<Result<(), GitHubError>> {
626 crate::github::request_copilot_review::request_copilot_review(
627 self.inner.clone(),
628 owner,
629 repo,
630 pr_number,
631 )
632 }
633}
634
635pub struct GitHubClientBuilder {
637 token: Option<String>,
638 app_auth: Option<(AppId, String)>,
639 base_uri: Option<String>,
640}
641
642impl GitHubClientBuilder {
643 #[must_use]
645 pub fn new() -> Self {
646 Self {
647 token: None,
648 app_auth: None,
649 base_uri: None,
650 }
651 }
652
653 pub fn personal_token(mut self, token: impl Into<String>) -> Self {
655 self.token = Some(token.into());
656 self
657 }
658
659 pub fn app(mut self, app_id: AppId, private_key: impl Into<String>) -> Self {
661 self.app_auth = Some((app_id, private_key.into()));
662 self
663 }
664
665 pub fn base_uri(mut self, uri: impl Into<String>) -> Self {
667 self.base_uri = Some(uri.into());
668 self
669 }
670
671 pub fn build(self) -> GitHubResult<GitHubClient> {
673 let mut builder = Octocrab::builder();
674
675 if let Some(token) = self.token {
677 builder = builder.personal_token(token);
678 } else if let Some((app_id, private_key)) = self.app_auth {
679 let key = EncodingKey::from_rsa_pem(private_key.as_bytes())
680 .map_err(|e| GitHubError::ClientSetup(format!("Invalid RSA key: {e}")))?;
681 builder = builder.app(app_id, key);
682 }
683
684 if let Some(uri) = self.base_uri {
686 builder = builder
687 .base_uri(&uri)
688 .map_err(|e| GitHubError::ClientSetup(e.to_string()))?;
689 }
690
691 let octocrab = builder
693 .build()
694 .map_err(|e| GitHubError::ClientSetup(e.to_string()))?;
695
696 Ok(GitHubClient {
697 inner: Arc::new(octocrab),
698 })
699 }
700}
701
702impl Default for GitHubClientBuilder {
703 fn default() -> Self {
704 Self::new()
705 }
706}