azure_storage_blobs/blob/
mod.rs

1mod lease_blob_options;
2pub mod operations;
3mod source_content_md5;
4pub use source_content_md5::*;
5mod blob_block_type;
6mod blob_block_with_size;
7mod block_list;
8mod block_list_type;
9mod block_with_size_list;
10mod page_range_list;
11
12pub use blob_block_type::BlobBlockType;
13pub use blob_block_with_size::BlobBlockWithSize;
14pub use block_list::BlockList;
15pub use block_list_type::BlockListType;
16pub use block_with_size_list::BlockWithSizeList;
17pub use lease_blob_options::{LeaseBlobOptions, LEASE_BLOB_OPTIONS_DEFAULT};
18pub use page_range_list::PageRangeList;
19
20use crate::options::{AccessTier, Snapshot, Tags, SNAPSHOT};
21use azure_core::{
22    content_type, date,
23    headers::{self, Headers},
24    parsing::from_azure_time,
25    Etag, LeaseDuration, LeaseState, LeaseStatus,
26};
27use azure_storage::{ConsistencyCRC64, ConsistencyMD5, CopyId, CopyProgress};
28use serde::{Deserialize, Deserializer};
29use serde_json::Value;
30use std::collections::HashMap;
31use time::OffsetDateTime;
32
33#[cfg(feature = "azurite_workaround")]
34fn get_creation_time(h: &Headers) -> azure_core::Result<Option<OffsetDateTime>> {
35    if let Some(creation_time) = h.get_optional_str(&headers::CREATION_TIME) {
36        // Check that the creation time is valid
37        let creation_time =
38            date::parse_rfc1123(creation_time).unwrap_or_else(|_| OffsetDateTime::now_utc());
39        Ok(Some(creation_time))
40    } else {
41        // Not having a creation time is ok
42        Ok(None)
43    }
44}
45
46create_enum!(
47    BlobType,
48    (BlockBlob, "BlockBlob"),
49    (PageBlob, "PageBlob"),
50    (AppendBlob, "AppendBlob")
51);
52
53create_enum!(
54    CopyStatus,
55    (Pending, "pending"),
56    (Success, "success"),
57    (Aborted, "aborted"),
58    (Failed, "failed")
59);
60
61create_enum!(RehydratePriority, (High, "High"), (Standard, "Standard"));
62
63create_enum!(PageWriteType, (Update, "update"), (Clear, "clear"));
64
65fn deserialize_crc64_optional<'de, D>(deserializer: D) -> Result<Option<ConsistencyCRC64>, D::Error>
66where
67    D: Deserializer<'de>,
68{
69    let s: Option<String> = Option::deserialize(deserializer)?;
70
71    s.filter(|s| !s.is_empty())
72        .map(ConsistencyCRC64::decode)
73        .transpose()
74        .map_err(serde::de::Error::custom)
75}
76
77fn deserialize_md5_optional<'de, D>(deserializer: D) -> Result<Option<ConsistencyMD5>, D::Error>
78where
79    D: Deserializer<'de>,
80{
81    let s: Option<String> = Option::deserialize(deserializer)?;
82
83    s.filter(|s| !s.is_empty())
84        .map(ConsistencyMD5::decode)
85        .transpose()
86        .map_err(serde::de::Error::custom)
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
90#[serde(rename_all = "PascalCase")]
91pub struct Blob {
92    pub name: String,
93    pub snapshot: Option<Snapshot>,
94    pub version_id: Option<String>,
95    pub is_current_version: Option<bool>,
96    pub deleted: Option<bool>,
97    pub properties: BlobProperties,
98    pub metadata: Option<HashMap<String, String>>,
99    pub tags: Option<Tags>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
103#[serde(rename_all = "PascalCase")]
104pub struct BlobProperties {
105    #[cfg(not(feature = "azurite_workaround"))]
106    #[serde(with = "azure_core::date::rfc1123", rename = "Creation-Time")]
107    pub creation_time: OffsetDateTime,
108    #[cfg(feature = "azurite_workaround")]
109    #[serde(
110        default,
111        with = "azure_core::date::rfc1123::option",
112        rename = "Creation-Time"
113    )]
114    pub creation_time: Option<OffsetDateTime>,
115    #[serde(with = "azure_core::date::rfc1123", rename = "Last-Modified")]
116    pub last_modified: OffsetDateTime,
117    #[serde(default, with = "azure_core::date::rfc1123::option")]
118    pub last_access_time: Option<OffsetDateTime>,
119    pub etag: Etag,
120    #[serde(rename = "Content-Length")]
121    pub content_length: u64,
122    #[serde(rename = "Content-Type")]
123    pub content_type: String,
124    #[serde(rename = "Content-Encoding")]
125    pub content_encoding: Option<String>,
126    #[serde(rename = "Content-Language")]
127    pub content_language: Option<String>,
128    #[serde(rename = "Content-Disposition")]
129    pub content_disposition: Option<String>,
130    #[serde(
131        default,
132        deserialize_with = "deserialize_md5_optional",
133        rename = "Content-MD5"
134    )]
135    pub content_md5: Option<ConsistencyMD5>,
136    #[serde(
137        default,
138        deserialize_with = "deserialize_crc64_optional",
139        rename = "Content-CRC64"
140    )]
141    pub content_crc64: Option<ConsistencyCRC64>,
142    #[serde(rename = "Cache-Control")]
143    pub cache_control: Option<String>,
144    #[serde(rename = "x-ms-blob-sequence-number")]
145    pub blob_sequence_number: Option<u64>,
146    pub blob_type: BlobType,
147    pub access_tier: Option<AccessTier>,
148    #[serde(default, with = "azure_core::date::rfc1123::option")]
149    pub access_tier_change_time: Option<OffsetDateTime>,
150    pub lease_status: Option<LeaseStatus>,
151    pub lease_state: Option<LeaseState>,
152    pub lease_duration: Option<LeaseDuration>,
153    pub copy_id: Option<CopyId>,
154    pub copy_status: Option<CopyStatus>,
155    pub copy_source: Option<String>,
156    pub copy_progress: Option<CopyProgress>,
157    #[serde(default, with = "azure_core::date::rfc1123::option")]
158    pub copy_completion_time: Option<OffsetDateTime>,
159    pub copy_status_description: Option<String>,
160    #[serde(default)]
161    pub server_encrypted: bool,
162    pub customer_provided_key_sha256: Option<String>,
163    pub encryption_scope: Option<String>,
164    pub incremental_copy: Option<bool>,
165    pub access_tier_inferred: Option<bool>,
166    #[serde(default, with = "azure_core::date::rfc1123::option")]
167    pub deleted_time: Option<OffsetDateTime>,
168    pub remaining_retention_days: Option<u32>,
169    pub tag_count: Option<u32>,
170    pub rehydrate_priority: Option<RehydratePriority>,
171    #[serde(
172        default,
173        with = "azure_core::date::rfc1123::option",
174        rename = "Expiry-Time"
175    )]
176    pub expiry_time: Option<OffsetDateTime>,
177    pub blob_committed_block_count: Option<u64>,
178    pub resource_type: Option<String>,
179    #[serde(flatten)]
180    extra: HashMap<String, Value>, // For debug purposes, should be compiled out in the future
181}
182
183impl Blob {
184    pub(crate) fn from_headers<BN: Into<String>>(
185        blob_name: BN,
186        h: &Headers,
187    ) -> azure_core::Result<Blob> {
188        #[cfg(not(feature = "azurite_workaround"))]
189        let creation_time = {
190            let creation_time = h.get_str(&headers::CREATION_TIME)?;
191            date::parse_rfc1123(creation_time)?
192        };
193        #[cfg(feature = "azurite_workaround")]
194        let creation_time = get_creation_time(h)?;
195
196        let content_type = h
197            .get_optional_str(&headers::CONTENT_TYPE)
198            .unwrap_or(content_type::APPLICATION_OCTET_STREAM.as_str())
199            .to_string();
200
201        let content_length = h.get_as(&headers::CONTENT_LENGTH)?;
202        let last_modified = from_azure_time(h.get_str(&headers::LAST_MODIFIED)?)?;
203        let etag = h.get_as(&headers::ETAG)?;
204        let blob_sequence_number = h.get_optional_as(&headers::BLOB_SEQUENCE_NUMBER)?;
205        let blob_type = h.get_as(&headers::BLOB_TYPE)?;
206        let access_tier = h.get_optional_as(&headers::BLOB_ACCESS_TIER)?;
207        let content_encoding = h.get_optional_string(&headers::CONTENT_ENCODING);
208        let content_language = h.get_optional_string(&headers::CONTENT_LANGUAGE);
209        let content_md5 = h.get_optional_as(&headers::CONTENT_MD5)?;
210        let content_crc64 = h.get_optional_as(&azure_storage::headers::CONTENT_CRC64)?;
211        let cache_control = h.get_optional_string(&headers::CACHE_CONTROL);
212        let content_disposition = h.get_optional_string(&headers::CONTENT_DISPOSITION);
213        let lease_status = h.get_optional_as(&headers::LEASE_STATUS)?;
214        let lease_state = h.get_optional_as(&headers::LEASE_STATE)?;
215        let lease_duration = h.get_optional_as(&headers::LEASE_DURATION)?;
216        let copy_id = h.get_optional_as(&azure_storage::headers::COPY_ID)?;
217        let copy_status = h.get_optional_as(&headers::COPY_STATUS)?;
218        let copy_source = h.get_optional_string(&headers::COPY_SOURCE);
219        let copy_progress = h.get_optional_as(&headers::COPY_PROGRESS)?;
220        let copy_completion_time: Option<OffsetDateTime> = h
221            .get_optional_str(&headers::COPY_COMPLETION_TIME)
222            .and_then(|cct| date::parse_rfc1123(cct).ok());
223        let copy_status_description = h.get_optional_string(&headers::COPY_STATUS_DESCRIPTION);
224        let server_encrypted = h.get_as(&headers::SERVER_ENCRYPTED)?;
225        let blob_committed_block_count = h.get_optional_as(&headers::BLOB_COMMITTED_BLOCK_COUNT)?;
226
227        let mut metadata = HashMap::new();
228        for (name, value) in h.iter() {
229            let name = name.as_str();
230            if let Some(name) = name.strip_prefix(headers::META_PREFIX.as_str()) {
231                metadata.insert(name.to_string(), value.as_str().to_string());
232            }
233        }
234        let metadata = if metadata.is_empty() {
235            None
236        } else {
237            Some(metadata)
238        };
239
240        let tags = h.get_optional_as(&headers::TAGS)?;
241
242        let snapshot = h.get_optional_as(&SNAPSHOT)?;
243
244        Ok(Blob {
245            name: blob_name.into(),
246            snapshot,
247            deleted: None,            //TODO
248            is_current_version: None, //TODO
249            version_id: None,         //TODO
250            properties: BlobProperties {
251                creation_time,
252                last_modified,
253                last_access_time: None, // TODO
254                etag,
255                content_length,
256                content_type,
257                content_encoding,
258                content_language,
259                content_md5,
260                content_crc64,
261                cache_control,
262                content_disposition,
263                blob_sequence_number,
264                blob_type,
265                access_tier,
266                lease_status,
267                lease_state,
268                lease_duration,
269                copy_id,
270                copy_status,
271                copy_source,
272                copy_progress,
273                copy_completion_time,
274                copy_status_description,
275                incremental_copy: None, // TODO: Not present or documentation bug?
276                server_encrypted,
277                customer_provided_key_sha256: None, // TODO
278                encryption_scope: None,             // TODO
279                access_tier_inferred: None,         // TODO: Not present
280                access_tier_change_time: None,      // TODO: Not present
281                deleted_time: None,                 // TODO
282                remaining_retention_days: None,     // TODO: Not present or documentation bug?
283                tag_count: None,                    // TODO
284                rehydrate_priority: None,           // TODO
285                expiry_time: None,
286                resource_type: None,
287                blob_committed_block_count,
288                extra: HashMap::new(),
289            },
290            metadata,
291            tags,
292        })
293    }
294}
295
296pub(crate) fn copy_status_from_headers(headers: &Headers) -> azure_core::Result<CopyStatus> {
297    headers.get_as(&headers::COPY_STATUS)
298}