use object_store::path::Path;
use object_store::ObjectStore;
use snafu::Snafu;
use url::Url;
#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Unable to convert URL \"{}\" to filesystem path", url))]
InvalidUrl { url: Url },
#[snafu(display("Unable to recognise URL \"{}\"", url))]
Unrecognised { url: Url },
#[snafu(display("Feature {scheme:?} not enabled"))]
NotEnabled { scheme: ObjectStoreScheme },
#[snafu(context(false))]
Path { source: object_store::path::Error },
}
impl From<Error> for object_store::Error {
fn from(e: Error) -> Self {
Self::Generic {
store: "URL",
source: Box::new(e),
}
}
}
#[derive(Debug, Eq, PartialEq)]
enum ObjectStoreScheme {
AmazonS3,
Http,
}
impl ObjectStoreScheme {
fn parse(url: &Url) -> Result<(Self, Path), Error> {
let strip_bucket = || Some(url.path().strip_prefix('/')?.split_once('/')?.1);
let (scheme, path) = match (url.scheme(), url.host_str()) {
("s3" | "s3a", Some(_)) => (Self::AmazonS3, url.path()),
("http", Some(_)) => (Self::Http, url.path()),
("https", Some(host)) => {
if host.ends_with("amazonaws.com") {
match host.starts_with("s3") {
true => (Self::AmazonS3, strip_bucket().unwrap_or_default()),
false => (Self::AmazonS3, url.path()),
}
} else if host.ends_with("r2.cloudflarestorage.com") {
(Self::AmazonS3, strip_bucket().unwrap_or_default())
} else {
(Self::Http, url.path())
}
}
_ => return Err(Error::Unrecognised { url: url.clone() }),
};
Ok((scheme, Path::from_url_path(path)?))
}
}
macro_rules! builder_opts {
($builder:ty, $url:expr, $options:expr) => {{
let builder = $options.into_iter().fold(
<$builder>::new().with_url($url.to_string()),
|builder, (key, value)| match key.as_ref().parse() {
Ok(k) => builder.with_config(k, value),
Err(_) => builder,
},
);
Box::new(builder.build()?) as _
}};
}
pub fn parse_url(url: &Url) -> Result<(Box<dyn ObjectStore>, Path), object_store::Error> {
parse_url_opts(url, std::iter::empty::<(&str, &str)>())
}
pub fn parse_url_opts<I, K, V>(
url: &Url,
options: I,
) -> Result<(Box<dyn ObjectStore>, Path), object_store::Error>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: Into<String>,
{
let _options = options;
let (scheme, path) = ObjectStoreScheme::parse(url)?;
let path = Path::parse(path)?;
let store: Box<dyn ObjectStore> = match scheme {
#[cfg(feature = "aws")]
ObjectStoreScheme::AmazonS3 => {
builder_opts!(crate::aws::builder::AmazonS3Builder, url, _options)
}
#[cfg(feature = "http")]
ObjectStoreScheme::Http => {
let url = &url[..url::Position::BeforePath];
let parsed_url = Url::parse(url).unwrap();
Box::new(crate::http::HttpStore::new(parsed_url))
}
#[cfg(not(all(feature = "aws", feature = "http")))]
s => {
return Err(object_store::Error::Generic {
store: "parse_url",
source: format!("feature for {s:?} not enabled").into(),
})
}
};
Ok((store, path))
}