Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/aws/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ use crate::aws::{
AmazonS3, AwsCredential, AwsCredentialProvider, Checksum, S3ConditionalPut, S3CopyIfNotExists,
STORE,
};
use crate::builder::FromUrlBuilder;
use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
use crate::config::ConfigValue;
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
use crate::{
ClientConfigKey, ClientOptions, ObjectStore, Result, RetryConfig, StaticCredentialProvider,
};
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use itertools::Itertools;
Expand Down Expand Up @@ -1257,6 +1260,30 @@ impl AmazonS3Builder {
}
}

impl FromUrlBuilder for AmazonS3Builder {
type ConfigKey = AmazonS3ConfigKey;

fn builder_new() -> Self {
Self::new()
}

fn builder_with_url(self, url: Url) -> Self {
self.with_url(url)
}

fn builder_with_config(self, key: &str, value: String) -> Self {
if let Ok(key) = key.parse() {
self.with_config(key, value)
} else {
self
}
}

fn builder_build(self) -> Result<Box<dyn ObjectStore>> {
Ok(Box::new(self.build()?) as _)
}
}

/// Extracts the AZ from a S3 Express One Zone bucket name
///
/// <https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-bucket-naming-rules.html>
Expand Down
29 changes: 28 additions & 1 deletion src/azure/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ use crate::azure::credential::{
ImdsManagedIdentityProvider, WorkloadIdentityOAuthProvider,
};
use crate::azure::{AzureCredential, AzureCredentialProvider, MicrosoftAzure, STORE};
use crate::builder::FromUrlBuilder;
use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
use crate::config::ConfigValue;
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
use crate::{
ClientConfigKey, ClientOptions, ObjectStore, Result, RetryConfig, StaticCredentialProvider,
};
use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
Expand Down Expand Up @@ -1058,6 +1061,30 @@ impl MicrosoftAzureBuilder {
}
}

impl FromUrlBuilder for MicrosoftAzureBuilder {
type ConfigKey = AzureConfigKey;

fn builder_new() -> Self {
Self::new()
}

fn builder_with_url(self, url: Url) -> Self {
self.with_url(url)
}

fn builder_with_config(self, key: &str, value: String) -> Self {
if let Ok(key) = key.parse() {
self.with_config(key, value)
} else {
self
}
}

fn builder_build(self) -> Result<Box<dyn ObjectStore>> {
Ok(Box::new(self.build()?) as _)
}
}

