Skip to main content

git_same/provider/
traits.rs

1//! Provider trait definitions.
2//!
3//! The [`Provider`] trait defines the interface that all Git hosting
4//! provider implementations must implement.
5
6use async_trait::async_trait;
7
8use crate::errors::ProviderError;
9use crate::types::{Org, OwnedRepo, ProviderKind, Repo};
10
11/// Authentication credentials for a provider.
12#[derive(Debug, Clone)]
13pub struct Credentials {
14    /// The authentication token
15    pub token: String,
16    /// Base URL for API calls
17    pub api_base_url: String,
18    /// The authenticated username (if known)
19    pub username: Option<String>,
20}
21
22impl Credentials {
23    /// Creates new credentials with token and API URL.
24    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    /// Sets the username.
33    pub fn with_username(mut self, username: impl Into<String>) -> Self {
34        self.username = Some(username.into());
35        self
36    }
37}
38
39/// Rate limit information from the provider.
40#[derive(Debug, Clone, Default)]
41pub struct RateLimitInfo {
42    /// Maximum requests allowed per period
43    pub limit: u32,
44    /// Remaining requests in current period
45    pub remaining: u32,
46    /// Unix timestamp when the limit resets
47    pub reset_at: Option<i64>,
48}
49
50impl RateLimitInfo {
51    /// Returns true if the rate limit is exhausted.
52    pub fn is_exhausted(&self) -> bool {
53        self.remaining == 0
54    }
55
56    /// Returns the number of seconds until the rate limit resets.
57    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/// Options for repository discovery.
66#[derive(Debug, Clone, Default)]
67pub struct DiscoveryOptions {
68    /// Include archived repositories
69    pub include_archived: bool,
70    /// Include forked repositories
71    pub include_forks: bool,
72    /// Filter to specific organizations (empty = all)
73    pub org_filter: Vec<String>,
74    /// Exclude specific repos by full name
75    pub exclude_repos: Vec<String>,
76}
77
78impl DiscoveryOptions {
79    /// Creates default discovery options.
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    /// Include archived repositories.
85    pub fn with_archived(mut self, include: bool) -> Self {
86        self.include_archived = include;
87        self
88    }
89
90    /// Include forked repositories.
91    pub fn with_forks(mut self, include: bool) -> Self {
92        self.include_forks = include;
93        self
94    }
95
96    /// Filter to specific organizations.
97    pub fn with_orgs(mut self, orgs: Vec<String>) -> Self {
98        self.org_filter = orgs;
99        self
100    }
101
102    /// Exclude specific repositories.
103    pub fn with_exclusions(mut self, repos: Vec<String>) -> Self {
104        self.exclude_repos = repos;
105        self
106    }
107
108    /// Check if a repo should be included based on filters.
109    pub fn should_include(&self, repo: &Repo) -> bool {
110        // Check archived filter
111        if !self.include_archived && repo.archived {
112            return false;
113        }
114
115        // Check fork filter
116        if !self.include_forks && repo.fork {
117            return false;
118        }
119
120        // Check exclusion list
121        if self.exclude_repos.contains(&repo.full_name) {
122            return false;
123        }
124
125        true
126    }
127
128    /// Check if an org should be included based on filters.
129    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
137/// Callback trait for progress reporting during discovery.
138pub trait DiscoveryProgress: Send + Sync {
139    /// Called when organizations are discovered.
140    fn on_orgs_discovered(&self, count: usize);
141
142    /// Called when starting to fetch repos for an org.
143    fn on_org_started(&self, org_name: &str);
144
145    /// Called when finished fetching repos for an org.
146    fn on_org_complete(&self, org_name: &str, repo_count: usize);
147
148    /// Called when starting to fetch personal repos.
149    fn on_personal_repos_started(&self);
150
151    /// Called when finished fetching personal repos.
152    fn on_personal_repos_complete(&self, count: usize);
153
154    /// Called on any error during discovery (non-fatal).
155    fn on_error(&self, message: &str);
156}
157
158/// A no-op implementation for when progress isn't needed.
159#[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/// The core trait that all providers must implement.
172///
173/// This trait defines the interface for interacting with Git hosting providers
174/// like GitHub, GitLab, and Bitbucket.
175#[async_trait]
176pub trait Provider: Send + Sync {
177    /// Returns the provider kind (GitHub, GitLab, etc.).
178    fn kind(&self) -> ProviderKind;
179
180    /// Returns the display name for this provider instance.
181    fn display_name(&self) -> &str;
182
183    /// Validates that the credentials are valid.
184    async fn validate_credentials(&self) -> Result<(), ProviderError>;
185
186    /// Gets the authenticated user's username.
187    async fn get_username(&self) -> Result<String, ProviderError>;
188
189    /// Fetches all organizations the user belongs to.
190    async fn get_organizations(&self) -> Result<Vec<Org>, ProviderError>;
191
192    /// Fetches all repositories for a specific organization.
193    async fn get_org_repos(&self, org: &str) -> Result<Vec<Repo>, ProviderError>;
194
195    /// Fetches the user's personal repositories (not org repos).
196    async fn get_user_repos(&self) -> Result<Vec<Repo>, ProviderError>;
197
198    /// Returns current rate limit information.
199    async fn get_rate_limit(&self) -> Result<RateLimitInfo, ProviderError>;
200
201    /// High-level discovery that returns all repos with filtering.
202    async fn discover_repos(
203        &self,
204        options: &DiscoveryOptions,
205        progress: &dyn DiscoveryProgress,
206    ) -> Result<Vec<OwnedRepo>, ProviderError>;
207
208    /// Returns the clone URL for a repo (SSH or HTTPS based on preference).
209    fn get_clone_url(&self, repo: &Repo, prefer_ssh: bool) -> String;
210}
211
212#[cfg(test)]
213#[path = "traits_tests.rs"]
214mod tests;