Skip to main content

azure_storage_blob/models/
extensions.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4use crate::models::{
5    AccessPolicy, AppendBlobClientCreateOptions, BlobTag, BlobTags,
6    BlockBlobClientCommitBlockListOptions, BlockBlobClientUploadBlobFromUrlOptions,
7    BlockBlobClientUploadOptions, PageBlobClientCreateOptions, SignedIdentifier, SignedIdentifiers,
8};
9use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
10use std::collections::HashMap;
11
12/// Converts a `BlobTags` to the `x-ms-tags` header string format (`key=value&key2=value2`).
13///
14/// Keys and values are percent-encoded to handle special characters (`&`, `=`, spaces, etc.).
15/// Returns `None` if there are no valid tag entries.
16pub(crate) fn encode_tags(tags: &BlobTags) -> Option<String> {
17    let result = match &tags.blob_tag_set {
18        Some(tag_set) => tag_set
19            .iter()
20            .filter_map(|tag| match (&tag.key, &tag.value) {
21                (Some(k), Some(v)) => {
22                    let encoded_key = percent_encode(k.as_bytes(), NON_ALPHANUMERIC);
23                    let encoded_value = percent_encode(v.as_bytes(), NON_ALPHANUMERIC);
24                    Some(format!("{}={}", encoded_key, encoded_value))
25                }
26                _ => None,
27            })
28            .collect::<Vec<_>>()
29            .join("&"),
30        None => String::new(),
31    };
32    if result.is_empty() {
33        None
34    } else {
35        Some(result)
36    }
37}
38
39/// Augments the current options bag to only create if the Page blob does not already exist.
40/// # Arguments
41///
42/// * `self` - The options bag to be modified.
43impl PageBlobClientCreateOptions<'_> {
44    pub fn if_not_exists(self) -> Self {
45        Self {
46            if_none_match: Some("*".into()),
47            ..self
48        }
49    }
50}
51
52/// Augments the current options bag to only create if the Append blob does not already exist.
53/// # Arguments
54///
55/// * `self` - The options bag to be modified.
56impl AppendBlobClientCreateOptions<'_> {
57    pub fn if_not_exists(self) -> Self {
58        Self {
59            if_none_match: Some("*".into()),
60            ..self
61        }
62    }
63}
64
65/// Augments the current options bag to only create if the Block blob does not already exist.
66/// # Arguments
67///
68/// * `self` - The options bag to be modified.
69impl BlockBlobClientUploadBlobFromUrlOptions<'_> {
70    pub fn if_not_exists(self) -> Self {
71        Self {
72            if_none_match: Some("*".into()),
73            ..self
74        }
75    }
76}
77
78/// Augments the current options bag to only create if the Block blob does not already exist.
79/// # Arguments
80///
81/// * `self` - The options bag to be modified.
82impl BlockBlobClientUploadOptions<'_> {
83    pub fn if_not_exists(self) -> Self {
84        Self {
85            if_none_match: Some("*".into()),
86            ..self
87        }
88    }
89}
90
91/// Sets blob tags on the options bag, encoded as the `x-ms-tags` header value.
92///
93/// Accepts anything convertible to [`BlobTags`], including `HashMap<String, String>`.
94impl PageBlobClientCreateOptions<'_> {
95    pub fn with_tags(mut self, tags: impl Into<BlobTags>) -> Self {
96        self.blob_tags_string = encode_tags(&tags.into());
97        self
98    }
99}
100
101impl AppendBlobClientCreateOptions<'_> {
102    pub fn with_tags(mut self, tags: impl Into<BlobTags>) -> Self {
103        self.blob_tags_string = encode_tags(&tags.into());
104        self
105    }
106}
107
108impl BlockBlobClientUploadBlobFromUrlOptions<'_> {
109    pub fn with_tags(mut self, tags: impl Into<BlobTags>) -> Self {
110        self.blob_tags_string = encode_tags(&tags.into());
111        self
112    }
113}
114
115impl BlockBlobClientUploadOptions<'_> {
116    pub fn with_tags(mut self, tags: impl Into<BlobTags>) -> Self {
117        self.blob_tags_string = encode_tags(&tags.into());
118        self
119    }
120}
121
122impl BlockBlobClientCommitBlockListOptions<'_> {
123    pub fn with_tags(mut self, tags: impl Into<BlobTags>) -> Self {
124        self.blob_tags_string = encode_tags(&tags.into());
125        self
126    }
127}
128
129/// Converts a `BlobTags` struct into `HashMap<String, String>`.
130impl From<BlobTags> for HashMap<String, String> {
131    fn from(blob_tags: BlobTags) -> Self {
132        let mut map = HashMap::new();
133
134        if let Some(tags) = blob_tags.blob_tag_set {
135            for tag in tags {
136                if let (Some(key), Some(value)) = (tag.key, tag.value) {
137                    map.insert(key, value);
138                }
139            }
140        }
141        map
142    }
143}
144
145/// Converts a `HashMap<String, String>` into a `BlobTags` struct.
146impl From<HashMap<String, String>> for BlobTags {
147    fn from(tags: HashMap<String, String>) -> Self {
148        let blob_tags = tags
149            .into_iter()
150            .map(|(k, v)| BlobTag {
151                key: Some(k),
152                value: Some(v),
153            })
154            .collect();
155        BlobTags {
156            blob_tag_set: Some(blob_tags),
157        }
158    }
159}
160
161/// Converts a `HashMap<String, AccessPolicy>` into a `SignedIdentifiers` struct.
162impl From<HashMap<String, AccessPolicy>> for SignedIdentifiers {
163    fn from(policies: HashMap<String, AccessPolicy>) -> Self {
164        if policies.is_empty() {
165            return SignedIdentifiers { items: None };
166        }
167
168        let signed_identifiers: Vec<SignedIdentifier> = policies
169            .into_iter()
170            .map(|(id, access_policy)| SignedIdentifier {
171                id: Some(id),
172                access_policy: Some(access_policy),
173            })
174            .collect();
175
176        SignedIdentifiers {
177            items: Some(signed_identifiers),
178        }
179    }
180}