mod cds;
mod file;
mod http;
mod s3;
pub use cds::{
build_cds_url, cds_api_key, cds_auth, parse_cds_url, CdsTransport, CDS_API_URL, CDS_REALM,
};
pub use file::{file_url_to_path, FileTransport};
pub use http::HttpTransport;
pub use s3::{resolve_region, s3_https_url, S3Transport, DEFAULT_S3_REGION};
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use crate::auth::AuthResolver;
use crate::error::Result;
pub(crate) fn env_secs(var: &str, default_secs: u64) -> std::time::Duration {
std::env::var(var)
.ok()
.and_then(|v| v.parse::<u64>().ok())
.map_or(
std::time::Duration::from_secs(default_secs),
std::time::Duration::from_secs,
)
}
#[derive(Debug, Default, Clone)]
pub struct Conditional {
pub etag: Option<String>,
pub last_modified: Option<String>,
}
impl Conditional {
pub fn is_empty(&self) -> bool {
self.etag.is_none() && self.last_modified.is_none()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FetchStatus {
Downloaded,
NotModified,
}
#[derive(Debug, Clone)]
pub struct FetchResult {
pub status: FetchStatus,
pub etag: Option<String>,
pub last_modified: Option<String>,
pub bytes_written: u64,
}
pub trait Transport: Send + Sync {
fn schemes(&self) -> &'static [&'static str];
fn fetch(
&self,
resolved_url: &str,
dest: &Path,
conditional: &Conditional,
auth: Option<&dyn AuthResolver>,
) -> Result<FetchResult>;
fn fetch_hashed(
&self,
resolved_url: &str,
dest: &Path,
conditional: &Conditional,
auth: Option<&dyn AuthResolver>,
) -> Result<(FetchResult, Option<String>)> {
self.fetch(resolved_url, dest, conditional, auth)
.map(|result| (result, None))
}
}
#[derive(Default, Clone)]
pub struct TransportRegistry {
by_scheme: HashMap<String, Arc<dyn Transport>>,
}
impl TransportRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_builtins() -> Self {
let mut r = Self::new();
r.register(Arc::new(HttpTransport::new()));
r.register(Arc::new(FileTransport::new()));
r.register(Arc::new(CdsTransport::new()));
r.register(Arc::new(S3Transport::new()));
r
}
pub fn register(&mut self, transport: Arc<dyn Transport>) -> &mut Self {
for scheme in transport.schemes() {
self.by_scheme
.insert((*scheme).to_string(), transport.clone());
}
self
}
pub fn get(&self, scheme: &str) -> Option<Arc<dyn Transport>> {
self.by_scheme.get(scheme).cloned()
}
pub fn schemes(&self) -> Vec<String> {
self.by_scheme.keys().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtins_cover_http_https_file_cds() {
let r = TransportRegistry::with_builtins();
assert!(r.get("http").is_some());
assert!(r.get("https").is_some());
assert!(r.get("file").is_some());
assert!(r.get("cds").is_some()); assert!(r.get("s3").is_some()); }
}