Skip to main content

azure_storage_blob/models/
mod.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4//! Model types for Azure Blob Storage.
5
6mod download_result;
7pub(crate) mod drains;
8pub(crate) mod error;
9pub(crate) mod extensions;
10pub(crate) mod http_ranges;
11mod method_options;
12
13pub use http_ranges::HttpRange;
14pub(crate) mod response_ext;
15mod upload_result;
16
17pub use crate::generated::models::*;
18pub use download_result::{
19    BlobClientDownloadIntoResult, BlobClientDownloadResult, BlobDownloadProperties,
20};
21pub use method_options::BlobClientDownloadOptions;
22pub use method_options::BlockBlobClientUploadOptions;
23pub use method_options::BlockBlobClientUploadOptions as BlobClientUploadOptions;
24pub use upload_result::BlockBlobClientUploadResult;
25pub use upload_result::BlockBlobClientUploadResult as BlobClientUploadResult;
26
27use azure_core::fmt::SafeDebug;
28use serde::{Deserialize, Serialize};
29use std::collections::HashMap;
30
31/// The blob metadata.
32#[derive(Clone, Default, SafeDebug)]
33#[non_exhaustive]
34pub struct BlobMetadata {
35    /// The metadata key-value pairs.
36    pub values: Option<HashMap<String, String>>,
37
38    /// Whether the blob metadata is encrypted.
39    pub encrypted: Option<String>,
40}
41
42impl<'de> Deserialize<'de> for BlobMetadata {
43    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
44    where
45        D: serde::Deserializer<'de>,
46    {
47        struct BlobMetadataVisitor;
48        impl<'de> serde::de::Visitor<'de> for BlobMetadataVisitor {
49            type Value = BlobMetadata;
50            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
51                formatter.write_str("a BlobMetadata struct definition")
52            }
53            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
54            where
55                A: serde::de::MapAccess<'de>,
56            {
57                let mut values = HashMap::new();
58                let mut encrypted = None;
59                while let Some(key) = map.next_key::<String>()? {
60                    match key.as_ref() {
61                        "@Encrypted" => encrypted = Some(map.next_value()?),
62                        _ => {
63                            let value: String = map.next_value()?;
64                            values.insert(key, value);
65                        }
66                    }
67                }
68                let values = match values.len() {
69                    0 => None,
70                    _ => Some(values),
71                };
72                Ok(BlobMetadata { values, encrypted })
73            }
74        }
75        deserializer.deserialize_map(BlobMetadataVisitor)
76    }
77}
78
79impl Serialize for BlobMetadata {
80    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: serde::Serializer,
83    {
84        use serde::ser::SerializeMap;
85        let mut map = serializer.serialize_map(Some(
86            1 + match &self.values {
87                Some(values) => values.len(),
88                None => 0,
89            },
90        ))?;
91        if let Some(values) = &self.values {
92            for (k, v) in values {
93                map.serialize_entry(k, v)?;
94            }
95        }
96        if let Some(encrypted) = &self.encrypted {
97            map.serialize_entry("@Encrypted", encrypted)?;
98        }
99        map.end()
100    }
101}
102
103/// Serde deserialization helpers for [`BlobName`] XML elements.
104///
105/// Deserializes a [`BlobName`] XML element directly into a `String`.
106/// If the `Encoded` attribute is `true`, the content will be percent-decoded.
107/// Otherwise, the content is returned as-is.
108///
109/// # Example
110///
111/// ```ignore
112/// #[derive(Deserialize)]
113/// struct MyStruct {
114///     #[serde(deserialize_with = "blob_name::deserialize")]
115///     name: String,
116/// }
117/// ```
118///
119/// For optional fields, use the [`blob_name::option`] module:
120///
121/// ```ignore
122/// #[derive(Deserialize)]
123/// struct MyStruct {
124///     #[serde(deserialize_with = "blob_name::option::deserialize")]
125///     name: Option<String>,
126/// }
127/// ```
128pub(crate) mod blob_name {
129    use super::BlobName;
130    use percent_encoding::percent_decode_str;
131    use serde::{de::Error, Deserialize, Deserializer};
132
133    /// Deserializes a [`BlobName`] XML element into a `String`.
134    ///
135    /// If the `Encoded` attribute is `true`, the content will be percent-decoded.
136    /// Otherwise, the content is returned as-is.
137    ///
138    /// # Errors
139    ///
140    /// Returns a deserialization error if:
141    /// - The `BlobName` element or its content is missing.
142    /// - The content is percent-encoded but contains invalid UTF-8 sequences after decoding.
143    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
144    where
145        D: Deserializer<'de>,
146    {
147        let blob_name = BlobName::deserialize(deserializer)?;
148
149        let content = blob_name
150            .content
151            .ok_or_else(|| D::Error::custom("missing BlobName content"))?;
152
153        if blob_name.encoded.unwrap_or_default() {
154            let decoded = percent_decode_str(&content)
155                .decode_utf8()
156                .map_err(D::Error::custom)?;
157            Ok(decoded.into_owned())
158        } else {
159            Ok(content)
160        }
161    }
162
163    /// Serde deserialization helpers for optional [`BlobName`] XML elements.
164    pub mod option {
165        use super::BlobName;
166        use percent_encoding::percent_decode_str;
167        use serde::{de::Error, Deserialize, Deserializer};
168
169        /// Deserializes a [`BlobName`] XML element into an `Option<String>`.
170        ///
171        /// If the `Encoded` attribute is `true`, the content will be percent-decoded.
172        /// Otherwise, the content is returned as-is.
173        ///
174        /// # Errors
175        ///
176        /// Returns a deserialization error if the content is percent-encoded but contains
177        /// invalid UTF-8 sequences after decoding.
178        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
179        where
180            D: Deserializer<'de>,
181        {
182            let blob_name = Option::<BlobName>::deserialize(deserializer)?;
183
184            let Some(blob_name) = blob_name else {
185                return Ok(None);
186            };
187
188            let Some(content) = blob_name.content else {
189                return Ok(None);
190            };
191
192            if blob_name.encoded.unwrap_or_default() {
193                let decoded = percent_decode_str(&content)
194                    .decode_utf8()
195                    .map_err(D::Error::custom)?;
196                Ok(Some(decoded.into_owned()))
197            } else {
198                Ok(Some(content))
199            }
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use azure_core::xml;
207    use serde::Deserialize;
208
209    #[derive(Deserialize)]
210    struct RequiredName {
211        #[serde(
212            deserialize_with = "crate::models::blob_name::deserialize",
213            rename = "Name"
214        )]
215        name: String,
216    }
217
218    #[derive(Deserialize)]
219    struct OptionalName {
220        #[serde(
221            deserialize_with = "crate::models::blob_name::option::deserialize",
222            rename = "Name",
223            default
224        )]
225        name: Option<String>,
226    }
227
228    #[test]
229    fn deserialize_plain_name() {
230        let input = b"<Root><Name>hello</Name></Root>";
231        let result: RequiredName = xml::from_xml(input).unwrap();
232        assert_eq!(result.name, "hello");
233    }
234
235    #[test]
236    fn deserialize_encoded_name() {
237        let input = b"<Root><Name Encoded=\"true\">hello%20world</Name></Root>";
238        let result: RequiredName = xml::from_xml(input).unwrap();
239        assert_eq!(result.name, "hello world");
240    }
241
242    #[test]
243    fn deserialize_not_encoded_name() {
244        let input = b"<Root><Name Encoded=\"false\">hello%20world</Name></Root>";
245        let result: RequiredName = xml::from_xml(input).unwrap();
246        assert_eq!(result.name, "hello%20world");
247    }
248
249    #[test]
250    fn deserialize_option_some() {
251        let input = b"<Root><Name>hello</Name></Root>";
252        let result: OptionalName = xml::from_xml(input).unwrap();
253        assert_eq!(result.name.as_deref(), Some("hello"));
254    }
255
256    #[test]
257    fn deserialize_option_some_encoded() {
258        let input = b"<Root><Name Encoded=\"true\">hello%20world</Name></Root>";
259        let result: OptionalName = xml::from_xml(input).unwrap();
260        assert_eq!(result.name.as_deref(), Some("hello world"));
261    }
262
263    #[test]
264    fn deserialize_option_none() {
265        let input = b"<Root></Root>";
266        let result: OptionalName = xml::from_xml(input).unwrap();
267        assert_eq!(result.name, None);
268    }
269
270    #[test]
271    fn deserialize_encoded_xml_invalid_fffe() {
272        // U+FFFE → UTF-8: EF BF BE → percent-encoded: %EF%BF%BE
273        let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BEname</Name></Root>";
274        let result: RequiredName = xml::from_xml(input).unwrap();
275        assert_eq!(result.name, "blob\u{FFFE}name");
276    }
277
278    #[test]
279    fn deserialize_encoded_xml_invalid_ffff() {
280        // U+FFFF → UTF-8: EF BF BF → percent-encoded: %EF%BF%BF
281        let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BFname</Name></Root>";
282        let result: RequiredName = xml::from_xml(input).unwrap();
283        assert_eq!(result.name, "blob\u{FFFF}name");
284    }
285
286    #[test]
287    fn deserialize_option_encoded_xml_invalid_fffe() {
288        let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BEname</Name></Root>";
289        let result: OptionalName = xml::from_xml(input).unwrap();
290        assert_eq!(result.name.as_deref(), Some("blob\u{FFFE}name"));
291    }
292
293    #[test]
294    fn deserialize_option_encoded_xml_invalid_ffff() {
295        let input = b"<Root><Name Encoded=\"true\">blob%EF%BF%BFname</Name></Root>";
296        let result: OptionalName = xml::from_xml(input).unwrap();
297        assert_eq!(result.name.as_deref(), Some("blob\u{FFFF}name"));
298    }
299
300    #[test]
301    fn deserialize_encoded_mixed_invalid_and_normal() {
302        // Name with both XML-invalid chars and normal path separators
303        let input = b"<Root><Name Encoded=\"true\">dir%2Fblob%EF%BF%BEname%EF%BF%BF</Name></Root>";
304        let result: RequiredName = xml::from_xml(input).unwrap();
305        assert_eq!(result.name, "dir/blob\u{FFFE}name\u{FFFF}");
306    }
307}