/// Parses the contents of the environment variable `env_name` as a URL
/// if present, otherwise falls back to default_url
fn url_from_env(env_name: &str, default_url: &str) -> Result<Url> {
Expand Down
35 changes: 35 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::str::FromStr;

use url::Url;

use crate::ObjectStore;

pub(crate) trait FromUrlBuilder {
type ConfigKey: FromStr;

fn builder_new() -> Self;

fn builder_with_url(self, url: Url) -> Self;

fn builder_with_config(self, key: &str, value: String) -> Self;

fn builder_build(self) -> crate::Result<Box<dyn ObjectStore>>;

fn build_from_url_and_options<K, V>(
url: &Url,
options: impl IntoIterator<Item = (K, V)>,
) -> crate::Result<Box<dyn ObjectStore>>
where
Self: Sized,
K: AsRef<str>,
V: Into<String>,
{
let b = Self::builder_new().builder_with_url(url.clone());

let b = options.into_iter().fold(b, |builder, (key, value)| {
builder.builder_with_config(key.as_ref(), value.into())
});

b.builder_build()
}
}
29 changes: 28 additions & 1 deletion src/gcp/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use crate::builder::FromUrlBuilder;
use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
use crate::config::ConfigValue;
use crate::gcp::client::{GoogleCloudStorageClient, GoogleCloudStorageConfig};
Expand All @@ -26,7 +27,9 @@ use crate::gcp::{
GcpCredential, GcpCredentialProvider, GcpSigningCredential, GcpSigningCredentialProvider,
GoogleCloudStorage, STORE, credential,
};
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
use crate::{
ClientConfigKey, ClientOptions, ObjectStore, Result, RetryConfig, StaticCredentialProvider,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
Expand Down Expand Up @@ -638,6 +641,30 @@ impl GoogleCloudStorageBuilder {
}
}

impl FromUrlBuilder for GoogleCloudStorageBuilder {
type ConfigKey = GoogleConfigKey;

fn builder_new() -> Self {
Self::new()
}

fn builder_with_url(self, url: Url) -> Self {
self.with_url(url)
}

fn builder_with_config(self, key: &str, value: String) -> Self {
if let Ok(key) = key.parse() {
self.with_config(key, value)
} else {
self
}
}

fn builder_build(self) -> Result<Box<dyn ObjectStore>> {
Ok(Box::new(self.build()?) as _)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
25 changes: 25 additions & 0 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use futures_util::{StreamExt, TryStreamExt};
use itertools::Itertools;
use url::Url;

use crate::builder::FromUrlBuilder;
use crate::client::get::GetClientExt;
use crate::client::header::get_etag;
use crate::client::{HttpConnector, http_connector};
Expand Down Expand Up @@ -290,6 +291,30 @@ impl HttpBuilder {
}
}

impl FromUrlBuilder for HttpBuilder {
type ConfigKey = ClientConfigKey;

fn builder_new() -> Self {
Self::new()
}

fn builder_with_url(self, url: Url) -> Self {
self.with_url(url)
}

fn builder_with_config(self, key: &str, value: String) -> Self {
if let Ok(key) = key.parse() {
self.with_config(key, value)
} else {
self
}
}

fn builder_build(self) -> Result<Box<dyn ObjectStore>> {
Ok(Box::new(self.build()?) as _)
}
}

#[cfg(test)]
mod tests {
use crate::integration::*;
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ pub mod signer;
#[cfg(feature = "tokio")]
pub mod throttle;

mod builder;
#[cfg(feature = "cloud")]
pub mod client;

Expand Down
28 changes: 8 additions & 20 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.

use crate::ObjectStore;
use crate::builder::FromUrlBuilder;
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
use crate::local::LocalFileSystem;
use crate::memory::InMemory;
Expand Down Expand Up @@ -139,20 +140,6 @@ impl ObjectStoreScheme {
}
}

#[cfg(feature = "cloud")]
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().to_ascii_lowercase().parse() {
Ok(k) => builder.with_config(k, value),
Err(_) => builder,
},
);
Box::new(builder.build()?) as _
}};
}

/// Create an [`ObjectStore`] based on the provided `url`
///
/// Returns
Expand Down Expand Up @@ -195,28 +182,29 @@ where
{
let _options = options;
let (scheme, path) = ObjectStoreScheme::parse(url)?;
let path = Path::parse(path)?;

let store = match scheme {
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
ObjectStoreScheme::Local => Box::new(LocalFileSystem::new()) as _,
ObjectStoreScheme::Memory => Box::new(InMemory::new()) as _,
#[cfg(feature = "aws")]
ObjectStoreScheme::AmazonS3 => {
builder_opts!(crate::aws::AmazonS3Builder, url, _options)
crate::aws::AmazonS3Builder::build_from_url_and_options(url, _options)?
}
#[cfg(feature = "gcp")]
ObjectStoreScheme::GoogleCloudStorage => {
builder_opts!(crate::gcp::GoogleCloudStorageBuilder, url, _options)
crate::gcp::GoogleCloudStorageBuilder::build_from_url_and_options(url, _options)?
}
#[cfg(feature = "azure")]
ObjectStoreScheme::MicrosoftAzure => {
builder_opts!(crate::azure::MicrosoftAzureBuilder, url, _options)
crate::azure::MicrosoftAzureBuilder::build_from_url_and_options(url, _options)?
}
#[cfg(feature = "http")]
ObjectStoreScheme::Http => {
let url = &url[..url::Position::BeforePath];
builder_opts!(crate::http::HttpBuilder, url, _options)
let url: Url = url[..url::Position::BeforePath]
.parse()
.expect("URL with empty path and no query or fragment must still be a valid URL");
crate::http::HttpBuilder::build_from_url_and_options(&url, _options)?
Comment on lines -218 to +207
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully I'm not crazy and the logic here is valid.

}
#[cfg(not(all(
feature = "fs",
Expand Down
Loading