azure_storage_blobs/blob/operations/
get_blob.rs1use crate::prelude::*;
2use azure_core::{
3 error::Error, headers::*, prelude::*, Pageable, RequestId, Response as AzureResponse,
4 ResponseBody,
5};
6use time::OffsetDateTime;
7
8const DEFAULT_CHUNK_SIZE: u64 = 0x1000 * 0x1000;
9
10operation! {
11 #[stream]
12 GetBlob,
13 client: BlobClient,
14 ?range: Range,
15 ?blob_versioning: BlobVersioning,
16 ?lease_id: LeaseId,
17 ?chunk_size: u64,
18 ?encryption_key: CPKInfo,
19 ?if_modified_since: IfModifiedSinceCondition,
20 ?if_match: IfMatchCondition,
21 ?if_tags: IfTags,
22}
23
24impl GetBlobBuilder {
25 pub fn into_stream(self) -> Pageable<GetBlobResponse, Error> {
26 let make_request = move |continuation: Option<Range>| {
27 let this = self.clone();
28 let mut ctx = self.context.clone();
29 async move {
30 let mut url = this.client.url()?;
31
32 let range = match continuation {
33 Some(range) => range,
34 None => initial_range(
35 this.chunk_size.unwrap_or(DEFAULT_CHUNK_SIZE),
36 this.range.clone(),
37 ),
38 };
39
40 this.blob_versioning.append_to_url_query(&mut url);
41
42 let mut headers = Headers::new();
43 for (name, value) in range.as_headers() {
44 headers.insert(name, value);
45 }
46
47 headers.add(this.lease_id);
48 headers.add(this.encryption_key.as_ref());
49 headers.add(this.if_modified_since);
50 headers.add(this.if_match.clone());
51 headers.add(this.if_tags.clone());
52
53 let mut request =
54 BlobClient::finalize_request(url, azure_core::Method::Get, headers, None)?;
55
56 let response = this.client.send(&mut ctx, &mut request).await?;
57
58 GetBlobResponse::try_from(this, response)
59 }
60 };
61 Pageable::new(make_request)
62 }
63}
64
65#[derive(Debug)]
66pub struct GetBlobResponse {
67 pub request_id: RequestId,
68 pub blob: Blob,
69 pub data: ResponseBody,
70 pub date: OffsetDateTime,
71 pub content_range: Option<Range>,
72 pub remaining_range: Option<Range>,
73}
74
75impl GetBlobResponse {
76 fn try_from(request: GetBlobBuilder, response: AzureResponse) -> azure_core::Result<Self> {
77 let headers = response.headers();
78
79 let request_id = request_id_from_headers(headers)?;
80 let date = date_from_headers(headers)?;
81
82 let content_range = headers.get_optional_as(&CONTENT_RANGE)?;
83
84 let remaining_range = remaining_range(
85 request.chunk_size.unwrap_or(DEFAULT_CHUNK_SIZE),
86 request.range,
87 content_range,
88 );
89 let blob = Blob::from_headers(request.client.blob_name(), headers)?;
90 let data = response.into_body();
91
92 Ok(Self {
93 request_id,
94 blob,
95 data,
96 date,
97 content_range: content_range.map(|cr| Range::new(cr.start(), cr.end())),
98 remaining_range,
99 })
100 }
101}
102
103impl Continuable for GetBlobResponse {
104 type Continuation = Range;
105 fn continuation(&self) -> Option<Self::Continuation> {
106 self.remaining_range.clone()
107 }
108}
109
110fn initial_range(chunk_size: u64, request_range: Option<Range>) -> Range {
112 match request_range {
113 Some(Range::Range(x)) => {
114 let len = std::cmp::min(x.end - x.start, chunk_size);
115 (x.start..x.start + len).into()
116 }
117 Some(Range::RangeFrom(x)) => (x.start..x.start + chunk_size).into(),
118 None => Range::new(0, chunk_size),
119 }
120}
121
122fn remaining_range(
135 chunk_size: u64,
136 base_range: Option<Range>,
137 content_range: Option<ContentRange>,
138) -> Option<Range> {
139 let content_range = content_range?;
142
143 if content_range.end() + 1 >= content_range.total_length() {
145 return None;
146 }
147
148 let requested_range = base_range.unwrap_or_else(|| Range::new(0, content_range.total_length()));
150
151 let after = content_range.end() + 1;
155
156 let remaining_size = match requested_range {
157 Range::Range(x) => {
158 if after >= x.end {
159 return None;
160 }
161 x.end - after
162 }
163 Range::RangeFrom(_) => after,
165 };
166
167 let size = std::cmp::min(remaining_size, chunk_size);
168
169 Some(Range::new(after, after + size))
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_initial_range() -> azure_core::Result<()> {
178 let result = initial_range(3, Some(Range::new(0, 10)));
179 let expected = Range::new(0, 3);
180 assert_eq!(result, expected);
181
182 let result = initial_range(3, Some(Range::new(3, 10)));
183 let expected = Range::new(3, 6);
184 assert_eq!(result, expected);
185
186 let result = initial_range(3, None);
187 let expected = Range::new(0, 3);
188 assert_eq!(result, expected);
189 Ok(())
190 }
191 #[test]
192 fn test_remaining_range() -> azure_core::Result<()> {
193 let result = remaining_range(3, None, None);
194 assert!(result.is_none());
195
196 let result = remaining_range(3, Some(Range::new(0, 10)), None);
197 assert!(result.is_none());
198
199 let result = remaining_range(
200 3,
201 Some(Range::new(0, 10)),
202 Some(ContentRange::new(0, 3, 10)),
203 );
204 assert_eq!(result, Some(Range::new(4, 7)));
205
206 let result = remaining_range(
207 3,
208 Some(Range::new(0, 10)),
209 Some(ContentRange::new(0, 10, 10)),
210 );
211 assert!(result.is_none());
212
213 let result = remaining_range(3, None, Some(ContentRange::new(0, 10, 10)));
214 assert!(result.is_none());
215
216 let result = remaining_range(3, None, Some(ContentRange::new(0, 10, 20)));
217 assert_eq!(result, Some(Range::new(11, 14)));
218
219 let result = remaining_range(
220 20,
221 Some(Range::new(5, 15)),
222 Some(ContentRange::new(5, 14, 20)),
223 );
224 assert_eq!(result, None);
225
226 Ok(())
227 }
228}