mod download_result;
pub(crate) mod drains;
pub(crate) mod error;
pub(crate) mod extensions;
pub(crate) mod http_ranges;
mod method_options;
pub use http_ranges::HttpRange;
pub(crate) mod response_ext;
mod upload_result;
pub use crate::generated::models::*;
pub use download_result::{BlobClientDownloadResult, BlobDownloadProperties};
pub use method_options::BlobClientDownloadOptions;
pub use method_options::BlockBlobClientUploadOptions;
pub use method_options::BlockBlobClientUploadOptions as BlobClientUploadOptions;
pub use upload_result::BlockBlobClientUploadResult;
pub use upload_result::BlockBlobClientUploadResult as BlobClientUploadResult;
use azure_core::fmt::SafeDebug;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Default, SafeDebug)]
#[non_exhaustive]
pub struct BlobMetadata {
pub values: Option<HashMap<String, String>>,
pub encrypted: Option<String>,
}
impl<'de> Deserialize<'de> for BlobMetadata {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct BlobMetadataVisitor;
impl<'de> serde::de::Visitor<'de> for BlobMetadataVisitor {
type Value = BlobMetadata;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a BlobMetadata struct definition")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut values = HashMap::new();
let mut encrypted = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_ref() {
"@Encrypted" => encrypted = Some(map.next_value()?),
_ => {
let value: String = map.next_value()?;
values.insert(key, value);
}
}
}
let values = match values.len() {
0 => None,
_ => Some(values),
};
Ok(BlobMetadata { values, encrypted })
}
}
deserializer.deserialize_map(BlobMetadataVisitor)
}
}
impl Serialize for BlobMetadata {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(
1 + match &self.values {
Some(values) => values.len(),
None => 0,
},
))?;
if let Some(values) = &self.values {
for (k, v) in values {
map.serialize_entry(k, v)?;
}
}
if let Some(encrypted) = &self.encrypted {
map.serialize_entry("@Encrypted", encrypted)?;
}
map.end()
}
}
pub(crate) mod blob_name {
use super::BlobName;
use percent_encoding::percent_decode_str;
use serde::{de::Error, Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let blob_name = BlobName::deserialize(deserializer)?;
let content = blob_name
.content
.ok_or_else(|| D::Error::custom("missing BlobName content"))?;
if blob_name.encoded.unwrap_or_default() {
let decoded = percent_decode_str(&content)
.decode_utf8()
.map_err(D::Error::custom)?;
Ok(decoded.into_owned())
} else {
Ok(content)
}
}
pub mod option {
use super::BlobName;
use percent_encoding::percent_decode_str;
use serde::{de::Error, Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let blob_name = Option::<BlobName>::deserialize(deserializer)?;
let Some(blob_name) = blob_name else {
return Ok(None);
};
let Some(content) = blob_name.content else {
return Ok(None);
};
if blob_name.encoded.unwrap_or_default() {
let decoded = percent_decode_str(&content)
.decode_utf8()
.map_err(D::Error::custom)?;
Ok(Some(decoded.into_owned()))
} else {
Ok(Some(content))
}
}
}
}
#[cfg(test)]
mod tests {
use azure_core::xml;
use serde::Deserialize;
#[derive(Deserialize)]
struct RequiredName {
#[serde(
deserialize_with = "crate::models::blob_name::deserialize",
rename = "Name"
)]
name: String,
}
#[derive(Deserialize)]
struct OptionalName {
#[serde(
deserialize_with = "crate::models::blob_name::option::deserialize",
rename = "Name",
default
)]
name: Option<String>,
}
#[test]
fn deserialize_plain_name() {
let input = b"<Root><Name>hello</Name></Root>";
let result: RequiredName = xml::from_xml(input).unwrap();
assert_eq!(result.name, "hello");
}
#[test]
fn deserialize_encoded_name() {
let input = b"<Root><Name Encoded=\"true\">hello%20world</Name></Root>";
let result: RequiredName = xml::from_xml(input).unwrap();
assert_eq!(result.name, "hello world");
}
#[test]
fn deserialize_not_encoded_name() {
let input = b"<Root><Name Encoded=\"false\">hello%20world</Name></Root>";
let result: RequiredName = xml::from_xml(input).unwrap();
assert_eq!(result.name, "hello%20world");
}
#[test]
fn deserialize_option_some() {
let input = b"<Root><Name>hello</Name></Root>";
let result: OptionalName = xml::from_xml(input).unwrap();
assert_eq!(result.name.as_deref(), Some("hello"));
}
#[test]
fn deserialize_option_some_encoded() {
let input = b"<Root><Name Encoded=\"true\">hello%20world</Name></Root>";
let result: OptionalName = xml::from_xml(input).unwrap();
assert_eq!(result.name.as_deref(), Some("hello world"));
}
#[test]
fn deserialize_option_none() {
let input = b"<Root></Root>";
let result: OptionalName = xml::from_xml(input).unwrap();
assert_eq!(result.name, None);
}
#[test]
fn deserialize_encoded_xml_invalid_fffe() {
let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BEname</Name></Root>";
let result: RequiredName = xml::from_xml(input).unwrap();
assert_eq!(result.name, "blob\u{FFFE}name");
}
#[test]
fn deserialize_encoded_xml_invalid_ffff() {
let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BFname</Name></Root>";
let result: RequiredName = xml::from_xml(input).unwrap();
assert_eq!(result.name, "blob\u{FFFF}name");
}
#[test]
fn deserialize_option_encoded_xml_invalid_fffe() {
let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BEname</Name></Root>";
let result: OptionalName = xml::from_xml(input).unwrap();
assert_eq!(result.name.as_deref(), Some("blob\u{FFFE}name"));
}
#[test]
fn deserialize_option_encoded_xml_invalid_ffff() {
let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BFname</Name></Root>";
let result: OptionalName = xml::from_xml(input).unwrap();
assert_eq!(result.name.as_deref(), Some("blob\u{FFFF}name"));
}
#[test]
fn deserialize_encoded_mixed_invalid_and_normal() {
let input = b"<Root><Name Encoded=\"true\">dir%2Fblob%EF%BF%BEname%EF%BF%BF</Name></Root>";
let result: RequiredName = xml::from_xml(input).unwrap();
assert_eq!(result.name, "dir/blob\u{FFFE}name\u{FFFF}");
}
}