Skip to content

Commit

Permalink
object_store/azure: Migrate from snafu to thiserror
Browse files Browse the repository at this point in the history
  • Loading branch information
Turbo87 committed Oct 3, 2024
1 parent 1d88357 commit 23bed3c
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 79 deletions.
65 changes: 39 additions & 26 deletions object_store/src/azure/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use crate::config::ConfigValue;
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use snafu::{OptionExt, ResultExt, Snafu};
use std::str::FromStr;
use std::sync::Arc;
use url::Url;
Expand All @@ -45,48 +44,48 @@ const EMULATOR_ACCOUNT_KEY: &str =
const MSI_ENDPOINT_ENV_KEY: &str = "IDENTITY_ENDPOINT";

/// A specialized `Error` for Azure builder-related errors
#[derive(Debug, Snafu)]
#[derive(Debug, thiserror::Error)]
enum Error {
#[snafu(display("Unable parse source url. Url: {}, Error: {}", url, source))]
#[error("Unable parse source url. Url: {}, Error: {}", url, source)]
UnableToParseUrl {
source: url::ParseError,
url: String,
},

#[snafu(display(
#[error(
"Unable parse emulator url {}={}, Error: {}",
env_name,
env_value,
source
))]
)]
UnableToParseEmulatorUrl {
env_name: String,
env_value: String,
source: url::ParseError,
},

#[snafu(display("Account must be specified"))]
#[error("Account must be specified")]
MissingAccount {},

#[snafu(display("Container name must be specified"))]
#[error("Container name must be specified")]
MissingContainerName {},

#[snafu(display(
#[error(
"Unknown url scheme cannot be parsed into storage location: {}",
scheme
))]
)]
UnknownUrlScheme { scheme: String },

#[snafu(display("URL did not match any known pattern for scheme: {}", url))]
#[error("URL did not match any known pattern for scheme: {}", url)]
UrlNotRecognised { url: String },

#[snafu(display("Failed parsing an SAS key"))]
#[error("Failed parsing an SAS key")]
DecodeSasKey { source: std::str::Utf8Error },

#[snafu(display("Missing component in SAS query pair"))]
#[error("Missing component in SAS query pair")]
MissingSasComponent {},

#[snafu(display("Configuration key: '{}' is not known.", key))]
#[error("Configuration key: '{}' is not known.", key)]
UnknownConfigurationKey { key: String },
}

Expand Down Expand Up @@ -628,11 +627,17 @@ impl MicrosoftAzureBuilder {
/// This is a separate member function to allow fallible computation to
/// be deferred until [`Self::build`] which in turn allows deriving [`Clone`]
fn parse_url(&mut self, url: &str) -> Result<()> {
let parsed = Url::parse(url).context(UnableToParseUrlSnafu { url })?;
let host = parsed.host_str().context(UrlNotRecognisedSnafu { url })?;
let parsed = Url::parse(url).map_err(|source| {
let url = url.into();
Error::UnableToParseUrl { url, source }
})?;

let host = parsed
.host_str()
.ok_or_else(|| Error::UrlNotRecognised { url: url.into() })?;

let validate = |s: &str| match s.contains('.') {
true => Err(UrlNotRecognisedSnafu { url }.build()),
true => Err(Error::UrlNotRecognised { url: url.into() }),
false => Ok(s.to_string()),
};

Expand All @@ -651,7 +656,7 @@ impl MicrosoftAzureBuilder {
self.account_name = Some(validate(a)?);
self.use_fabric_endpoint = true.into();
} else {
return Err(UrlNotRecognisedSnafu { url }.build().into());
return Err(Error::UrlNotRecognised { url: url.into() }.into());
}
}
"https" => match host.split_once('.') {
Expand All @@ -675,9 +680,12 @@ impl MicrosoftAzureBuilder {
}
self.use_fabric_endpoint = true.into();
}
_ => return Err(UrlNotRecognisedSnafu { url }.build().into()),
_ => return Err(Error::UrlNotRecognised { url: url.into() }.into()),
},
scheme => return Err(UnknownUrlSchemeSnafu { scheme }.build().into()),
scheme => {
let scheme = scheme.into();
return Err(Error::UnknownUrlScheme { scheme }.into());
}
}
Ok(())
}
Expand Down Expand Up @@ -910,8 +918,10 @@ impl MicrosoftAzureBuilder {
},
};

