Skip to main content

ossify/ops/object/base/
get_object.rs

1use std::future::Future;
2
3use bytes::Bytes;
4use http::{HeaderMap, Method, header};
5use serde::Serialize;
6
7use crate::body::NoneBody;
8use crate::error::Result;
9use crate::response::BinaryResponseProcessor;
10use crate::{Client, Ops, Prepared, QueryAuthOptions, Request};
11
12/// GetObject request parameters
13#[derive(Debug, Clone, Default, Serialize)]
14pub struct GetObjectParams {
15    /// Version ID for retrieving a specific version of the object
16    #[serde(rename = "versionId")]
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub version_id: Option<String>,
19    /// Response Cache-Control header
20    #[serde(rename = "response-cache-control")]
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub response_cache_control: Option<String>,
23    /// Response Content-Disposition header
24    #[serde(rename = "response-content-disposition")]
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub response_content_disposition: Option<String>,
27    /// Response Content-Encoding header
28    #[serde(rename = "response-content-encoding")]
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub response_content_encoding: Option<String>,
31    /// Response Content-Language header
32    #[serde(rename = "response-content-language")]
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub response_content_language: Option<String>,
35    /// Response Content-Type header
36    #[serde(rename = "response-content-type")]
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub response_content_type: Option<String>,
39    /// Response Expires header
40    #[serde(rename = "response-expires")]
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub response_expires: Option<String>,
43}
44
45impl GetObjectParams {
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Set the Version ID
51    pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
52        self.version_id = Some(version_id.into());
53        self
54    }
55
56    /// Set the response Cache-Control header
57    pub fn response_cache_control(mut self, cache_control: impl Into<String>) -> Self {
58        self.response_cache_control = Some(cache_control.into());
59        self
60    }
61
62    /// Set the response Content-Disposition header
63    pub fn response_content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
64        self.response_content_disposition = Some(content_disposition.into());
65        self
66    }
67
68    /// Set the response Content-Encoding header
69    pub fn response_content_encoding(mut self, content_encoding: impl Into<String>) -> Self {
70        self.response_content_encoding = Some(content_encoding.into());
71        self
72    }
73
74    /// Set the response Content-Language header
75    pub fn response_content_language(mut self, content_language: impl Into<String>) -> Self {
76        self.response_content_language = Some(content_language.into());
77        self
78    }
79
80    /// Set the response Content-Type header
81    pub fn response_content_type(mut self, content_type: impl Into<String>) -> Self {
82        self.response_content_type = Some(content_type.into());
83        self
84    }
85
86    /// Set the response Expires header
87    pub fn response_expires(mut self, expires: impl Into<String>) -> Self {
88        self.response_expires = Some(expires.into());
89        self
90    }
91}
92
93/// GetObject request options (primarily set through HTTP headers)
94#[derive(Debug, Clone, Default)]
95pub struct GetObjectOptions {
96    /// Specify the file transfer range (e.g., "bytes=0-1023")
97    pub range: Option<String>,
98    /// Return the object if the specified time is earlier than the actual modification time
99    pub if_modified_since: Option<String>,
100    /// Return the object if the specified time is equal to or later than the actual modification time
101    pub if_unmodified_since: Option<String>,
102    /// Return the object if the provided ETag matches the object's ETag
103    pub if_match: Option<String>,
104    /// Return the object if the provided ETag does not match the object's ETag
105    pub if_none_match: Option<String>,
106    /// Accepted encoding format
107    pub accept_encoding: Option<String>,
108}
109
110impl GetObjectOptions {
111    /// Set the Range header for segmented download
112    pub fn range(mut self, range: impl Into<String>) -> Self {
113        self.range = Some(range.into());
114        self
115    }
116
117    /// Set the If-Modified-Since header
118    pub fn if_modified_since(mut self, time: impl Into<String>) -> Self {
119        self.if_modified_since = Some(time.into());
120        self
121    }
122
123    /// Set the If-Unmodified-Since header
124    pub fn if_unmodified_since(mut self, time: impl Into<String>) -> Self {
125        self.if_unmodified_since = Some(time.into());
126        self
127    }
128
129    /// Set the If-Match header
130    pub fn if_match(mut self, etag: impl Into<String>) -> Self {
131        self.if_match = Some(etag.into());
132        self
133    }
134
135    /// Set the If-None-Match header
136    pub fn if_none_match(mut self, etag: impl Into<String>) -> Self {
137        self.if_none_match = Some(etag.into());
138        self
139    }
140
141    /// Set the Accept-Encoding header
142    pub fn accept_encoding(mut self, encoding: impl Into<String>) -> Self {
143        self.accept_encoding = Some(encoding.into());
144        self
145    }
146}
147
148impl GetObjectOptions {
149    fn into_headers(self) -> Result<HeaderMap> {
150        let mut headers = HeaderMap::new();
151
152        // Set Range header
153        if let Some(range) = self.range {
154            headers.insert(header::RANGE, range.parse()?);
155        }
156
157        // Set conditional request headers
158        if let Some(if_modified_since) = self.if_modified_since {
159            headers.insert(header::IF_MODIFIED_SINCE, if_modified_since.parse()?);
160        }
161
162        if let Some(if_unmodified_since) = self.if_unmodified_since {
163            headers.insert(header::IF_UNMODIFIED_SINCE, if_unmodified_since.parse()?);
164        }
165
166        if let Some(if_match) = self.if_match {
167            headers.insert(header::IF_MATCH, if_match.parse()?);
168        }
169
170        if let Some(if_none_match) = self.if_none_match {
171            headers.insert(header::IF_NONE_MATCH, if_none_match.parse()?);
172        }
173
174        // Set Accept-Encoding header
175        if let Some(accept_encoding) = self.accept_encoding {
176            headers.insert(header::ACCEPT_ENCODING, accept_encoding.parse()?);
177        }
178
179        Ok(headers)
180    }
181}
182
183/// GetObject operation
184pub struct GetObject {
185    pub object_key: String,
186    pub params: GetObjectParams,
187    pub options: GetObjectOptions,
188}
189
190impl Ops for GetObject {
191    type Response = BinaryResponseProcessor;
192    type Body = NoneBody;
193    type Query = GetObjectParams;
194
195    fn prepare(self) -> Result<Prepared<GetObjectParams>> {
196        Ok(Prepared {
197            method: Method::GET,
198            key: Some(self.object_key),
199            query: Some(self.params),
200            headers: Some(self.options.into_headers()?),
201            ..Default::default()
202        })
203    }
204}
205
206/// GetObject operations trait
207pub trait GetObjectOperations {
208    /// Get an object (file)
209    ///
210    /// Official documentation: <https://www.alibabacloud.com/help/en/oss/developer-reference/getobject>
211    fn get_object(
212        &self,
213        object_key: impl Into<String>,
214        params: GetObjectParams,
215        options: Option<GetObjectOptions>,
216    ) -> impl Future<Output = Result<Bytes>>;
217
218    fn presign_get_object(
219        &self,
220        object_key: impl Into<String>,
221        public: bool,
222        params: GetObjectParams,
223        options: Option<GetObjectOptions>,
224        query_auth_options: QueryAuthOptions,
225    ) -> impl Future<Output = Result<String>>;
226}
227
228impl GetObjectOperations for Client {
229    async fn get_object(
230        &self,
231        object_key: impl Into<String>,
232        params: GetObjectParams,
233        options: Option<GetObjectOptions>,
234    ) -> Result<Bytes> {
235        let ops = GetObject {
236            object_key: object_key.into(),
237            params,
238            options: options.unwrap_or_default(),
239        };
240
241        self.request(ops).await
242    }
243
244    async fn presign_get_object(
245        &self,
246        object_key: impl Into<String>,
247        public: bool,
248        params: GetObjectParams,
249        options: Option<GetObjectOptions>,
250        query_auth_options: QueryAuthOptions,
251    ) -> Result<String> {
252        let ops = GetObject {
253            object_key: object_key.into(),
254            params,
255            options: options.unwrap_or_default(),
256        };
257        self.presign(ops, public, Some(query_auth_options)).await
258    }
259}
260
261// =============================================================================
262// Convenience builder and helper functions
263// =============================================================================
264
265/// GetObjectRequest builder
266#[derive(Debug, Clone, Default)]
267pub struct GetObjectRequestBuilder {
268    params: GetObjectParams,
269    options: GetObjectOptions,
270}
271
272impl GetObjectRequestBuilder {
273    pub fn new() -> Self {
274        Self::default()
275    }
276
277    /// Set the Version ID
278    pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
279        self.params.version_id = Some(version_id.into());
280        self
281    }
282
283    /// Set Range header (for segmented download)
284    pub fn range(mut self, range: impl Into<String>) -> Self {
285        self.options.range = Some(range.into());
286        self
287    }
288
289    /// Set Range header (by start and end positions)
290    pub fn range_bytes(mut self, start: u64, end: Option<u64>) -> Self {
291        let range = match end {
292            Some(end) => format!("bytes={start}-{end}"),
293            None => format!("bytes={start}-"),
294        };
295        self.options.range = Some(range);
296        self
297    }
298
299    /// Set the If-Modified-Since header
300    pub fn if_modified_since(mut self, time: impl Into<String>) -> Self {
301        self.options.if_modified_since = Some(time.into());
302        self
303    }
304
305    /// Set the If-Unmodified-Since header
306    pub fn if_unmodified_since(mut self, time: impl Into<String>) -> Self {
307        self.options.if_unmodified_since = Some(time.into());
308        self
309    }
310
311    /// Set the If-Match header
312    pub fn if_match(mut self, etag: impl Into<String>) -> Self {
313        self.options.if_match = Some(etag.into());
314        self
315    }
316
317    /// Set the If-None-Match header
318    pub fn if_none_match(mut self, etag: impl Into<String>) -> Self {
319        self.options.if_none_match = Some(etag.into());
320        self
321    }
322
323    /// Set the response Cache-Control header
324    pub fn response_cache_control(mut self, cache_control: impl Into<String>) -> Self {
325        self.params.response_cache_control = Some(cache_control.into());
326        self
327    }
328
329    /// Set the response Content-Disposition header
330    pub fn response_content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
331        self.params.response_content_disposition = Some(content_disposition.into());
332        self
333    }
334
335    /// Set the response Content-Type header
336    pub fn response_content_type(mut self, content_type: impl Into<String>) -> Self {
337        self.params.response_content_type = Some(content_type.into());
338        self
339    }
340
341    /// Build parameters and options
342    pub fn build(self) -> (GetObjectParams, Option<GetObjectOptions>) {
343        let options = if self.options.range.is_some()
344            || self.options.if_modified_since.is_some()
345            || self.options.if_unmodified_since.is_some()
346            || self.options.if_match.is_some()
347            || self.options.if_none_match.is_some()
348            || self.options.accept_encoding.is_some()
349        {
350            Some(self.options)
351        } else {
352            None
353        };
354
355        (self.params, options)
356    }
357}