pub const RENDER_BUDGET: usize = 500;
pub mod id;
pub use id::{GitRef, OwnerId, RepoId, RepoPath, Sha};
mod commit;
pub use commit::{CommitDetail, CommitFile, CommitStatistics, FileStatus};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RepoInfo {
pub name: String,
pub description: Option<String>,
pub private: bool,
pub archived: bool,
pub pushed_at: Option<String>,
}
impl RepoInfo {
pub fn bare(name: impl Into<String>) -> Self {
RepoInfo {
name: name.into(),
..Default::default()
}
}
}
impl From<String> for RepoInfo {
fn from(name: String) -> Self {
RepoInfo::bare(name)
}
}
impl From<&str> for RepoInfo {
fn from(name: &str) -> Self {
RepoInfo::bare(name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProviderError {
pub kind: ErrorKind,
pub message: String,
pub retry_after: Option<std::time::Duration>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorKind {
Auth,
RateLimited,
NotFound,
Network,
Timeout,
Provider,
Other,
}
impl ErrorKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Auth => "auth",
Self::RateLimited => "rate_limited",
Self::NotFound => "not_found",
Self::Network => "network",
Self::Timeout => "timeout",
Self::Provider => "provider",
Self::Other => "other",
}
}
}
impl ProviderError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
ProviderError {
kind,
message: message.into(),
retry_after: None,
}
}
pub fn other(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Other, message)
}
pub fn with_retry_after(mut self, retry: std::time::Duration) -> Self {
self.retry_after = Some(retry);
self
}
}
impl std::fmt::Display for ProviderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for ProviderError {}
impl From<String> for ProviderError {
fn from(message: String) -> Self {
Self::other(message)
}
}
impl From<&str> for ProviderError {
fn from(message: &str) -> Self {
Self::other(message)
}
}
pub type ProviderResult<T> = std::result::Result<T, ProviderError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Capabilities {
pub orgs: bool,
pub code_search: bool,
pub file_search: bool,
pub refs: bool,
pub log: bool,
pub blame: bool,
pub commit: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefInfo {
pub name: String,
pub sha: String,
pub is_default: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RepoRefs {
pub branches: Vec<RefInfo>,
pub tags: Vec<RefInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
pub struct LogEntry {
pub sha: String,
pub subject: String,
pub author: String,
pub date: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
pub struct BlameRange {
pub start_line: u32,
pub end_line: u32,
pub sha: String,
pub author: String,
pub date: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SearchItem {
Repo(String),
Org(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TreeNode {
pub path: String,
pub is_dir: bool,
pub sha: String,
pub size: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct TreeResult {
pub entries: Vec<TreeNode>,
pub truncated: bool,
pub branch: String,
}
#[derive(Debug, Clone)]
pub struct CodeMatch {
pub repo: String,
pub path: String,
pub sha: String,
pub branch: String,
pub matches: Vec<String>,
pub located: bool,
pub line: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct SearchCodeResult {
pub hits: Vec<CodeMatch>,
pub truncated: bool,
pub index_as_of: Option<String>,
}
pub trait Provider: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> Capabilities;
fn search(&self, query: &str) -> ProviderResult<Vec<SearchItem>>;
fn org_repos(&self, org: &str) -> ProviderResult<Vec<RepoInfo>>;
fn fetch_tree(&self, repo: &RepoId, ref_: Option<&GitRef>) -> ProviderResult<TreeResult>;
fn fetch_blob(&self, repo: &RepoId, sha: &Sha) -> ProviderResult<Vec<u8>>;
fn source_tarball(&self, repo: &RepoId) -> ProviderResult<Vec<u8>> {
let _ = repo;
Err(ProviderError::other(
"source tarball not supported by this provider",
))
}
fn refs(&self, repo: &RepoId) -> ProviderResult<RepoRefs> {
let _ = repo;
Err(ProviderError::new(
ErrorKind::Provider,
"provider has no revision listing",
))
}
fn log(
&self,
repo: &RepoId,
path: Option<&str>,
ref_: Option<&GitRef>,
limit: Option<usize>,
on_entries: &mut dyn FnMut(Vec<LogEntry>),
) -> ProviderResult<bool> {
let _ = (repo, path, ref_, limit, on_entries);
Err(ProviderError::new(
ErrorKind::Provider,
"provider has no commit log",
))
}
fn blob_at(
&self,
repo: &RepoId,
path: &str,
ref_: Option<&GitRef>,
) -> ProviderResult<(Vec<u8>, Sha)> {
let _ = (repo, path, ref_);
Err(ProviderError::new(
ErrorKind::Provider,
"provider cannot serve blobs at a ref",
))
}
fn blame(
&self,
repo: &RepoId,
path: &str,
ref_: Option<&GitRef>,
) -> ProviderResult<Vec<BlameRange>> {
let _ = (repo, path, ref_);
Err(ProviderError::new(
ErrorKind::Provider,
"provider has no blame",
))
}
fn commit(&self, repo: &RepoId, sha: &Sha) -> ProviderResult<CommitDetail> {
let _ = (repo, sha);
Err(ProviderError::new(
ErrorKind::Provider,
"provider has no commit detail",
))
}
fn search_code(&self, q: &str) -> ProviderResult<SearchCodeResult>;
fn icon(&self) -> Option<String> {
None
}
fn search_code_progressive(
&self,
q: &str,
on_hits: &(dyn Fn(&[CodeMatch]) + Send + Sync),
) -> ProviderResult<SearchCodeResult> {
let result = self.search_code(q)?;
on_hits(&result.hits);
Ok(SearchCodeResult {
hits: Vec::new(),
truncated: result.truncated,
index_as_of: result.index_as_of,
})
}
fn advise_cancel(&self) {}
fn take_notice(&self) -> Option<String> {
None
}
fn cache_usage(&self) -> Option<u64> {
None
}
fn clone_url(&self, repo: &RepoId) -> ProviderResult<String>;
fn web_url(
&self,
repo: &RepoId,
path: &str,
branch: Option<&GitRef>,
line: Option<u32>,
end: Option<u32>,
is_file: bool,
) -> ProviderResult<String>;
fn org_url(&self, org: &str) -> ProviderResult<String>;
}
pub mod paths;