let url =
Url::parse(&account_url).context(UnableToParseUrlSnafu { url: account_url })?;
let url = Url::parse(&account_url).map_err(|source| {
let url = account_url.clone();
Error::UnableToParseUrl { url, source }
})?;

let credential = if let Some(credential) = self.credentials {
credential
Expand Down Expand Up @@ -1016,10 +1026,13 @@ impl MicrosoftAzureBuilder {
/// if present, otherwise falls back to default_url
fn url_from_env(env_name: &str, default_url: &str) -> Result<Url> {
let url = match std::env::var(env_name) {
Ok(env_value) => Url::parse(&env_value).context(UnableToParseEmulatorUrlSnafu {
env_name,
env_value,
})?,
Ok(env_value) => {
Url::parse(&env_value).map_err(|source| Error::UnableToParseEmulatorUrl {
env_name: env_name.into(),
env_value,
source,
})?
}
Err(_) => Url::parse(default_url).expect("Failed to parse default URL"),
};
Ok(url)
Expand All @@ -1028,7 +1041,7 @@ fn url_from_env(env_name: &str, default_url: &str) -> Result<Url> {
fn split_sas(sas: &str) -> Result<Vec<(String, String)>, Error> {
let sas = percent_decode_str(sas)
.decode_utf8()
.context(DecodeSasKeySnafu {})?;
.map_err(|source| Error::DecodeSasKey { source })?;
let kv_str_pairs = sas
.trim_start_matches('?')
.split('&')
Expand Down
75 changes: 41 additions & 34 deletions object_store/src/azure/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ use reqwest::{
Client as ReqwestClient, Method, RequestBuilder, Response,
};
use serde::{Deserialize, Serialize};
use snafu::{OptionExt, ResultExt, Snafu};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
Expand All @@ -59,56 +58,56 @@ static MS_CONTENT_LANGUAGE: HeaderName = HeaderName::from_static("x-ms-blob-cont
static TAGS_HEADER: HeaderName = HeaderName::from_static("x-ms-tags");

/// A specialized `Error` for object store-related errors
#[derive(Debug, Snafu)]
#[derive(Debug, thiserror::Error)]
pub(crate) enum Error {
#[snafu(display("Error performing get request {}: {}", path, source))]
#[error("Error performing get request {}: {}", path, source)]
GetRequest {
source: crate::client::retry::Error,
path: String,
},

#[snafu(display("Error performing put request {}: {}", path, source))]
#[error("Error performing put request {}: {}", path, source)]
PutRequest {
source: crate::client::retry::Error,
path: String,
},

#[snafu(display("Error performing delete request {}: {}", path, source))]
#[error("Error performing delete request {}: {}", path, source)]
DeleteRequest {
source: crate::client::retry::Error,
path: String,
},

#[snafu(display("Error performing list request: {}", source))]
#[error("Error performing list request: {}", source)]
ListRequest { source: crate::client::retry::Error },

#[snafu(display("Error getting list response body: {}", source))]
#[error("Error getting list response body: {}", source)]
ListResponseBody { source: reqwest::Error },

#[snafu(display("Got invalid list response: {}", source))]
#[error("Got invalid list response: {}", source)]
InvalidListResponse { source: quick_xml::de::DeError },

#[snafu(display("Unable to extract metadata from headers: {}", source))]
#[error("Unable to extract metadata from headers: {}", source)]
Metadata {
source: crate::client::header::Error,
},

#[snafu(display("ETag required for conditional update"))]
#[error("ETag required for conditional update")]
MissingETag,

#[snafu(display("Error requesting user delegation key: {}", source))]
#[error("Error requesting user delegation key: {}", source)]
DelegationKeyRequest { source: crate::client::retry::Error },

#[snafu(display("Error getting user delegation key response body: {}", source))]
#[error("Error getting user delegation key response body: {}", source)]
DelegationKeyResponseBody { source: reqwest::Error },

#[snafu(display("Got invalid user delegation key response: {}", source))]
#[error("Got invalid user delegation key response: {}", source)]
DelegationKeyResponse { source: quick_xml::de::DeError },

#[snafu(display("Generating SAS keys with SAS tokens auth is not supported"))]
#[error("Generating SAS keys with SAS tokens auth is not supported")]
SASforSASNotSupported,

#[snafu(display("Generating SAS keys while skipping signatures is not supported"))]
#[error("Generating SAS keys while skipping signatures is not supported")]
SASwithSkipSignature,
}

Expand Down Expand Up @@ -239,8 +238,9 @@ impl<'a> PutRequest<'a> {
.payload(Some(self.payload))
.send()
.await
.context(PutRequestSnafu {
path: self.path.as_ref(),
.map_err(|source| {
let path = self.path.as_ref().into();
Error::PutRequest { path, source }
})?;

Ok(response)
Expand Down Expand Up @@ -298,13 +298,14 @@ impl AzureClient {
PutMode::Overwrite => builder.idempotent(true),
PutMode::Create => builder.header(&IF_NONE_MATCH, "*"),
PutMode::Update(v) => {
let etag = v.e_tag.as_ref().context(MissingETagSnafu)?;
let etag = v.e_tag.as_ref().ok_or(Error::MissingETag)?;
builder.header(&IF_MATCH, etag)
}
};

let response = builder.header(&BLOB_TYPE, "BlockBlob").send().await?;
Ok(get_put_result(response.headers(), VERSION_HEADER).context(MetadataSnafu)?)
Ok(get_put_result(response.headers(), VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?)
}

/// PUT a block <https://learn.microsoft.com/en-us/rest/api/storageservices/put-block>
Expand Down Expand Up @@ -348,7 +349,8 @@ impl AzureClient {
.send()
.await?;

Ok(get_put_result(response.headers(), VERSION_HEADER).context(MetadataSnafu)?)
Ok(get_put_result(response.headers(), VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?)
}

/// Make an Azure Delete request <https://docs.microsoft.com/en-us/rest/api/storageservices/delete-blob>
Expand All @@ -373,8 +375,9 @@ impl AzureClient {
.sensitive(sensitive)
.send()
.await
.context(DeleteRequestSnafu {
path: path.as_ref(),
.map_err(|source| {
let path = path.as_ref().into();
Error::DeleteRequest { source, path }
})?;

Ok(())
Expand Down Expand Up @@ -453,13 +456,13 @@ impl AzureClient {
.idempotent(true)
.send()
.await
.context(DelegationKeyRequestSnafu)?
.map_err(|source| Error::DelegationKeyRequest { source })?
.bytes()
.await
.context(DelegationKeyResponseBodySnafu)?;
.map_err(|source| Error::DelegationKeyResponseBody { source })?;

let response: UserDelegationKey =
quick_xml::de::from_reader(response.reader()).context(DelegationKeyResponseSnafu)?;
let response: UserDelegationKey = quick_xml::de::from_reader(response.reader())
.map_err(|source| Error::DelegationKeyResponse { source })?;

Ok(response)
}
Expand Down Expand Up @@ -515,9 +518,11 @@ impl AzureClient {
.sensitive(sensitive)
.send()
.await
.context(GetRequestSnafu {
path: path.as_ref(),
.map_err(|source| {
let path = path.as_ref().into();
Error::GetRequest { source, path }
})?;

Ok(response)
}
}
Expand Down Expand Up @@ -573,8 +578,9 @@ impl GetClient for AzureClient {
.sensitive(sensitive)
.send()
.await
.context(GetRequestSnafu {
path: path.as_ref(),
.map_err(|source| {
let path = path.as_ref().into();
Error::GetRequest { source, path }
})?;

match response.headers().get("x-ms-resource-type") {
Expand Down Expand Up @@ -635,13 +641,14 @@ impl ListClient for AzureClient {
.sensitive(sensitive)
.send()
.await
.context(ListRequestSnafu)?
.map_err(|source| Error::ListRequest { source })?
.bytes()
.await
.context(ListResponseBodySnafu)?;
.map_err(|source| Error::ListResponseBody { source })?;

let mut response: ListResultInternal = quick_xml::de::from_reader(response.reader())
.map_err(|source| Error::InvalidListResponse { source })?;

let mut response: ListResultInternal =
quick_xml::de::from_reader(response.reader()).context(InvalidListResponseSnafu)?;
let token = response.next_marker.take();

Ok((to_list_result(response, prefix)?, token))
Expand Down
Loading

0 comments on commit 23bed3c

Please sign in to comment.