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 let creation_time =
38 date::parse_rfc1123(creation_time).unwrap_or_else(|_| OffsetDateTime::now_utc());
39 Ok(Some(creation_time))
40 } else {
41 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>, }
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, is_current_version: None, version_id: None, properties: BlobProperties {
251 creation_time,
252 last_modified,
253 last_access_time: None, 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, server_encrypted,
277 customer_provided_key_sha256: None, encryption_scope: None, access_tier_inferred: None, access_tier_change_time: None, deleted_time: None, remaining_retention_days: None, tag_count: None, rehydrate_priority: None, 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}