git_same/provider/
traits.rs1use async_trait::async_trait;
7
8use crate::errors::ProviderError;
9use crate::types::{Org, OwnedRepo, ProviderKind, Repo};
10
11#[derive(Debug, Clone)]
13pub struct Credentials {
14 pub token: String,
16 pub api_base_url: String,
18 pub username: Option<String>,
20}
21
22impl Credentials {
23 pub fn new(token: impl Into<String>, api_base_url: impl Into<String>) -> Self {
25 Self {
26 token: token.into(),
27 api_base_url: api_base_url.into(),
28 username: None,
29 }
30 }
31
32 pub fn with_username(mut self, username: impl Into<String>) -> Self {
34 self.username = Some(username.into());
35 self
36 }
37}
38
39#[derive(Debug, Clone, Default)]
41pub struct RateLimitInfo {
42 pub limit: u32,
44 pub remaining: u32,
46 pub reset_at: Option<i64>,
48}
49
50impl RateLimitInfo {
51 pub fn is_exhausted(&self) -> bool {
53 self.remaining == 0
54 }
55
56 pub fn seconds_until_reset(&self) -> Option<i64> {
58 self.reset_at.map(|reset| {
59 let now = chrono::Utc::now().timestamp();
60 (reset - now).max(0)
61 })
62 }
63}
64
65#[derive(Debug, Clone, Default)]
67pub struct DiscoveryOptions {
68 pub include_archived: bool,
70 pub include_forks: bool,
72 pub org_filter: Vec<String>,
74 pub exclude_repos: Vec<String>,
76}
77
78impl DiscoveryOptions {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn with_archived(mut self, include: bool) -> Self {
86 self.include_archived = include;
87 self
88 }
89
90 pub fn with_forks(mut self, include: bool) -> Self {
92 self.include_forks = include;
93 self
94 }
95
96 pub fn with_orgs(mut self, orgs: Vec<String>) -> Self {
98 self.org_filter = orgs;
99 self
100 }
101
102 pub fn with_exclusions(mut self, repos: Vec<String>) -> Self {
104 self.exclude_repos = repos;
105 self
106 }
107
108 pub fn should_include(&self, repo: &Repo) -> bool {
110 if !self.include_archived && repo.archived {
112 return false;
113 }
114
115 if !self.include_forks && repo.fork {
117 return false;
118 }
119
120 if self.exclude_repos.contains(&repo.full_name) {
122 return false;
123 }
124
125 true
126 }
127
128 pub fn should_include_org(&self, org: &str) -> bool {
130 if self.org_filter.is_empty() {
131 return true;
132 }
133 self.org_filter.iter().any(|o| o == org)
134 }
135}
136
137pub trait DiscoveryProgress: Send + Sync {
139 fn on_orgs_discovered(&self, count: usize);
141
142 fn on_org_started(&self, org_name: &str);
144
145 fn on_org_complete(&self, org_name: &str, repo_count: usize);
147
148 fn on_personal_repos_started(&self);
150
151 fn on_personal_repos_complete(&self, count: usize);
153
154 fn on_error(&self, message: &str);
156}
157
158#[derive(Debug, Default)]
160pub struct NoProgress;
161
162impl DiscoveryProgress for NoProgress {
163 fn on_orgs_discovered(&self, _: usize) {}
164 fn on_org_started(&self, _: &str) {}
165 fn on_org_complete(&self, _: &str, _: usize) {}
166 fn on_personal_repos_started(&self) {}
167 fn on_personal_repos_complete(&self, _: usize) {}
168 fn on_error(&self, _: &str) {}
169}
170
171#[async_trait]
176pub trait Provider: Send + Sync {
177 fn kind(&self) -> ProviderKind;
179
180 fn display_name(&self) -> &str;
182
183 async fn validate_credentials(&self) -> Result<(), ProviderError>;
185
186 async fn get_username(&self) -> Result<String, ProviderError>;
188
189 async fn get_organizations(&self) -> Result<Vec<Org>, ProviderError>;
191
192 async fn get_org_repos(&self, org: &str) -> Result<Vec<Repo>, ProviderError>;
194
195 async fn get_user_repos(&self) -> Result<Vec<Repo>, ProviderError>;
197
198 async fn get_rate_limit(&self) -> Result<RateLimitInfo, ProviderError>;
200
201 async fn discover_repos(
203 &self,
204 options: &DiscoveryOptions,
205 progress: &dyn DiscoveryProgress,
206 ) -> Result<Vec<OwnedRepo>, ProviderError>;
207
208 fn get_clone_url(&self, repo: &Repo, prefer_ssh: bool) -> String;
210}
211
212#[cfg(test)]
213#[path = "traits_tests.rs"]
214mod tests;