mod default;
mod github_repo;
pub use default::DefaultFetcher;
pub use github_repo::GitHubRepoFetcher;
use crate::client::FetchOptions;
use crate::error::FetchError;
use crate::types::{FetchRequest, FetchResponse};
use async_trait::async_trait;
use url::Url;
#[async_trait]
pub trait Fetcher: Send + Sync {
fn name(&self) -> &'static str;
fn matches(&self, url: &Url) -> bool;
async fn fetch(
&self,
request: &FetchRequest,
options: &FetchOptions,
) -> Result<FetchResponse, FetchError>;
}
pub struct FetcherRegistry {
fetchers: Vec<Box<dyn Fetcher>>,
}
impl Default for FetcherRegistry {
fn default() -> Self {
Self::new()
}
}
impl FetcherRegistry {
pub fn new() -> Self {
Self {
fetchers: Vec::new(),
}
}
pub fn with_defaults() -> Self {
let mut registry = Self::new();
registry.register(Box::new(GitHubRepoFetcher::new()));
registry.register(Box::new(DefaultFetcher::new()));
registry
}
pub fn register(&mut self, fetcher: Box<dyn Fetcher>) {
self.fetchers.push(fetcher);
}
pub async fn fetch(
&self,
request: FetchRequest,
options: FetchOptions,
) -> Result<FetchResponse, FetchError> {
if !request.url.starts_with("http://") && !request.url.starts_with("https://") {
return Err(FetchError::InvalidUrlScheme);
}
let parsed_url = Url::parse(&request.url).map_err(|_| FetchError::InvalidUrlScheme)?;
if !options.allow_prefixes.is_empty() {
let allowed = options
.allow_prefixes
.iter()
.any(|prefix| request.url.starts_with(prefix));
if !allowed {
return Err(FetchError::BlockedUrl);
}
}
if options
.block_prefixes
.iter()
.any(|prefix| request.url.starts_with(prefix))
{
return Err(FetchError::BlockedUrl);
}
for fetcher in &self.fetchers {
if fetcher.matches(&parsed_url) {
tracing::debug!(fetcher = fetcher.name(), url = %request.url, "Using fetcher");
return fetcher.fetch(&request, &options).await;
}
}
Err(FetchError::FetcherError(
"No fetcher available for URL".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_with_defaults() {
let registry = FetcherRegistry::with_defaults();
assert_eq!(registry.fetchers.len(), 2);
assert_eq!(registry.fetchers[0].name(), "github_repo");
assert_eq!(registry.fetchers[1].name(), "default");
}
#[test]
fn test_empty_registry() {
let registry = FetcherRegistry::new();
assert!(registry.fetchers.is_empty());
}
}