azure_storage_blobs/blob/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
mod lease_blob_options;
pub mod operations;
mod source_content_md5;
pub use source_content_md5::*;
mod blob_block_type;
mod blob_block_with_size;
mod block_list;
mod block_list_type;
mod block_with_size_list;
mod page_range_list;

pub use blob_block_type::BlobBlockType;
pub use blob_block_with_size::BlobBlockWithSize;
pub use block_list::BlockList;
pub use block_list_type::BlockListType;
pub use block_with_size_list::BlockWithSizeList;
pub use lease_blob_options::{LeaseBlobOptions, LEASE_BLOB_OPTIONS_DEFAULT};
pub use page_range_list::PageRangeList;

use crate::options::{AccessTier, Snapshot, Tags, SNAPSHOT};
use azure_core::{
    content_type, date,
    headers::{self, Headers},
    parsing::from_azure_time,
    Etag, LeaseDuration, LeaseState, LeaseStatus,
};
use azure_storage::{ConsistencyCRC64, ConsistencyMD5, CopyId, CopyProgress};
use serde::{Deserialize, Deserializer};
use serde_json::Value;
use std::collections::HashMap;
use time::OffsetDateTime;

#[cfg(feature = "azurite_workaround")]
fn get_creation_time(h: &Headers) -> azure_core::Result<Option<OffsetDateTime>> {
    if let Some(creation_time) = h.get_optional_str(&headers::CREATION_TIME) {
        // Check that the creation time is valid
        let creation_time =
            date::parse_rfc1123(creation_time).unwrap_or_else(|_| OffsetDateTime::now_utc());
        Ok(Some(creation_time))
    } else {
        // Not having a creation time is ok
        Ok(None)
    }
}

create_enum!(
    BlobType,
    (BlockBlob, "BlockBlob"),
    (PageBlob, "PageBlob"),
    (AppendBlob, "AppendBlob")
);

create_enum!(
    CopyStatus,
    (Pending, "pending"),
    (Success, "success"),
    (Aborted, "aborted"),
    (Failed, "failed")
);

create_enum!(RehydratePriority, (High, "High"), (Standard, "Standard"));

create_enum!(PageWriteType, (Update, "update"), (Clear, "clear"));

fn deserialize_crc64_optional<'de, D>(deserializer: D) -> Result<Option<ConsistencyCRC64>, D::Error>
where
    D: Deserializer<'de>,
{
    let s: Option<String> = Option::deserialize(deserializer)?;

    s.filter(|s| !s.is_empty())
        .map(ConsistencyCRC64::decode)
        .transpose()
        .map_err(serde::de::Error::custom)
}

fn deserialize_md5_optional<'de, D>(deserializer: D) -> Result<Option<ConsistencyMD5>, D::Error>
where
    D: Deserializer<'de>,
{
    let s: Option<String> = Option::deserialize(deserializer)?;

    s.filter(|s| !s.is_empty())
        .map(ConsistencyMD5::decode)
        .transpose()
        .map_err(serde::de::Error::custom)
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Blob {
    pub name: String,
    pub snapshot: Option<Snapshot>,
    pub version_id: Option<String>,
    pub is_current_version: Option<bool>,
    pub deleted: Option<bool>,
    pub properties: BlobProperties,
    pub metadata: Option<HashMap<String, String>>,
    pub tags: Option<Tags>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BlobProperties {
    #[cfg(not(feature = "azurite_workaround"))]
    #[serde(with = "azure_core::date::rfc1123", rename = "Creation-Time")]
    pub creation_time: OffsetDateTime,
    #[cfg(feature = "azurite_workaround")]
    #[serde(
        default,
        with = "azure_core::date::rfc1123::option",
        rename = "Creation-Time"
    )]
    pub creation_time: Option<OffsetDateTime>,
    #[serde(with = "azure_core::date::rfc1123", rename = "Last-Modified")]
    pub last_modified: OffsetDateTime,
    #[serde(default, with = "azure_core::date::rfc1123::option")]
    pub last_access_time: Option<OffsetDateTime>,
    pub etag: Etag,
    #[serde(rename = "Content-Length")]
    pub content_length: u64,
    #[serde(rename = "Content-Type")]
    pub content_type: String,
    #[serde(rename = "Content-Encoding")]
    pub content_encoding: Option<String>,
    #[serde(rename = "Content-Language")]
    pub content_language: Option<String>,
    #[serde(rename = "Content-Disposition")]
    pub content_disposition: Option<String>,
    #[serde(
        default,
        deserialize_with = "deserialize_md5_optional",
        rename = "Content-MD5"
    )]
    pub content_md5: Option<ConsistencyMD5>,
    #[serde(
        default,
        deserialize_with = "deserialize_crc64_optional",
        rename = "Content-CRC64"
    )]
    pub content_crc64: Option<ConsistencyCRC64>,
    #[serde(rename = "Cache-Control")]
    pub cache_control: Option<String>,
    #[serde(rename = "x-ms-blob-sequence-number")]
    pub blob_sequence_number: Option<u64>,
    pub blob_type: BlobType,
    pub access_tier: Option<AccessTier>,
    #[serde(default, with = "azure_core::date::rfc1123::option")]
    pub access_tier_change_time: Option<OffsetDateTime>,
    pub lease_status: Option<LeaseStatus>,
    pub lease_state: Option<LeaseState>,
    pub lease_duration: Option<LeaseDuration>,
    pub copy_id: Option<CopyId>,
    pub copy_status: Option<CopyStatus>,
    pub copy_source: Option<String>,
    pub copy_progress: Option<CopyProgress>,
    #[serde(default, with = "azure_core::date::rfc1123::option")]
    pub copy_completion_time: Option<OffsetDateTime>,
    pub copy_status_description: Option<String>,
    #[serde(default)]
    pub server_encrypted: bool,
    pub customer_provided_key_sha256: Option<String>,
    pub encryption_scope: Option<String>,
    pub incremental_copy: Option<bool>,
    pub access_tier_inferred: Option<bool>,
    #[serde(default, with = "azure_core::date::rfc1123::option")]
    pub deleted_time: Option<OffsetDateTime>,
    pub remaining_retention_days: Option<u32>,
    pub tag_count: Option<u32>,
    pub rehydrate_priority: Option<RehydratePriority>,
    #[serde(
        default,
        with = "azure_core::date::rfc1123::option",
        rename = "Expiry-Time"
    )]
    pub expiry_time: Option<OffsetDateTime>,
    pub blob_committed_block_count: Option<u64>,
    pub resource_type: Option<String>,
    #[serde(flatten)]
    extra: HashMap<String, Value>, // For debug purposes, should be compiled out in the future
}

