Skip to main content

ufile_rus3/api/
object.rs

1use std::{
2    collections::HashMap,
3    fmt::{Display, Formatter},
4};
5
6use anyhow::Error;
7use derive_builder::Builder;
8use reqwest::Method;
9use serde::{Deserialize, Serialize};
10
11use crate::auth::{HmacSha1Signer, Signer};
12
13/// U-cloud protocol
14#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
15pub enum UfileProtocol {
16    Http,
17    #[default]
18    Https,
19}
20
21impl Display for UfileProtocol {
22    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23        match self {
24            UfileProtocol::Http => write!(f, "http"),
25            UfileProtocol::Https => write!(f, "https"),
26        }
27    }
28}
29
30#[derive(Debug, Builder)]
31pub struct ObjectOptAuthParam {
32    /// Required.
33    /// Specify the http method.
34    pub method: Method,
35    /// Required.
36    /// Specify the name of the bucket.
37    #[builder(setter(into))]
38    pub bucket: String,
39    /// Required.
40    /// Specify the name of the object.
41    #[builder(setter(into))]
42    pub key_name: String,
43    /// Content-Type.
44    /// Specify the content type of the file.
45    #[builder(setter(into, strip_option), default)]
46    pub content_type: Option<String>,
47    /// Content-MD5.
48    /// Specify the md5 of the file.
49    #[builder(setter(into, strip_option), default)]
50    pub content_md5: Option<String>,
51    /// Date.
52    /// Specify the date of the request.
53    #[builder(setter(into, strip_option), default)]
54    pub date: Option<String>,
55    /// Specify the source file to be copied.
56    ///
57    /// Example:
58    /// ```
59    /// let source = "ufile://bucket-name/file-name";
60    /// ```
61    #[builder(setter(into, strip_option), default)]
62    pub x_ufile_copy_source: Option<String>,
63    /// X-UFile-Copy-Source-Range.
64    /// Specify the range of the file to be copied.
65    #[builder(setter(into, strip_option), default)]
66    pub x_ufile_copy_source_range: Option<String>,
67}
68
69/// Configuration for Ucloud object operations.
70/// This struct holds the necessary information such as region, proxy suffix, and custom host
71/// to interact with Ucloud object storage.
72#[derive(Debug, Clone, Builder, Serialize, Deserialize)]
73pub struct ObjectConfig {
74    /// default http request endpoint.
75    #[builder(default = "https://api.ucloud.cn".to_string())]
76    #[builder(setter(into))]
77    pub endpoint: String,
78    /// private key
79    #[builder(setter(into))]
80    pub private_key: String,
81    /// public key
82    #[builder(setter(into))]
83    pub public_key: String,
84    /// 仓库地区 (eg: 'cn-bj')
85    #[serde(rename = "Region")]
86    #[builder(setter(into))]
87    pub region: String,
88    /// 代理后缀 (eg: 'ufileos.com')
89    #[serde(rename = "ProxySuffix")]
90    #[builder(setter(into, strip_option), default = Some("ufileos.com".to_string()))]
91    pub proxy_suffix: Option<String>,
92
93    /// 自定义域名 (eg: 'api.ucloud.cn'):若配置了非空自定义域名,则使用自定义域名,不会使用 region + proxySuffix 拼接
94    #[serde(rename = "CustomHost")]
95    #[builder(setter(into, strip_option), default)]
96    pub custom_host: Option<String>,
97
98    /// protocol
99    #[serde(skip)]
100    #[builder(setter(into, strip_option), default)]
101    pub protocol: UfileProtocol,
102}
103
104impl Default for ObjectConfig {
105    fn default() -> Self {
106        Self {
107            endpoint: "https://api.ucloud.cn".to_string(),
108            private_key: "".to_string(),
109            public_key: "".to_string(),
110            region: "cn-sh2".to_string(),
111            proxy_suffix: None,
112            custom_host: None,
113            protocol: UfileProtocol::Https,
114        }
115    }
116}
117
118impl ObjectConfig {
119    /// A method to generate the final request full hosts.
120    pub fn generate_final_host(&self, bucket_name: &str, key_name: &str) -> String {
121        let key_name = urlencoding::encode(key_name);
122        if let Some(ref custom_hosts) = self.custom_host {
123            format!("{}/{}", custom_hosts, key_name)
124        } else {
125            let bucket_name = urlencoding::encode(bucket_name);
126            let region = urlencoding::encode(&self.region);
127            let proxy_suffix = if let Some(ref suffix) = self.proxy_suffix {
128                suffix
129            } else {
130                ""
131            };
132            let proxy_suffix = urlencoding::encode(proxy_suffix);
133            format!(
134                "{}://{}.{}.{}/{}",
135                self.protocol,
136                bucket_name.as_ref(),
137                region.as_ref(),
138                proxy_suffix.as_ref(),
139                key_name.as_ref()
140            )
141        }
142    }
143
144    /// This method is used to generate private url which contains signature and expire time.
145    ///
146    /// # Arguments
147    ///
148    /// * `method` - The http method.
149    /// * `bucket_name` - The name of the bucket.
150    /// * `key_name` - The name of the object.
151    /// * `expires` - The expire time of the url. unit: second.
152    pub fn authorization_private_url(
153        &self,
154        method: Method,
155        bucket_name: &str,
156        key_name: &str,
157        expires: &str,
158    ) -> Result<String, Error> {
159        if bucket_name.is_empty() {
160            return Err(Error::msg("bucket must not be empty."));
161        }
162
163        if key_name.is_empty() {
164            return Err(Error::msg("key_name must not be empty."));
165        }
166
167        if expires.parse::<u64>()? == 0 {
168            return Err(Error::msg("expires must not be zero."));
169        }
170        let sign_data = format!(
171            "{}\n{}\n{}\n{}\n/{}/{}",
172            method.as_str(),
173            "",
174            "",
175            expires,
176            bucket_name,
177            key_name
178        );
179        tracing::debug!("sign_data: \n{}", sign_data);
180        // we should calculate signature here.
181        HmacSha1Signer.signature(&self.private_key, &sign_data)
182    }
183}
184
185#[derive(Debug, Serialize, Deserialize)]
186pub struct BaseResponse {
187    #[serde(skip)]
188    pub headers: HashMap<String, String>,
189    #[serde(rename = "RetCode")]
190    pub ret_code: i32,
191    #[serde(rename = "Message", alias = "ErrMsg")]
192    pub message: Option<String>,
193}
194
195#[derive(Debug, Serialize, Deserialize)]
196pub struct PutObjectResultResponse {
197    #[serde(flatten)]
198    pub resp: BaseResponse,
199    #[serde(rename = "ETag")]
200    pub etag: String,
201}
202
203impl From<BaseResponse> for PutObjectResultResponse {
204    fn from(resp: BaseResponse) -> Self {
205        Self {
206            resp,
207            etag: String::new(),
208        }
209    }
210}
211
212/// This struct describe the init multipart upload task.
213#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(rename_all = "PascalCase")]
215pub struct InitMultipartState {
216    /// 上传 ID
217    pub upload_id: String,
218    /// 块大小
219    pub blk_size: u64,
220    /// Target Bucket
221    pub bucket: String,
222    /// Cloud object name
223    #[serde(rename = "Key", alias = "Key")]
224    pub key_name: String,
225    /// Mime type
226    pub mime_type: Option<String>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "PascalCase")]
231pub struct MultipartUploadState {
232    #[serde(skip_deserializing)]
233    pub headers: HashMap<String, String>,
234    pub part_number: usize,
235    #[serde(skip_deserializing)]
236    pub etag: String,
237}
238
239/// This struct describe the response of finish multipart upload task.
240#[derive(Debug, Serialize, Deserialize)]
241#[serde(rename_all = "PascalCase")]
242pub struct FinishUploadResponse {
243    #[serde(skip_deserializing)]
244    pub headers: HashMap<String, String>,
245    pub bucket: String,
246    pub key: String,
247    pub file_size: isize,
248    #[serde(skip_deserializing)]
249    pub etag: String,
250}
251
252/// This struct describe the response headers of head file api request.
253#[derive(Debug, Serialize, Deserialize)]
254pub struct HeadFileResponse {
255    #[serde(skip_deserializing)]
256    pub headers: Option<HashMap<String, String>>,
257    /// Http response headers
258    pub etag: Option<String>,
259    /// Content-Type of the file
260    pub content_type: String,
261    /// Content-length of the file.
262    pub content_length: u64,
263    /// Last modified time.
264    pub last_modified: Option<String>,
265}