1#![allow(clippy::derive_partial_eq_without_eq)]
170#![allow(clippy::too_many_arguments)]
171#![allow(clippy::nonstandard_macro_braces)]
172#![allow(clippy::large_enum_variant)]
173#![allow(clippy::tabs_in_doc_comments)]
174#![allow(missing_docs)]
175#![cfg_attr(docsrs, feature(doc_cfg))]
176
177pub mod actions;
179pub mod activity;
181pub mod apps;
183pub mod auth;
184pub mod billing;
186pub mod checks;
188pub mod code_scanning;
190pub mod codes_of_conduct;
192pub mod emojis;
194pub mod enterprise_admin;
196pub mod gists;
198pub mod git;
200pub mod gitignore;
202#[cfg(feature = "httpcache")]
203#[cfg_attr(docsrs, doc(cfg(feature = "httpcache")))]
204pub mod http_cache;
205pub mod interactions;
207pub mod issues;
209pub mod licenses;
211pub mod markdown;
213pub mod meta;
215pub mod migrations;
217pub mod oauth_authorizations;
219pub mod orgs;
221pub mod packages;
223pub mod projects;
225pub mod pulls;
227pub mod rate_limit;
229pub mod reactions;
231pub mod repos;
233pub mod scim;
235pub mod search;
237pub mod secret_scanning;
239pub mod teams;
241pub mod types;
242pub mod users;
244#[doc(hidden)]
245pub mod utils;
246
247pub use reqwest::{header::HeaderMap, StatusCode};
248
249#[derive(Debug)]
250pub struct Response<T> {
251 pub status: reqwest::StatusCode,
252 pub headers: reqwest::header::HeaderMap,
253 pub body: T,
254}
255
256impl<T> Response<T> {
257 pub fn new(status: reqwest::StatusCode, headers: reqwest::header::HeaderMap, body: T) -> Self {
258 Self {
259 status,
260 headers,
261 body,
262 }
263 }
264}
265
266type ClientResult<T> = Result<T, ClientError>;
267
268use thiserror::Error;
269
270#[derive(Debug, Error)]
272pub enum ClientError {
273 #[error("Rate limited for the next {duration} seconds")]
276 RateLimited { duration: u64 },
277 #[error(transparent)]
279 JsonWebTokenError(#[from] jsonwebtoken::errors::Error),
280 #[cfg(feature = "httpcache")]
282 #[error(transparent)]
283 #[cfg(feature = "httpcache")]
284 IoError(#[from] std::io::Error),
285 #[error(transparent)]
287 UrlParserError(#[from] url::ParseError),
288 #[error(transparent)]
290 SerdeJsonError(#[from] serde_json::Error),
291 #[error(transparent)]
293 ReqwestError(#[from] reqwest::Error),
294 #[error(transparent)]
296 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
297 #[cfg(feature = "middleware")]
298 #[error(transparent)]
300 ReqwestMiddleWareError(#[from] reqwest_middleware::Error),
301 #[error("HTTP Error. Code: {status}, message: {error}")]
303 HttpError {
304 status: http::StatusCode,
305 headers: reqwest::header::HeaderMap,
306 error: String,
307 },
308}
309
310pub const FALLBACK_HOST: &str = "https://api.github.com";
311
312mod progenitor_support {
313 use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
314
315 const PATH_SET: &AsciiSet = &CONTROLS
316 .add(b' ')
317 .add(b'"')
318 .add(b'#')
319 .add(b'<')
320 .add(b'>')
321 .add(b'?')
322 .add(b'`')
323 .add(b'{')
324 .add(b'}');
325
326 #[allow(dead_code)]
327 pub(crate) fn encode_path(pc: &str) -> String {
328 utf8_percent_encode(pc, PATH_SET).to_string()
329 }
330}
331
332#[derive(Debug, Default)]
333pub(crate) struct Message {
334 pub body: Option<reqwest::Body>,
335 pub content_type: Option<String>,
336}
337
338#[derive(Debug, Default, Clone)]
339pub struct RootDefaultServer {}
340
341impl RootDefaultServer {
342 pub fn default_url(&self) -> &str {
343 "https://api.github.com"
344 }
345}
346
347#[derive(Clone)]
349pub struct Client {
350 host: String,
351 host_override: Option<String>,
352 agent: String,
353 #[cfg(feature = "middleware")]
354 client: reqwest_middleware::ClientWithMiddleware,
355 #[cfg(not(feature = "middleware"))]
356 client: reqwest::Client,
357 credentials: Option<crate::auth::Credentials>,
358 #[cfg(feature = "httpcache")]
359 http_cache: crate::http_cache::BoxedHttpCache,
360}
361
362impl Client {
363 pub fn new<A, C>(agent: A, credentials: C) -> ClientResult<Self>
364 where
365 A: Into<String>,
366 C: Into<Option<crate::auth::Credentials>>,
367 {
368 let http = reqwest::Client::builder()
369 .redirect(reqwest::redirect::Policy::none())
370 .build()?;
371
372 #[cfg(feature = "middleware")]
373 let client = {
374 let retry_policy =
375 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
376 reqwest_middleware::ClientBuilder::new(http)
377 .with(reqwest_tracing::TracingMiddleware::default())
379 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
381 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
382 |req: &reqwest::Request| req.try_clone().is_some(),
383 ))
384 .build()
385 };
386 #[cfg(not(feature = "middleware"))]
387 let client = http;
388
389 #[cfg(feature = "httpcache")]
390 {
391 Ok(Self::custom(
392 agent,
393 credentials,
394 client,
395 <dyn crate::http_cache::HttpCache>::noop(),
396 ))
397 }
398 #[cfg(not(feature = "httpcache"))]
399 {
400 Ok(Self::custom(agent, credentials, client))
401 }
402 }
403
404 #[cfg(feature = "httpcache")]
405 pub fn custom<A, CR>(
406 agent: A,
407 credentials: CR,
408 #[cfg(feature = "middleware")] http: reqwest_middleware::ClientWithMiddleware,
409 #[cfg(not(feature = "middleware"))] http: reqwest::Client,
410 http_cache: crate::http_cache::BoxedHttpCache,
411 ) -> Self
412 where
413 A: Into<String>,
414 CR: Into<Option<crate::auth::Credentials>>,
415 {
416 Self {
417 host: RootDefaultServer::default().default_url().to_string(),
418 host_override: None,
419 agent: agent.into(),
420 client: http,
421 credentials: credentials.into(),
422 http_cache,
423 }
424 }
425
426 #[cfg(not(feature = "httpcache"))]
427 pub fn custom<A, CR>(
428 agent: A,
429 credentials: CR,
430 #[cfg(feature = "middleware")] http: reqwest_middleware::ClientWithMiddleware,
431 #[cfg(not(feature = "middleware"))] http: reqwest::Client,
432 ) -> Self
433 where
434 A: Into<String>,
435 CR: Into<Option<crate::auth::Credentials>>,
436 {
437 Self {
438 host: RootDefaultServer::default().default_url().to_string(),
439 host_override: None,
440 agent: agent.into(),
441 client: http,
442 credentials: credentials.into(),
443 }
444 }
445
446 pub fn with_host_override<H>(&mut self, host: H) -> &mut Self
448 where
449 H: ToString,
450 {
451 self.host_override = Some(host.to_string());
452 self
453 }
454
455 pub fn remove_host_override(&mut self) -> &mut Self {
457 self.host_override = None;
458 self
459 }
460
461 pub fn get_host_override(&self) -> Option<&str> {
462 self.host_override.as_deref()
463 }
464
465 pub(crate) fn url(&self, path: &str, host: Option<&str>) -> String {
466 format!(
467 "{}{}",
468 self.get_host_override()
469 .or(host)
470 .unwrap_or(self.host.as_str()),
471 path
472 )
473 }
474
475 pub fn set_credentials<CR>(&mut self, credentials: CR)
476 where
477 CR: Into<Option<crate::auth::Credentials>>,
478 {
479 self.credentials = credentials.into();
480 }
481
482 fn credentials(
483 &self,
484 authentication: crate::auth::AuthenticationConstraint,
485 ) -> Option<&crate::auth::Credentials> {
486 match (authentication, self.credentials.as_ref()) {
487 (crate::auth::AuthenticationConstraint::Unconstrained, creds) => creds,
488 (
489 crate::auth::AuthenticationConstraint::JWT,
490 creds @ Some(&crate::auth::Credentials::JWT(_)),
491 ) => creds,
492 (
493 crate::auth::AuthenticationConstraint::JWT,
494 Some(crate::auth::Credentials::InstallationToken(apptoken)),
495 ) => Some(apptoken.jwt()),
496 (crate::auth::AuthenticationConstraint::JWT, _) => {
497 log::info!(
498 "Request needs JWT authentication but only a mismatched method is available"
499 );
500 None
501 }
502 }
503 }
504
505 async fn url_and_auth(
506 &self,
507 uri: &str,
508 authentication: crate::auth::AuthenticationConstraint,
509 ) -> ClientResult<(reqwest::Url, Option<String>)> {
510 let mut parsed_url = uri.parse::<reqwest::Url>()?;
511
512 match self.credentials(authentication) {
513 Some(crate::auth::Credentials::Client(id, secret)) => {
514 parsed_url
515 .query_pairs_mut()
516 .append_pair("client_id", id)
517 .append_pair("client_secret", secret);
518 Ok((parsed_url, None))
519 }
520 Some(crate::auth::Credentials::Token(token)) => {
521 let auth = format!("token {}", token);
522 Ok((parsed_url, Some(auth)))
523 }
524 Some(crate::auth::Credentials::JWT(jwt)) => {
525 let auth = format!("Bearer {}", jwt.token());
526 Ok((parsed_url, Some(auth)))
527 }
528 Some(crate::auth::Credentials::InstallationToken(apptoken)) => {
529 let token = if let Some(token) = apptoken.token().await {
530 token
531 } else {
532 let mut token_guard = apptoken.access_key.write().await;
533 if let Some(token) = token_guard.as_ref().and_then(|t| t.token()) {
534 token.to_owned()
535 } else {
536 log::debug!("app token is stale, refreshing");
537
538 let created_at = tokio::time::Instant::now();
539 let token = self
540 .apps()
541 .create_installation_access_token(
542 apptoken.installation_id,
543 &types::AppsCreateInstallationAccessTokenRequest {
544 permissions: Default::default(),
545 repositories: Default::default(),
546 repository_ids: Default::default(),
547 },
548 )
549 .await?;
550 *token_guard = Some(crate::auth::ExpiringInstallationToken::new(
551 token.body.token.clone(),
552 created_at,
553 ));
554 token.body.token
555 }
556 };
557 let auth = format!("token {}", token);
558 Ok((parsed_url, Some(auth)))
559 }
560 None => Ok((parsed_url, None)),
561 }
562 }
563
564 #[cfg(feature = "middleware")]
565 async fn make_request(
566 &self,
567 method: http::Method,
568 uri: &str,
569 message: Message,
570 media_type: crate::utils::MediaType,
571 authentication: crate::auth::AuthenticationConstraint,
572 ) -> ClientResult<reqwest_middleware::RequestBuilder> {
573 let (url, auth) = self.url_and_auth(uri, authentication).await?;
574
575 let mut req = self.client.request(method, url);
576
577 if let Some(content_type) = &message.content_type {
578 req = req.header(http::header::CONTENT_TYPE, content_type.clone());
579 }
580
581 req = req.header(http::header::USER_AGENT, &*self.agent);
582 req = req.header(http::header::ACCEPT, &media_type.to_string());
583
584 if let Some(auth_str) = auth {
585 req = req.header(http::header::AUTHORIZATION, &*auth_str);
586 }
587
588 if let Some(body) = message.body {
589 req = req.body(body);
590 }
591
592 Ok(req)
593 }
594 #[cfg(not(feature = "middleware"))]
595 async fn make_request(
596 &self,
597 method: http::Method,
598 uri: &str,
599 message: Message,
600 media_type: crate::utils::MediaType,
601 authentication: crate::auth::AuthenticationConstraint,
602 ) -> ClientResult<reqwest::RequestBuilder> {
603 let (url, auth) = self.url_and_auth(uri, authentication).await?;
604
605 let mut req = self.client.request(method, url);
606
607 if let Some(content_type) = &message.content_type {
608 req = req.header(http::header::CONTENT_TYPE, content_type.clone());
609 }
610
611 req = req.header(http::header::USER_AGENT, &*self.agent);
612 req = req.header(http::header::ACCEPT, &media_type.to_string());
613
614 if let Some(auth_str) = auth {
615 req = req.header(http::header::AUTHORIZATION, &*auth_str);
616 }
617
618 if let Some(body) = message.body {
619 req = req.body(body);
620 }
621
622 Ok(req)
623 }
624
625 async fn request<Out>(
626 &self,
627 method: http::Method,
628 uri: &str,
629 message: Message,
630 media_type: crate::utils::MediaType,
631 authentication: crate::auth::AuthenticationConstraint,
632 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Out>)>
633 where
634 Out: serde::de::DeserializeOwned + 'static + Send,
635 {
636 #[cfg(not(feature = "httpcache"))]
637 let req = self
638 .make_request(method.clone(), uri, message, media_type, authentication)
639 .await?;
640
641 #[cfg(feature = "httpcache")]
642 let req = {
643 let mut req = self
644 .make_request(method.clone(), uri, message, media_type, authentication)
645 .await?;
646
647 if method == http::Method::GET {
648 if let Ok(etag) = self.http_cache.lookup_etag(&uri) {
649 req = req.header(http::header::IF_NONE_MATCH, etag);
650 }
651 }
652
653 req
654 };
655
656 let response = req.send().await?;
657
658 #[cfg(not(feature = "httpcache"))]
659 let (remaining, reset) = crate::utils::get_header_values(response.headers());
660
661 #[cfg(feature = "httpcache")]
662 let (remaining, reset, etag) = crate::utils::get_header_values(response.headers());
663
664 let status = response.status();
665 let headers = response.headers().clone();
666 let link = response
667 .headers()
668 .get(http::header::LINK)
669 .and_then(|l| l.to_str().ok())
670 .and_then(|l| parse_link_header::parse(l).ok());
671 let next_link = link.as_ref().and_then(crate::utils::next_link);
672
673 let response_body = response.bytes().await?;
674
675 if status.is_success() {
676 log::debug!("Received successful response. Read payload.");
677 #[cfg(feature = "httpcache")]
678 {
679 if let Some(etag) = etag {
680 if let Err(e) = self.http_cache.cache_response(
681 &uri,
682 &response_body,
683 &etag,
684 &next_link.as_ref().map(|n| n.0.clone()),
685 ) {
686 log::info!("failed to cache body & etag: {}", e);
688 }
689 }
690 }
691
692 let parsed_response = if status == http::StatusCode::NO_CONTENT
693 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
694 {
695 serde_json::from_str("null")?
696 } else {
697 serde_json::from_slice::<Out>(&response_body)?
698 };
699 Ok((
700 next_link,
701 crate::Response::new(status, headers, parsed_response),
702 ))
703 } else if status.is_redirection() {
704 match status {
705 http::StatusCode::NOT_MODIFIED => {
706 #[cfg(feature = "httpcache")]
709 {
710 let body = self.http_cache.lookup_body(&uri).unwrap();
711 let out = serde_json::from_str::<Out>(&body).unwrap();
712 let link = match next_link {
713 Some(next_link) => Ok(Some(next_link)),
714 None => self
715 .http_cache
716 .lookup_next_link(&uri)
717 .map(|next_link| next_link.map(crate::utils::NextLink)),
718 };
719 link.map(|link| (link, Response::new(status, headers, out)))
720 }
721 #[cfg(not(feature = "httpcache"))]
722 {
723 unreachable!(
724 "this should not be reachable without the httpcache feature enabled"
725 )
726 }
727 }
728 _ => {
729 let body = if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>() {
732 serde_json::from_str("null")?
733 } else {
734 serde_json::from_slice::<Out>(&response_body)?
735 };
736
737 Ok((None, crate::Response::new(status, headers, body)))
738 }
739 }
740 } else {
741 let error = match (remaining, reset) {
742 (Some(remaining), Some(reset)) if remaining == 0 => {
743 let now = std::time::SystemTime::now()
744 .duration_since(std::time::UNIX_EPOCH)
745 .unwrap()
746 .as_secs();
747 ClientError::RateLimited {
748 duration: u64::from(reset).saturating_sub(now),
749 }
750 }
751 _ => {
752 if response_body.is_empty() {
753 ClientError::HttpError {
754 status,
755 headers,
756 error: "empty response".into(),
757 }
758 } else {
759 ClientError::HttpError {
760 status,
761 headers,
762 error: String::from_utf8_lossy(&response_body).into(),
763 }
764 }
765 }
766 };
767 Err(error)
768 }
769 }
770
771 async fn request_entity<D>(
772 &self,
773 method: http::Method,
774 uri: &str,
775 message: Message,
776 media_type: crate::utils::MediaType,
777 authentication: crate::auth::AuthenticationConstraint,
778 ) -> ClientResult<crate::Response<D>>
779 where
780 D: serde::de::DeserializeOwned + 'static + Send,
781 {
782 let (_, r) = self
783 .request(method, uri, message, media_type, authentication)
784 .await?;
785 Ok(r)
786 }
787
788 async fn get<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
789 where
790 D: serde::de::DeserializeOwned + 'static + Send,
791 {
792 self.get_media(uri, crate::utils::MediaType::Json, message)
793 .await
794 }
795
796 async fn get_media<D>(
797 &self,
798 uri: &str,
799 media: crate::utils::MediaType,
800 message: Message,
801 ) -> ClientResult<crate::Response<D>>
802 where
803 D: serde::de::DeserializeOwned + 'static + Send,
804 {
805 self.request_entity(
806 http::Method::GET,
807 uri,
808 message,
809 media,
810 crate::auth::AuthenticationConstraint::Unconstrained,
811 )
812 .await
813 }
814
815 async fn get_all_pages<D>(
816 &self,
817 uri: &str,
818 _message: Message,
819 ) -> ClientResult<crate::Response<Vec<D>>>
820 where
821 D: serde::de::DeserializeOwned + 'static + Send,
822 {
823 self.unfold(uri).await
824 }
825
826 async fn get_pages<D>(
827 &self,
828 uri: &str,
829 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
830 where
831 D: serde::de::DeserializeOwned + 'static + Send,
832 {
833 self.request(
834 http::Method::GET,
835 uri,
836 Message::default(),
837 crate::utils::MediaType::Json,
838 crate::auth::AuthenticationConstraint::Unconstrained,
839 )
840 .await
841 }
842
843 async fn get_pages_url<D>(
844 &self,
845 url: &reqwest::Url,
846 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
847 where
848 D: serde::de::DeserializeOwned + 'static + Send,
849 {
850 self.request(
851 http::Method::GET,
852 url.as_str(),
853 Message::default(),
854 crate::utils::MediaType::Json,
855 crate::auth::AuthenticationConstraint::Unconstrained,
856 )
857 .await
858 }
859
860 async fn post<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
861 where
862 D: serde::de::DeserializeOwned + 'static + Send,
863 {
864 self.post_media(
865 uri,
866 message,
867 crate::utils::MediaType::Json,
868 crate::auth::AuthenticationConstraint::Unconstrained,
869 )
870 .await
871 }
872
873 async fn post_media<D>(
874 &self,
875 uri: &str,
876 message: Message,
877 media: crate::utils::MediaType,
878 authentication: crate::auth::AuthenticationConstraint,
879 ) -> ClientResult<crate::Response<D>>
880 where
881 D: serde::de::DeserializeOwned + 'static + Send,
882 {
883 self.request_entity(http::Method::POST, uri, message, media, authentication)
884 .await
885 }
886
887 async fn patch_media<D>(
888 &self,
889 uri: &str,
890 message: Message,
891 media: crate::utils::MediaType,
892 ) -> ClientResult<crate::Response<D>>
893 where
894 D: serde::de::DeserializeOwned + 'static + Send,
895 {
896 self.request_entity(
897 http::Method::PATCH,
898 uri,
899 message,
900 media,
901 crate::auth::AuthenticationConstraint::Unconstrained,
902 )
903 .await
904 }
905
906 async fn patch<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
907 where
908 D: serde::de::DeserializeOwned + 'static + Send,
909 {
910 self.patch_media(uri, message, crate::utils::MediaType::Json)
911 .await
912 }
913
914 async fn put<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
915 where
916 D: serde::de::DeserializeOwned + 'static + Send,
917 {
918 self.put_media(uri, message, crate::utils::MediaType::Json)
919 .await
920 }
921
922 async fn put_media<D>(
923 &self,
924 uri: &str,
925 message: Message,
926 media: crate::utils::MediaType,
927 ) -> ClientResult<crate::Response<D>>
928 where
929 D: serde::de::DeserializeOwned + 'static + Send,
930 {
931 self.request_entity(
932 http::Method::PUT,
933 uri,
934 message,
935 media,
936 crate::auth::AuthenticationConstraint::Unconstrained,
937 )
938 .await
939 }
940
941 async fn delete<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
942 where
943 D: serde::de::DeserializeOwned + 'static + Send,
944 {
945 self.request_entity(
946 http::Method::DELETE,
947 uri,
948 message,
949 crate::utils::MediaType::Json,
950 crate::auth::AuthenticationConstraint::Unconstrained,
951 )
952 .await
953 }
954
955 async fn unfold<D>(&self, uri: &str) -> ClientResult<crate::Response<Vec<D>>>
957 where
958 D: serde::de::DeserializeOwned + 'static + Send,
959 {
960 let mut global_items = Vec::new();
961 let (new_link, mut response) = self.get_pages(uri).await?;
962 let mut link = new_link;
963 while !response.body.is_empty() {
964 global_items.append(&mut response.body);
965 if let Some(url) = &link {
967 let url = reqwest::Url::parse(&url.0)?;
968 let (new_link, new_response) = self.get_pages_url(&url).await?;
969 link = new_link;
970 response = new_response;
971 }
972 }
973
974 Ok(Response::new(
975 response.status,
976 response.headers,
977 global_items,
978 ))
979 }
980
981 pub fn actions(&self) -> actions::Actions {
983 actions::Actions::new(self.clone())
984 }
985
986 pub fn activity(&self) -> activity::Activity {
988 activity::Activity::new(self.clone())
989 }
990
991 pub fn apps(&self) -> apps::Apps {
993 apps::Apps::new(self.clone())
994 }
995
996 pub fn billing(&self) -> billing::Billing {
998 billing::Billing::new(self.clone())
999 }
1000
1001 pub fn checks(&self) -> checks::Checks {
1003 checks::Checks::new(self.clone())
1004 }
1005
1006 pub fn code_scanning(&self) -> code_scanning::CodeScanning {
1008 code_scanning::CodeScanning::new(self.clone())
1009 }
1010
1011 pub fn codes_of_conduct(&self) -> codes_of_conduct::CodesOfConduct {
1013 codes_of_conduct::CodesOfConduct::new(self.clone())
1014 }
1015
1016 pub fn emojis(&self) -> emojis::Emojis {
1018 emojis::Emojis::new(self.clone())
1019 }
1020
1021 pub fn enterprise_admin(&self) -> enterprise_admin::EnterpriseAdmin {
1023 enterprise_admin::EnterpriseAdmin::new(self.clone())
1024 }
1025
1026 pub fn gists(&self) -> gists::Gists {
1028 gists::Gists::new(self.clone())
1029 }
1030
1031 pub fn git(&self) -> git::Git {
1033 git::Git::new(self.clone())
1034 }
1035
1036 pub fn gitignore(&self) -> gitignore::Gitignore {
1038 gitignore::Gitignore::new(self.clone())
1039 }
1040
1041 pub fn interactions(&self) -> interactions::Interactions {
1043 interactions::Interactions::new(self.clone())
1044 }
1045
1046 pub fn issues(&self) -> issues::Issues {
1048 issues::Issues::new(self.clone())
1049 }
1050
1051 pub fn licenses(&self) -> licenses::Licenses {
1053 licenses::Licenses::new(self.clone())
1054 }
1055
1056 pub fn markdown(&self) -> markdown::Markdown {
1058 markdown::Markdown::new(self.clone())
1059 }
1060
1061 pub fn meta(&self) -> meta::Meta {
1063 meta::Meta::new(self.clone())
1064 }
1065
1066 pub fn migrations(&self) -> migrations::Migrations {
1068 migrations::Migrations::new(self.clone())
1069 }
1070
1071 pub fn oauth_authorizations(&self) -> oauth_authorizations::OauthAuthorizations {
1073 oauth_authorizations::OauthAuthorizations::new(self.clone())
1074 }
1075
1076 pub fn orgs(&self) -> orgs::Orgs {
1078 orgs::Orgs::new(self.clone())
1079 }
1080
1081 pub fn packages(&self) -> packages::Packages {
1083 packages::Packages::new(self.clone())
1084 }
1085
1086 pub fn projects(&self) -> projects::Projects {
1088 projects::Projects::new(self.clone())
1089 }
1090
1091 pub fn pulls(&self) -> pulls::Pulls {
1093 pulls::Pulls::new(self.clone())
1094 }
1095
1096 pub fn rate_limit(&self) -> rate_limit::RateLimit {
1098 rate_limit::RateLimit::new(self.clone())
1099 }
1100
1101 pub fn reactions(&self) -> reactions::Reactions {
1103 reactions::Reactions::new(self.clone())
1104 }
1105
1106 pub fn repos(&self) -> repos::Repos {
1108 repos::Repos::new(self.clone())
1109 }
1110
1111 pub fn scim(&self) -> scim::Scim {
1113 scim::Scim::new(self.clone())
1114 }
1115
1116 pub fn search(&self) -> search::Search {
1118 search::Search::new(self.clone())
1119 }
1120
1121 pub fn secret_scanning(&self) -> secret_scanning::SecretScanning {
1123 secret_scanning::SecretScanning::new(self.clone())
1124 }
1125
1126 pub fn teams(&self) -> teams::Teams {
1128 teams::Teams::new(self.clone())
1129 }
1130
1131 pub fn users(&self) -> users::Users {
1133 users::Users::new(self.clone())
1134 }
1135}