aliyun_oss_rs/
common.rs

1//! Common data definitions
2//!
3//!
4#[cfg(feature = "async")]
5use bytes::Bytes;
6#[cfg(feature = "async")]
7use http_body_util::BodyExt;
8#[cfg(feature = "async")]
9use hyper::body::Incoming;
10use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
11use serde_derive::{Deserialize, Serialize};
12use std::fmt;
13use std::sync::OnceLock;
14use time::{OffsetDateTime, UtcOffset, format_description};
15
16// -------------------------- Common functions --------------------------
17// Encode query parameter values
18const URL_ENCODE: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'/');
19#[inline]
20pub(crate) fn url_encode(input: &str) -> String {
21    utf8_percent_encode(input, URL_ENCODE).to_string()
22}
23
24// Check if metadata keys are valid
25#[inline]
26pub(crate) fn invalid_metadata_key(input: &str) -> bool {
27    !input
28        .bytes()
29        .all(|b| b.is_ascii_alphanumeric() || b == b'-')
30}
31
32#[cfg(feature = "async")]
33#[inline]
34pub(crate) async fn body_to_bytes(body: Incoming) -> Result<Bytes, hyper::Error> {
35    Ok(body.collect().await?.to_bytes())
36}
37
38static GMT_FORMAT: OnceLock<Vec<format_description::FormatItem<'static>>> = OnceLock::new();
39
40#[inline]
41pub(crate) fn format_gmt(datetime: OffsetDateTime) -> String {
42    let format = GMT_FORMAT.get_or_init(|| {
43        format_description::parse(
44            "[weekday repr:short], [day padding:space] [month repr:short] [year] [hour]:[minute]:[second] GMT",
45        )
46        .expect("valid format")
47    });
48    datetime
49        .to_offset(UtcOffset::UTC)
50        .format(format)
51        .expect("formatting")
52}
53
54/// Query parameters that should be included in the canonicalized resource
55/// when signing requests. These values are derived from the officially
56/// maintained SDKs for other languages to keep parity.
57///
58/// Parameters beginning with `x-oss-` are automatically included and therefore
59/// omitted from this list.
60pub(crate) const EXCLUDED_VALUES: [&str; 85] = [
61    "accessPoint",
62    "accessPointPolicy",
63    "acl",
64    "append",
65    "asyncFetch",
66    "bucketArchiveDirectRead",
67    "bucketInfo",
68    "callback",
69    "callback-var",
70    "cname",
71    "comp",
72    "continuation-token",
73    "cors",
74    "delete",
75    "encryption",
76    "endTime",
77    "group",
78    "httpsConfig",
79    "img",
80    "inventory",
81    "inventoryId",
82    "lifecycle",
83    "link",
84    "live",
85    "location",
86    "logging",
87    "metaQuery",
88    "objectInfo",
89    "objectMeta",
90    "partNumber",
91    "policy",
92    "policyStatus",
93    "position",
94    "processConfiguration",
95    "publicAccessBlock",
96    "qos",
97    "qosInfo",
98    "qosRequester",
99    "redundancyTransition",
100    "referer",
101    "regionList",
102    "replication",
103    "replicationLocation",
104    "replicationProgress",
105    "requestPayment",
106    "requesterQosInfo",
107    "resourceGroup",
108    "resourcePool",
109    "resourcePoolBuckets",
110    "resourcePoolInfo",
111    "response-cache-control",
112    "response-content-disposition",
113    "response-content-encoding",
114    "response-content-language",
115    "response-content-type",
116    "response-expires",
117    "restore",
118    "security-token",
119    "sequential",
120    "startTime",
121    "stat",
122    "status",
123    "style",
124    "styleName",
125    "symlink",
126    "tagging",
127    "transferAcceleration",
128    "udf",
129    "udfApplication",
130    "udfApplicationLog",
131    "udfImage",
132    "udfImageDesc",
133    "udfName",
134    "uploadId",
135    "uploads",
136    "versionId",
137    "versioning",
138    "versions",
139    "vip",
140    "vod",
141    "vpcip",
142    "website",
143    "worm",
144    "wormExtend",
145    "wormId",
146];
147
148// -------------------------- Common data --------------------------
149
150/// Access permissions (ACL)
151#[derive(Debug, Deserialize, Clone)]
152pub enum Acl {
153    /// Only for object ACL; indicates the object's ACL inherits the bucket ACL
154    #[serde(rename = "default")]
155    Default,
156    /// Private; all read and write requests require authorization
157    #[serde(rename = "private")]
158    Private,
159    /// Public read; objects can be read anonymously but not written
160    #[serde(rename = "public-read")]
161    PublicRead,
162    /// Public read/write; objects can be read and written anonymously
163    #[serde(rename = "public-read-write")]
164    PublicReadWrite,
165}
166impl fmt::Display for Acl {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
168        let value = match self {
169            Acl::Default => "default",
170            Acl::Private => "private",
171            Acl::PublicRead => "public-read",
172            Acl::PublicReadWrite => "public-read-write",
173        };
174        write!(f, "{}", value)
175    }
176}
177
178/// Storage class
179#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
180pub enum StorageClass {
181    /// Standard storage
182    Standard,
183    /// Infrequent access
184    IA,
185    /// Archive storage
186    Archive,
187    /// Cold archive storage
188    ColdArchive,
189    /// Deep cold archive storage
190    DeepColdArchive,
191}
192impl fmt::Display for StorageClass {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        match self {
195            StorageClass::Standard => f.write_str("Standard"),
196            StorageClass::IA => f.write_str("IA"),
197            StorageClass::Archive => f.write_str("Archive"),
198            StorageClass::ColdArchive => f.write_str("ColdArchive"),
199            StorageClass::DeepColdArchive => f.write_str("DeepColdArchive"),
200        }
201    }
202}
203
204/// Data redundancy type
205#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
206pub enum DataRedundancyType {
207    /// Local redundancy (LRS) stores data on multiple devices within the same availability zone; up to two devices can fail simultaneously without data loss and access remains normal.
208    LRS,
209    /// Zonal redundancy (ZRS) stores data redundantly across multiple availability zones in the same region, ensuring access even if one zone becomes unavailable.
210    ZRS,
211}
212impl fmt::Display for DataRedundancyType {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        match self {
215            DataRedundancyType::LRS => f.write_str("LRS"),
216            DataRedundancyType::ZRS => f.write_str("ZRS"),
217        }
218    }
219}
220
221/// Restore priority
222#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
223pub enum RestoreTier {
224    /// Expedited
225    Expedited,
226    /// Standard
227    Standard,
228    /// Bulk
229    Bulk,
230}
231impl fmt::Display for RestoreTier {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        match self {
234            RestoreTier::Standard => f.write_str("Standard"),
235            RestoreTier::Expedited => f.write_str("Expedited"),
236            RestoreTier::Bulk => f.write_str("Bulk"),
237        }
238    }
239}
240
241/// HTTP header: cache_control
242#[derive(Debug, Clone)]
243pub enum CacheControl {
244    NoCache,
245    NoStore,
246    Public,
247    Private,
248    MaxAge(u32),
249}
250impl fmt::Display for CacheControl {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        match self {
253            CacheControl::NoCache => f.write_str("no-cache"),
254            CacheControl::NoStore => f.write_str("no-store"),
255            CacheControl::Public => f.write_str("public"),
256            CacheControl::Private => f.write_str("private"),
257            CacheControl::MaxAge(val) => write!(f, "max-age={}", val),
258        }
259    }
260}
261
262/// HTTP header: content-disposition
263#[derive(Debug, Clone)]
264pub enum ContentDisposition {
265    Inline,
266    Attachment,
267    AttachmentWithNewName(String),
268}
269impl fmt::Display for ContentDisposition {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            ContentDisposition::Inline => f.write_str("inline"),
273            ContentDisposition::AttachmentWithNewName(file_name) => write!(
274                f,
275                "attachment;filename=\"{0}\";filename*=UTF-8''{0}",
276                url_encode(file_name)
277            ),
278            ContentDisposition::Attachment => f.write_str("attachment"),
279        }
280    }
281}
282
283/// Owner information
284#[derive(Debug, Deserialize)]
285pub struct Owner {
286    /// User ID
287    #[serde(rename = "ID")]
288    pub id: u64,
289    /// User name
290    #[serde(rename = "DisplayName")]
291    pub display_name: String,
292}
293
294/// Result returned by `GetBucketAcl`, containing the owner and ACL.
295#[derive(Debug)]
296pub struct BucketAcl {
297    pub owner: Owner,
298    pub acl: Acl,
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn test_url_encode_and_invalid_metadata_key() {
307        assert_eq!(url_encode("a b"), "a%20b");
308        assert!(!invalid_metadata_key("abc-123"));
309        assert!(invalid_metadata_key("abc_123"));
310    }
311
312    #[test]
313    fn test_cache_control_and_content_disposition() {
314        assert_eq!(CacheControl::MaxAge(60).to_string(), "max-age=60");
315        let name = "file name.txt";
316        let expected = format!(
317            "attachment;filename=\"{0}\";filename*=UTF-8''{0}",
318            url_encode(name)
319        );
320        assert_eq!(
321            ContentDisposition::AttachmentWithNewName(name.into()).to_string(),
322            expected
323        );
324    }
325}