impl Blob {
    pub(crate) fn from_headers<BN: Into<String>>(
        blob_name: BN,
        h: &Headers,
    ) -> azure_core::Result<Blob> {
        #[cfg(not(feature = "azurite_workaround"))]
        let creation_time = {
            let creation_time = h.get_str(&headers::CREATION_TIME)?;
            date::parse_rfc1123(creation_time)?
        };
        #[cfg(feature = "azurite_workaround")]
        let creation_time = get_creation_time(h)?;

        let content_type = h
            .get_optional_str(&headers::CONTENT_TYPE)
            .unwrap_or(content_type::APPLICATION_OCTET_STREAM.as_str())
            .to_string();

        let content_length = h.get_as(&headers::CONTENT_LENGTH)?;
        let last_modified = from_azure_time(h.get_str(&headers::LAST_MODIFIED)?)?;
        let etag = h.get_as(&headers::ETAG)?;
        let blob_sequence_number = h.get_optional_as(&headers::BLOB_SEQUENCE_NUMBER)?;
        let blob_type = h.get_as(&headers::BLOB_TYPE)?;
        let access_tier = h.get_optional_as(&headers::BLOB_ACCESS_TIER)?;
        let content_encoding = h.get_optional_string(&headers::CONTENT_ENCODING);
        let content_language = h.get_optional_string(&headers::CONTENT_LANGUAGE);
        let content_md5 = h.get_optional_as(&headers::CONTENT_MD5)?;
        let content_crc64 = h.get_optional_as(&azure_storage::headers::CONTENT_CRC64)?;
        let cache_control = h.get_optional_string(&headers::CACHE_CONTROL);
        let content_disposition = h.get_optional_string(&headers::CONTENT_DISPOSITION);
        let lease_status = h.get_optional_as(&headers::LEASE_STATUS)?;
        let lease_state = h.get_optional_as(&headers::LEASE_STATE)?;
        let lease_duration = h.get_optional_as(&headers::LEASE_DURATION)?;
        let copy_id = h.get_optional_as(&azure_storage::headers::COPY_ID)?;
        let copy_status = h.get_optional_as(&headers::COPY_STATUS)?;
        let copy_source = h.get_optional_string(&headers::COPY_SOURCE);
        let copy_progress = h.get_optional_as(&headers::COPY_PROGRESS)?;
        let copy_completion_time: Option<OffsetDateTime> = h
            .get_optional_str(&headers::COPY_COMPLETION_TIME)
            .and_then(|cct| date::parse_rfc1123(cct).ok());
        let copy_status_description = h.get_optional_string(&headers::COPY_STATUS_DESCRIPTION);
        let server_encrypted = h.get_as(&headers::SERVER_ENCRYPTED)?;
        let blob_committed_block_count = h.get_optional_as(&headers::BLOB_COMMITTED_BLOCK_COUNT)?;

        let mut metadata = HashMap::new();
        for (name, value) in h.iter() {
            let name = name.as_str();
            if let Some(name) = name.strip_prefix(headers::META_PREFIX.as_str()) {
                metadata.insert(name.to_string(), value.as_str().to_string());
            }
        }
        let metadata = if metadata.is_empty() {
            None
        } else {
            Some(metadata)
        };

        let tags = h.get_optional_as(&headers::TAGS)?;

        let snapshot = h.get_optional_as(&SNAPSHOT)?;

        Ok(Blob {
            name: blob_name.into(),
            snapshot,
            deleted: None,            //TODO
            is_current_version: None, //TODO
            version_id: None,         //TODO
            properties: BlobProperties {
                creation_time,
                last_modified,
                last_access_time: None, // TODO
                etag,
                content_length,
                content_type,
                content_encoding,
                content_language,
                content_md5,
                content_crc64,
                cache_control,
                content_disposition,
                blob_sequence_number,
                blob_type,
                access_tier,
                lease_status,
                lease_state,
                lease_duration,
                copy_id,
                copy_status,
                copy_source,
                copy_progress,
                copy_completion_time,
                copy_status_description,
                incremental_copy: None, // TODO: Not present or documentation bug?
                server_encrypted,
                customer_provided_key_sha256: None, // TODO
                encryption_scope: None,             // TODO
                access_tier_inferred: None,         // TODO: Not present
                access_tier_change_time: None,      // TODO: Not present
                deleted_time: None,                 // TODO
                remaining_retention_days: None,     // TODO: Not present or documentation bug?
                tag_count: None,                    // TODO
                rehydrate_priority: None,           // TODO
                expiry_time: None,
                resource_type: None,
                blob_committed_block_count,
                extra: HashMap::new(),
            },
            metadata,
            tags,
        })
    }
}

pub(crate) fn copy_status_from_headers(headers: &Headers) -> azure_core::Result<CopyStatus> {
    headers.get_as(&headers::COPY_STATUS)
}