azure_functions/blob/
properties.rs

1use chrono::{DateTime, Utc};
2use serde::{de::Error, Deserialize, Deserializer};
3
4/// Represents the type of blob.
5#[derive(Debug)]
6pub enum BlobType {
7    /// The blob type is not specified.
8    Unspecified,
9    /// A page blob.
10    PageBlob,
11    /// A block blob.
12    BlockBlob,
13    /// An append blob.
14    AppendBlob,
15}
16
17impl Default for BlobType {
18    fn default() -> Self {
19        BlobType::Unspecified
20    }
21}
22
23/// Represents the duration of the blob's lease.
24#[derive(Debug)]
25pub enum LeaseDuration {
26    /// The lease duration is not specified.
27    Unspecified,
28    /// The lease duration is finite.
29    Fixed,
30    /// The lease duration is infinite.
31    Infinite,
32}
33
34impl Default for LeaseDuration {
35    fn default() -> Self {
36        LeaseDuration::Unspecified
37    }
38}
39
40/// Represents the lease state of the blob.
41#[derive(Debug)]
42pub enum LeaseState {
43    /// Not specified.
44    Unspecified,
45    /// The lease is in the Available state.
46    Available,
47    /// The lease is in the Leased state.
48    Leased,
49    /// The lease is in the Expired state.
50    Expired,
51    /// The lease is in the Breaking state.
52    Breaking,
53    /// The lease is in the Broken state.
54    Broken,
55}
56
57impl Default for LeaseState {
58    fn default() -> Self {
59        LeaseState::Unspecified
60    }
61}
62
63/// Represents the status of the blob's lease.
64#[derive(Debug)]
65pub enum LeaseStatus {
66    /// The lease status is not specified.
67    Unspecified,
68    /// The resource is locked.
69    Locked,
70    /// The resource is unlocked.
71    Unlocked,
72}
73
74impl Default for LeaseStatus {
75    fn default() -> Self {
76        LeaseStatus::Unspecified
77    }
78}
79
80/// Represents the tier of the page blob.
81#[derive(Debug)]
82pub enum PremiumPageBlobTier {
83    /// The tier is not recognized.
84    Unknown,
85    /// P4 tier.
86    P4,
87    /// P6 tier.
88    P6,
89    /// P10 tier.
90    P10,
91    /// P20 tier.
92    P20,
93    /// P30 tier.
94    P30,
95    /// P40 tier.
96    P40,
97    /// P50 tier.
98    P50,
99    /// P60 tier.
100    P60,
101}
102
103impl Default for PremiumPageBlobTier {
104    fn default() -> Self {
105        PremiumPageBlobTier::Unknown
106    }
107}
108
109/// Represents the rehydration status for a blob that is currently archived.
110#[derive(Debug)]
111pub enum RehydrationStatus {
112    /// The rehydration status is unknown.
113    Unknown,
114    /// The blob is being rehydrated to hot storage.
115    PendingToHot,
116    /// The blob is being rehydrated to cool storage.
117    PendingToCool,
118}
119
120impl Default for RehydrationStatus {
121    fn default() -> Self {
122        RehydrationStatus::Unknown
123    }
124}
125
126/// Represents the standard tier of the block blob.
127#[derive(Debug)]
128pub enum StandardBlobTier {
129    /// The tier is not recognized
130    Unknown,
131    /// Hot storage tier.
132    Hot,
133    /// Cool storage tier.
134    Cool,
135    /// Archive storage tier.
136    Archive,
137}
138
139impl Default for StandardBlobTier {
140    fn default() -> Self {
141        StandardBlobTier::Unknown
142    }
143}
144
145/// Represents the properties of an Azure Storage blob.
146#[derive(Default, Debug, serde::Deserialize)]
147#[serde(rename_all = "PascalCase")]
148pub struct Properties {
149    /// The number of committed blocks, if the blob is an append blob.
150    pub append_blob_committed_block_count: Option<i32>,
151    /// The value indicating if the tier of the blob has been inferred.
152    pub blob_tier_inferred: Option<bool>,
153    /// The time for when the tier of the blob was last-modified.
154    pub blob_tier_last_modified_time: Option<DateTime<Utc>>,
155    /// The type of the blob.
156    #[serde(deserialize_with = "deserialize_blob_type")]
157    pub blob_type: BlobType,
158    /// The cache-control value stored for the blob.
159    pub cache_control: Option<String>,
160    /// The content-disposition value stored for the blob.
161    pub content_disposition: Option<String>,
162    /// The content-encoding value stored for the blob.
163    pub content_encoding: Option<String>,
164    /// The content-language value stored for the blob.
165    pub content_language: Option<String>,
166    /// The content-MD5 value stored for the blob.
167    #[serde(rename = "ContentMD5")]
168    pub content_md5: Option<String>,
169    /// The content-type value stored for the blob.
170    pub content_type: Option<String>,
171    /// The creation time for the blob
172    pub created: Option<DateTime<Utc>>,
173    /// The deletion time for the blob, if it was deleted.
174    pub deleted_time: Option<DateTime<Utc>>,
175    /// The blob's ETag value.
176    #[serde(rename = "ETag")]
177    pub etag: Option<String>,
178    /// The value indicating whether or not this blob is an incremental copy.
179    pub is_incremental_copy: bool,
180    /// The blob's server-side encryption state.
181    pub is_server_encrypted: bool,
182    /// The last-modified time for the blob.
183    pub last_modified: Option<DateTime<Utc>>,
184    /// The blob's lease duration.
185    #[serde(deserialize_with = "deserialize_lease_duration")]
186    pub lease_duration: LeaseDuration,
187    /// The blob's lease state.
188    #[serde(deserialize_with = "deserialize_lease_state")]
189    pub lease_state: LeaseState,
190    /// The blob's lease status.
191    #[serde(deserialize_with = "deserialize_lease_status")]
192    pub lease_status: LeaseStatus,
193    /// The size of the blob, in bytes.
194    pub length: i64,
195    /// The blob's current sequence number, if the blob is a page blob.
196    pub page_blob_sequence_number: Option<i64>,
197    /// The value indicating the tier of the premium page blob, if the blob is a page blob.
198    #[serde(deserialize_with = "deserialize_page_blob_tier")]
199    pub premium_page_blob_tier: Option<PremiumPageBlobTier>,
200    #[serde(deserialize_with = "deserialize_rehydration_status")]
201    /// The value indicating that the blob is being rehdrated and the tier of the blob once the rehydration from archive has completed.
202    pub rehydration_status: Option<RehydrationStatus>,
203    /// The number of remaining days before the blob is permenantly deleted, if the blob is soft-deleted.
204    pub remaining_days_before_permanent_delete: Option<i32>,
205    /// The value indicating the tier of the block blob.
206    #[serde(deserialize_with = "deserialize_standard_blob_tier")]
207    pub standard_blob_tier: Option<StandardBlobTier>,
208}
209
210fn deserialize_blob_type<'a, D>(deserializer: D) -> Result<BlobType, D::Error>
211where
212    D: Deserializer<'a>,
213{
214    match u32::deserialize(deserializer)? {
215        0 => Ok(BlobType::Unspecified),
216        1 => Ok(BlobType::PageBlob),
217        2 => Ok(BlobType::BlockBlob),
218        3 => Ok(BlobType::AppendBlob),
219        _ => Err(Error::custom("unexpected blob type")),
220    }
221}
222
223fn deserialize_lease_duration<'a, D>(deserializer: D) -> Result<LeaseDuration, D::Error>
224where
225    D: Deserializer<'a>,
226{
227    match u32::deserialize(deserializer)? {
228        0 => Ok(LeaseDuration::Unspecified),
229        1 => Ok(LeaseDuration::Fixed),
230        2 => Ok(LeaseDuration::Infinite),
231        _ => Err(Error::custom("unexpected lease duration")),
232    }
233}
234
235fn deserialize_lease_state<'a, D>(deserializer: D) -> Result<LeaseState, D::Error>
236where
237    D: Deserializer<'a>,
238{
239    match u32::deserialize(deserializer)? {
240        0 => Ok(LeaseState::Unspecified),
241        1 => Ok(LeaseState::Available),
242        2 => Ok(LeaseState::Leased),
243        3 => Ok(LeaseState::Expired),
244        4 => Ok(LeaseState::Breaking),
245        5 => Ok(LeaseState::Broken),
246        _ => Err(Error::custom("unexpected lease state")),
247    }
248}
249
250fn deserialize_lease_status<'a, D>(deserializer: D) -> Result<LeaseStatus, D::Error>
251where
252    D: Deserializer<'a>,
253{
254    match u32::deserialize(deserializer)? {
255        0 => Ok(LeaseStatus::Unspecified),
256        1 => Ok(LeaseStatus::Locked),
257        2 => Ok(LeaseStatus::Unlocked),
258        _ => Err(Error::custom("unexpected lease status")),
259    }
260}
261
262fn deserialize_page_blob_tier<'a, D>(
263    deserializer: D,
264) -> Result<Option<PremiumPageBlobTier>, D::Error>
265where
266    D: Deserializer<'a>,
267{
268    match Option::<u32>::deserialize(deserializer)? {
269        Some(x) => match x {
270            0 => Ok(Some(PremiumPageBlobTier::Unknown)),
271            1 => Ok(Some(PremiumPageBlobTier::P4)),
272            2 => Ok(Some(PremiumPageBlobTier::P6)),
273            3 => Ok(Some(PremiumPageBlobTier::P10)),
274            4 => Ok(Some(PremiumPageBlobTier::P20)),
275            5 => Ok(Some(PremiumPageBlobTier::P30)),
276            6 => Ok(Some(PremiumPageBlobTier::P40)),
277            7 => Ok(Some(PremiumPageBlobTier::P50)),
278            8 => Ok(Some(PremiumPageBlobTier::P60)),
279            _ => Err(Error::custom("unexpected page blob tier")),
280        },
281        None => Ok(None),
282    }
283}
284
285fn deserialize_rehydration_status<'a, D>(
286    deserializer: D,
287) -> Result<Option<RehydrationStatus>, D::Error>
288where
289    D: Deserializer<'a>,
290{
291    match Option::<u32>::deserialize(deserializer)? {
292        Some(x) => match x {
293            0 => Ok(Some(RehydrationStatus::Unknown)),
294            1 => Ok(Some(RehydrationStatus::PendingToHot)),
295            2 => Ok(Some(RehydrationStatus::PendingToCool)),
296            _ => Err(Error::custom("unexpected rehydration status")),
297        },
298        None => Ok(None),
299    }
300}
301
302fn deserialize_standard_blob_tier<'a, D>(
303    deserializer: D,
304) -> Result<Option<StandardBlobTier>, D::Error>
305where
306    D: Deserializer<'a>,
307{
308    match Option::<u32>::deserialize(deserializer)? {
309        Some(x) => match x {
310            0 => Ok(Some(StandardBlobTier::Unknown)),
311            1 => Ok(Some(StandardBlobTier::Hot)),
312            2 => Ok(Some(StandardBlobTier::Cool)),
313            3 => Ok(Some(StandardBlobTier::Archive)),
314            _ => Err(Error::custom("unexpected blob tier")),
315        },
316        None => Ok(None),
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323    use matches::matches;
324    use serde_json::{from_value, json};
325
326    #[test]
327    fn it_deserializes_from_json() {
328        const CACHE_CONTROL: &'static str = "test-cache-control";
329        const CONTENT_DISPOSITION: &'static str = "test-content-disposition";
330        const CONTENT_ENCODING: &'static str = "test-content-encoding";
331        const CONTENT_LANGUAGE: &'static str = "test-content-language";
332        const CONTENT_LENGTH: u32 = 12345;
333        const CONTENT_MD5: &'static str = "test-md5-hash";
334        const CONTENT_TYPE: &'static str = "test-content-type";
335        const ETAG: &'static str = "test-etag";
336        const IS_SERVER_ENCRYPTED: bool = false;
337        const IS_INCREMENTAL_COPY: bool = true;
338        const BLOB_TIER_INFERRED: bool = true;
339        const APPEND_BLOCK_COUNT: i32 = 101;
340        const PAGE_BLOCK_SEQ_NUM: i64 = 54321;
341
342        let now = Utc::now();
343
344        let value = json!({
345            "CacheControl": CACHE_CONTROL,
346            "ContentDisposition": CONTENT_DISPOSITION,
347            "ContentEncoding": CONTENT_ENCODING,
348            "ContentLanguage": CONTENT_LANGUAGE,
349            "Length": CONTENT_LENGTH,
350            "ContentMD5": CONTENT_MD5,
351            "ContentType": CONTENT_TYPE,
352            "ETag": ETAG,
353            "LastModified": now.to_rfc3339(),
354            "BlobType": 1,
355            "LeaseStatus": 1,
356            "LeaseState": 0,
357            "LeaseDuration": 2,
358            "PageBlobSequenceNumber": PAGE_BLOCK_SEQ_NUM,
359            "AppendBlobCommittedBlockCount": APPEND_BLOCK_COUNT,
360            "IsServerEncrypted": IS_SERVER_ENCRYPTED,
361            "IsIncrementalCopy": IS_INCREMENTAL_COPY,
362            "StandardBlobTier": 1,
363            "RehydrationStatus": 2,
364            "PremiumPageBlobTier": 1,
365            "BlobTierInferred": BLOB_TIER_INFERRED,
366            "BlobTierLastModifiedTime": now.to_rfc3339()
367        });
368
369        let properties: Properties = from_value(value).unwrap();
370
371        assert_eq!(
372            *properties
373                .append_blob_committed_block_count
374                .as_ref()
375                .unwrap(),
376            APPEND_BLOCK_COUNT
377        );
378        assert_eq!(
379            *properties.blob_tier_inferred.as_ref().unwrap(),
380            BLOB_TIER_INFERRED
381        );
382        assert_eq!(
383            properties
384                .blob_tier_last_modified_time
385                .as_ref()
386                .unwrap()
387                .to_rfc3339(),
388            now.to_rfc3339()
389        );
390        assert!(matches!(properties.blob_type, BlobType::PageBlob));
391        assert_eq!(properties.cache_control.as_ref().unwrap(), CACHE_CONTROL);
392        assert_eq!(
393            properties.content_disposition.as_ref().unwrap(),
394            CONTENT_DISPOSITION
395        );
396        assert_eq!(
397            properties.content_encoding.as_ref().unwrap(),
398            CONTENT_ENCODING
399        );
400        assert_eq!(
401            properties.content_language.as_ref().unwrap(),
402            CONTENT_LANGUAGE
403        );
404        assert_eq!(properties.content_md5.as_ref().unwrap(), CONTENT_MD5);
405        assert_eq!(properties.content_type.as_ref().unwrap(), CONTENT_TYPE);
406        assert!(properties.created.is_none());
407        assert!(properties.deleted_time.is_none());
408        assert_eq!(properties.etag.as_ref().unwrap(), ETAG);
409        assert_eq!(properties.is_incremental_copy, IS_INCREMENTAL_COPY);
410        assert_eq!(properties.is_server_encrypted, IS_SERVER_ENCRYPTED);
411        assert_eq!(
412            properties.last_modified.as_ref().unwrap().to_rfc3339(),
413            now.to_rfc3339()
414        );
415        assert!(matches!(properties.lease_duration, LeaseDuration::Infinite));
416        assert!(matches!(properties.lease_state, LeaseState::Unspecified));
417        assert!(matches!(properties.lease_status, LeaseStatus::Locked));
418        assert_eq!(properties.length, CONTENT_LENGTH as i64);
419        assert_eq!(
420            *properties.page_blob_sequence_number.as_ref().unwrap(),
421            PAGE_BLOCK_SEQ_NUM
422        );
423        assert!(matches!(
424            properties.premium_page_blob_tier.as_ref().unwrap(),
425            PremiumPageBlobTier::P4
426        ));
427        assert!(matches!(
428            properties.rehydration_status.as_ref().unwrap(),
429            RehydrationStatus::PendingToCool
430        ));
431        assert!(properties.remaining_days_before_permanent_delete.is_none());
432        assert!(matches!(
433            properties.standard_blob_tier.as_ref().unwrap(),
434            StandardBlobTier::Hot
435        ));
436    }
437}