1pub mod client;
2pub mod config;
3pub mod discussion;
4pub mod error;
5pub mod filters;
6pub mod types;
7
8pub use client::GitHubClient;
9pub use config::{FetchConfig, GitHubConfig, RateLimitConfig};
10pub use discussion::DiscussionClient;
11pub use error::{GitHubFetchError, Result};
12pub use filters::{
13 extract_error_codes, has_code_blocks, has_rust_error_codes, DateRange, IssueFilters, IssueState,
14};
15pub use types::{
16 CollectionResult, Discussion, DiscussionComment, GitHubComment, GitHubIssue, GitHubLabel,
17 GitHubUser, PrFile, PrReview, PrReviewComment, Repository,
18};
19
20pub struct GitHubFetcher {
21 client: GitHubClient,
22 discussion_client: Option<DiscussionClient>,
23}
24
25impl GitHubFetcher {
26 pub fn new(token: Option<String>) -> Result<Self> {
27 let config = if let Some(token) = token {
28 std::env::set_var("GITHUB_TOKEN", token);
29 FetchConfig::default()
30 } else {
31 FetchConfig::default()
32 };
33
34 Self::with_config(config)
35 }
36
37 pub fn with_config(config: FetchConfig) -> Result<Self> {
38 let client = GitHubClient::with_config(config.clone())?;
39 let discussion_client = DiscussionClient::new(config.github).ok();
40
41 Ok(Self {
42 client,
43 discussion_client,
44 })
45 }
46
47 pub async fn fetch_issues(
48 &self,
49 repo: &Repository,
50 filters: &IssueFilters,
51 ) -> Result<Vec<GitHubIssue>> {
52 let result = self.client.fetch_issues(repo, filters, None).await?;
53 Ok(result.issues)
54 }
55
56 pub async fn fetch_issues_with_limit(
57 &self,
58 repo: &Repository,
59 filters: &IssueFilters,
60 max_issues: usize,
61 ) -> Result<CollectionResult> {
62 self.client
63 .fetch_issues(repo, filters, Some(max_issues))
64 .await
65 }
66
67 pub async fn fetch_issue(&self, repo: &Repository, number: u64) -> Result<GitHubIssue> {
68 self.client.fetch_issue(repo, number).await
69 }
70
71 pub async fn fetch_pr(&self, repo: &Repository, number: u64) -> Result<GitHubIssue> {
72 self.client.fetch_pr(repo, number).await
73 }
74
75 pub async fn fetch_comments(
76 &self,
77 repo: &Repository,
78 issue_number: u64,
79 ) -> Result<Vec<GitHubComment>> {
80 self.client.fetch_comments(repo, issue_number).await
81 }
82
83 pub async fn fetch_pr_files(&self, repo: &Repository, pr_number: u64) -> Result<Vec<PrFile>> {
84 self.client.fetch_pr_files(repo, pr_number).await
85 }
86
87 pub async fn fetch_pr_reviews(
89 &self,
90 repo: &Repository,
91 pr_number: u64,
92 ) -> Result<Vec<PrReview>> {
93 self.client.fetch_pr_reviews(repo, pr_number).await
94 }
95
96 pub async fn fetch_pr_review_comments(
98 &self,
99 repo: &Repository,
100 pr_number: u64,
101 ) -> Result<Vec<PrReviewComment>> {
102 self.client.fetch_pr_review_comments(repo, pr_number).await
103 }
104
105 pub async fn fetch_discussion(
106 &self,
107 repo: &Repository,
108 discussion_number: u64,
109 ) -> Result<Discussion> {
110 let discussion_client = self.discussion_client.as_ref().ok_or_else(|| {
111 GitHubFetchError::ConfigError("Discussion client not initialized".to_string())
112 })?;
113
114 discussion_client
115 .fetch_discussion(repo, discussion_number)
116 .await
117 }
118
119 pub async fn fetch_discussion_by_url(&self, discussion_url: &str) -> Result<Discussion> {
120 let discussion_client = self.discussion_client.as_ref().ok_or_else(|| {
121 GitHubFetchError::ConfigError("Discussion client not initialized".to_string())
122 })?;
123
124 discussion_client
125 .fetch_discussion_by_url(discussion_url)
126 .await
127 }
128
129 pub async fn test_connection(&self) -> Result<()> {
130 self.client.test_connection().await
131 }
132
133 pub async fn get_rate_limit(&self) -> Result<String> {
134 self.client.get_rate_limit().await
135 }
136}
137
138pub struct GitHubFetcherBuilder {
139 config: FetchConfig,
140}
141
142impl GitHubFetcherBuilder {
143 pub fn new() -> Self {
144 Self {
145 config: FetchConfig::default(),
146 }
147 }
148
149 pub fn token(self, token: impl Into<String>) -> Self {
150 std::env::set_var(&self.config.github.token_env_var, token.into());
151 self
152 }
153
154 pub fn token_env_var(mut self, var_name: impl Into<String>) -> Self {
155 self.config.github.token_env_var = var_name.into();
156 self
157 }
158
159 pub fn api_base_url(mut self, url: impl Into<String>) -> Self {
160 self.config.github.api_base_url = url.into();
161 self
162 }
163
164 pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
165 self.config.github.user_agent = agent.into();
166 self
167 }
168
169 pub fn timeout(mut self, seconds: u64) -> Self {
170 self.config.github.timeout_seconds = seconds;
171 self
172 }
173
174 pub fn rate_limit(mut self, requests_per_minute: u32) -> Self {
175 self.config.rate_limiting.requests_per_minute = requests_per_minute;
176 self.config.rate_limiting.delay_between_requests_ms =
177 60_000 / requests_per_minute.max(1) as u64;
178 self
179 }
180
181 pub fn max_retries(mut self, retries: u32) -> Self {
182 self.config.rate_limiting.max_retries = retries;
183 self
184 }
185
186 pub fn build(self) -> Result<GitHubFetcher> {
187 GitHubFetcher::with_config(self.config)
188 }
189}
190
191impl Default for GitHubFetcherBuilder {
192 fn default() -> Self {
193 Self::new()
194 }
195}