pub mod github;
pub mod stdio;
use std::sync::Arc;
#[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)]
pub enum ErrorKind {
Auth,
RateLimited,
NotFound,
Network,
Timeout,
Provider,
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,
}
#[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,
}
#[derive(Debug, Clone)]
pub struct SearchCodeResult {
pub hits: Vec<CodeMatch>,
pub truncated: bool,
}
pub trait Provider: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> Capabilities;
fn default_orgs(&self) -> Vec<String> {
Vec::new()
}
fn search(&self, query: &str) -> ProviderResult<Vec<SearchItem>>;
fn org_repos(&self, org: &str) -> ProviderResult<Vec<String>>;
fn fetch_tree(&self, repo: &str) -> ProviderResult<TreeResult>;
fn fetch_blob(&self, repo: &str, sha: &str) -> ProviderResult<Vec<u8>>;
fn search_code(&self, q: &str) -> ProviderResult<SearchCodeResult>;
fn advise_cancel(&self) {}
fn take_notice(&self) -> Option<String> {
None
}
fn clone_url(&self, repo: &str) -> ProviderResult<String>;
fn web_url(
&self,
repo: &str,
path: &str,
branch: &str,
line: Option<u32>,
is_file: bool,
) -> ProviderResult<String>;
fn org_url(&self, org: &str) -> ProviderResult<String>;
}
pub fn build(config: &crate::config::Config) -> (Arc<dyn Provider>, Option<String>) {
match config.provider.kind.as_str() {
"github" => (
Arc::new(github::GitHubProvider::new(config.cache.max_mb)),
None,
),
"stdio" => match stdio::StdioProvider::spawn_with_stderr(
&config.provider.command,
std::time::Duration::from_millis(config.provider.timeout_ms),
config.provider.stderr == "inherit",
) {
Ok(p) => (Arc::new(p), None),
Err(e) => (
Arc::new(github::GitHubProvider::new(config.cache.max_mb)),
Some(format!("provider stdio failed ({e}); fell back to github")),
),
},
other => (
Arc::new(github::GitHubProvider::new(config.cache.max_mb)),
Some(format!("unknown provider kind {other:?}; using github")),
),
}
}
pub fn offline() -> Arc<dyn Provider> {
struct Offline;
impl Provider for Offline {
fn name(&self) -> &str {
"offline"
}
fn capabilities(&self) -> Capabilities {
Capabilities {
orgs: false,
code_search: false,
}
}
fn search(&self, _: &str) -> ProviderResult<Vec<SearchItem>> {
Err("offline".into())
}
fn org_repos(&self, _: &str) -> ProviderResult<Vec<String>> {
Err("offline".into())
}
fn fetch_tree(&self, _: &str) -> ProviderResult<TreeResult> {
Err("offline".into())
}
fn fetch_blob(&self, _: &str, _: &str) -> ProviderResult<Vec<u8>> {
Err("offline".into())
}
fn search_code(&self, _: &str) -> ProviderResult<SearchCodeResult> {
Err("offline".into())
}
fn clone_url(&self, _: &str) -> ProviderResult<String> {
Err("offline".into())
}
fn web_url(
&self,
_: &str,
_: &str,
_: &str,
_: Option<u32>,
_: bool,
) -> ProviderResult<String> {
Err("offline".into())
}
fn org_url(&self, _: &str) -> ProviderResult<String> {
Err("offline".into())
}
}
Arc::new(Offline)
}