qiniu_upload_token/
upload_policy.rs

1use super::{FileType, FromUploadPolicy, StaticUploadTokenProvider, ToStringOptions, UploadTokenProvider};
2use assert_impl::assert_impl;
3use qiniu_credential::{Credential, CredentialProvider};
4use qiniu_utils::{BucketName, ObjectName};
5use serde_json::{
6    json,
7    map::{Keys as JsonMapKeys, Values as JsonMapValues},
8    value::Index as JsonValueIndex,
9    Value as JsonValue,
10};
11use std::{
12    borrow::{Borrow, Cow},
13    fmt,
14    hash::Hash,
15    ops::{Bound, RangeBounds},
16    str::Split,
17    time::{Duration, SystemTime},
18};
19
20const SCOPE_KEY: &str = "scope";
21const IS_PREFIXAL_SCOPE_KEY: &str = "isPrefixalScope";
22const DEADLINE_KEY: &str = "deadline";
23const INSERT_ONLY_KEY: &str = "insertOnly";
24const RETURN_URL_KEY: &str = "returnUrl";
25const RETURN_BODY_KEY: &str = "returnBody";
26const CALLBACK_URL_KEY: &str = "callbackUrl";
27const CALLBACK_HOST_KEY: &str = "callbackHost";
28const CALLBACK_BODY_KEY: &str = "callbackBody";
29const CALLBACK_BODY_TYPE_KEY: &str = "callbackBodyType";
30const SAVE_KEY_KEY: &str = "saveKey";
31const FORCE_SAVE_KEY_KEY: &str = "forceSaveKey";
32const FSIZE_MIN_KEY: &str = "fsizeMin";
33const FSIZE_LIMIT_KEY: &str = "fsizeLimit";
34const DETECT_MIME_KEY: &str = "detectMime";
35const MIME_LIMIT_KEY: &str = "mimeLimit";
36const FILE_TYPE_KEY: &str = "fileType";
37const DELETE_AFTER_DAYS_KEY: &str = "deleteAfterDays";
38
39/// 上传策略
40///
41/// 可以阅读 <https://developer.qiniu.com/kodo/manual/1206/put-policy> 了解七牛安全机制。
42///
43/// ### 根据指定的存储空间和对象名称,生成可以用来上传低频存储类型文件的上传策略
44///
45/// ```
46/// use qiniu_upload_token::{FileType, UploadPolicy};
47/// use std::time::Duration;
48///
49/// let upload_policy = UploadPolicy::new_for_object("your-bucket", "your-key", Duration::from_secs(3600))
50///     .file_type(FileType::InfrequentAccess)
51///     .build();
52/// ```
53#[derive(Clone, Eq, PartialEq)]
54pub struct UploadPolicy {
55    inner: JsonValue,
56}
57
58impl UploadPolicy {
59    /// 为指定的存储空间生成的上传策略
60    ///
61    /// 允许用户上传文件到指定的存储空间,不限制上传客户端指定对象名称。
62    ///
63    /// 上传策略根据给出的客户端配置指定上传凭证有效期
64    #[inline]
65    pub fn new_for_bucket(bucket: impl Into<BucketName>, upload_token_lifetime: Duration) -> UploadPolicyBuilder {
66        UploadPolicyBuilder::new_policy_for_bucket(bucket, upload_token_lifetime)
67    }
68
69    /// 为指定的存储空间和对象名称生成的上传策略
70    ///
71    /// 允许用户以指定的对象名称上传文件到指定的存储空间。
72    /// 上传客户端不能指定与上传策略冲突的对象名称。
73    ///
74    /// 上传策略根据给出的客户端配置指定上传凭证有效期
75    #[inline]
76    pub fn new_for_object(
77        bucket: impl Into<BucketName>,
78        object: impl Into<ObjectName>,
79        upload_token_lifetime: Duration,
80    ) -> UploadPolicyBuilder {
81        UploadPolicyBuilder::new_policy_for_object(bucket, object, upload_token_lifetime)
82    }
83
84    /// 为指定的存储空间和对象名称前缀生成的上传策略
85    ///
86    /// 允许用户以指定的对象名称前缀上传文件到指定的存储空间。
87    /// 上传客户端指定包含该前缀的对象名称。
88    ///
89    /// 上传策略根据给出的客户端配置指定上传凭证有效期
90    #[inline]
91    pub fn new_for_objects_with_prefix(
92        bucket: impl Into<BucketName>,
93        prefix: impl AsRef<str>,
94        upload_token_lifetime: Duration,
95    ) -> UploadPolicyBuilder {
96        UploadPolicyBuilder::new_policy_for_objects_with_prefix(bucket, prefix, upload_token_lifetime)
97    }
98
99    /// 存储空间约束
100    pub fn bucket(&self) -> Option<&str> {
101        self.get(SCOPE_KEY)
102            .as_ref()
103            .and_then(|s| s.as_str())
104            .and_then(|s| s.split(':').next())
105    }
106
107    /// 对象名称约束或对象名称前缀约束
108    pub fn key(&self) -> Option<&str> {
109        self.get(SCOPE_KEY)
110            .as_ref()
111            .and_then(|v| v.as_str())
112            .and_then(|s| s.split_once(':').map(|x| x.1))
113    }
114
115    /// 是否是对象名称前缀约束
116    pub fn use_prefixal_object_key(&self) -> bool {
117        self.get(IS_PREFIXAL_SCOPE_KEY).and_then(|v| v.as_u64()).is_some()
118    }
119
120    /// 是否仅允许新增对象,不允许覆盖对象
121    pub fn is_insert_only(&self) -> bool {
122        self.get(INSERT_ONLY_KEY).and_then(|v| v.as_u64()).unwrap_or_default() > 0
123    }
124
125    /// 是否启用 MIME 类型自动检测
126    pub fn mime_detection_enabled(&self) -> bool {
127        self.get(DETECT_MIME_KEY).and_then(|v| v.as_u64()).unwrap_or_default() > 0
128    }
129
130    /// 上传凭证过期时间
131    pub fn token_deadline(&self) -> Option<SystemTime> {
132        self.get(DEADLINE_KEY)
133            .and_then(|v| v.as_u64())
134            .map(|t| SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(t)).unwrap())
135    }
136
137    /// Web 端文件上传成功后,浏览器执行 303 跳转的 URL
138    pub fn return_url(&self) -> Option<&str> {
139        self.get(RETURN_URL_KEY).and_then(|v| v.as_str())
140    }
141
142    /// 上传成功后,自定义七牛云最终返回给上传端的数据
143    pub fn return_body(&self) -> Option<&str> {
144        self.get(RETURN_BODY_KEY).and_then(|v| v.as_str())
145    }
146
147    /// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL 列表
148    pub fn callback_urls(&self) -> Option<Split<char>> {
149        self.get(CALLBACK_URL_KEY)
150            .and_then(|v| v.as_str())
151            .map(|s| s.split(';'))
152    }
153
154    /// 上传成功后,七牛云向业务服务器发送回调请求时的 `Host`
155    pub fn callback_host(&self) -> Option<&str> {
156        self.get(CALLBACK_HOST_KEY).and_then(|v| v.as_str())
157    }
158
159    /// 上传成功后,七牛云向业务服务器发送回调请求时的内容
160    ///
161    /// 支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)
162    pub fn callback_body(&self) -> Option<&str> {
163        self.get(CALLBACK_BODY_KEY).and_then(|v| v.as_str())
164    }
165
166    /// 上传成功后,七牛云向业务服务器发送回调请求时的 `Content-Type`
167    ///
168    /// 默认为 `application/x-www-form-urlencoded`,也可设置为 `application/json`
169    pub fn callback_body_type(&self) -> Option<&str> {
170        self.get(CALLBACK_BODY_TYPE_KEY).and_then(|v| v.as_str())
171    }
172
173    /// 自定义对象名称
174    ///
175    /// 支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)
176    pub fn save_key(&self) -> Option<&str> {
177        self.get(SAVE_KEY_KEY).and_then(|v| v.as_str())
178    }
179
180    /// 是否忽略客户端指定的对象名称,强制使用自定义对象名称进行文件命名
181    pub fn is_save_key_forced(&self) -> bool {
182        self.get(FORCE_SAVE_KEY_KEY).and_then(|v| v.as_bool()).unwrap_or(false)
183    }
184
185    /// 限定上传文件尺寸的范围
186    ///
187    /// 返回的第一个元素为最小尺寸,第二个元素为最大尺寸,如果为 `None` 表示不限制,单位为字节
188    pub fn file_size_limitation(&self) -> (Option<u64>, Option<u64>) {
189        (
190            self.get(FSIZE_MIN_KEY).and_then(|v| v.as_u64()),
191            self.get(FSIZE_LIMIT_KEY).and_then(|v| v.as_u64()),
192        )
193    }
194
195    /// 限定用户上传的文件类型
196    ///
197    /// 指定本字段值,七牛服务器会侦测文件内容以判断 MIME 类型,再用判断值跟指定值进行匹配,
198    /// 匹配成功则允许上传,匹配失败则返回 403 状态码
199    pub fn mime_types(&self) -> Option<Split<char>> {
200        self.get(MIME_LIMIT_KEY).and_then(|v| v.as_str()).map(|s| s.split(';'))
201    }
202
203    /// 文件类型
204    pub fn file_type(&self) -> Option<FileType> {
205        self.get(FILE_TYPE_KEY).and_then(|v| v.as_u64()).map(FileType::from)
206    }
207
208    /// 对象生命周期
209    ///
210    /// 精确到天
211    pub fn object_lifetime(&self) -> Option<Duration> {
212        self.get(DELETE_AFTER_DAYS_KEY)
213            .and_then(|v| v.as_u64())
214            .map(|d| Duration::from_secs(d * 60 * 60 * 24))
215    }
216
217    /// 获取 JSON 格式的上传凭证
218    pub fn as_json(&self) -> String {
219        serde_json::to_string(&self.inner).unwrap()
220    }
221
222    /// 解析 JSON 格式的上传凭证
223    pub fn from_json(json: impl AsRef<[u8]>) -> serde_json::Result<UploadPolicy> {
224        serde_json::from_slice(json.as_ref()).map(|inner| UploadPolicy { inner })
225    }
226
227    /// 根据指定的上传策略字段获取相应的值
228    #[inline]
229    pub fn get(&self, key: impl JsonValueIndex) -> Option<&JsonValue> {
230        self.inner.get(key)
231    }
232
233    /// 获取上传策略的字段迭代器
234    #[inline]
235    pub fn keys(&self) -> JsonMapKeys {
236        self.inner.as_object().unwrap().keys()
237    }
238
239    /// 获取上传策略的字段值的迭代器
240    #[inline]
241    pub fn values(&self) -> JsonMapValues {
242        self.inner.as_object().unwrap().values()
243    }
244
245    /// 将上传策略转换为动态上传凭证提供者的实例
246    ///
247    /// 该方法与 [`UploadPolicy::into_static_upload_token_provider`] 的区别在于该方法接受 [`CredentialProvider`] 实例
248    #[inline]
249    pub fn into_dynamic_upload_token_provider<T: CredentialProvider + Clone>(
250        self,
251        credential: T,
252    ) -> FromUploadPolicy<T> {
253        FromUploadPolicy::new(self, credential)
254    }
255
256    /// 将上传策略转换为静态上传凭证提供者的实例
257    ///
258    /// 该方法与 [`UploadPolicy::into_dynamic_upload_token_provider`] 的区别在于该方法只能接受 [`Credential`] 实例
259    pub fn into_static_upload_token_provider(
260        self,
261        credential: Credential,
262        opts: ToStringOptions,
263    ) -> StaticUploadTokenProvider {
264        let provider = self.into_dynamic_upload_token_provider(credential);
265        let token = provider.to_token_string(opts).unwrap();
266        let token: StaticUploadTokenProvider = token.parse().unwrap();
267        let (policy, credential) = provider.split();
268        let (access_key, _) = credential.split();
269        token.set_policy(policy);
270        token.set_access_key(access_key);
271        token
272    }
273
274    #[allow(dead_code)]
275    fn assert() {
276        assert_impl!(Send: Self);
277        assert_impl!(Sync: Self);
278    }
279}
280
281impl fmt::Debug for UploadPolicy {
282    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283        self.inner.fmt(f)
284    }
285}
286
287/// 上传策略构建器
288///
289/// 用于生成上传策略,一旦生成完毕,上传策略将无法被修改
290#[derive(Clone)]
291pub struct UploadPolicyBuilder {
292    inner: JsonValue,
293}
294
295impl From<UploadPolicy> for UploadPolicyBuilder {
296    fn from(policy: UploadPolicy) -> Self {
297        Self { inner: policy.inner }
298    }
299}
300
301impl UploadPolicyBuilder {
302    /// 为指定的存储空间生成的上传策略
303    ///
304    /// 允许用户上传文件到指定的存储空间,不限制上传客户端指定对象名称。
305    ///
306    /// 上传策略根据给出的客户端配置指定上传凭证有效期
307    pub fn new_policy_for_bucket(bucket: impl Into<BucketName>, upload_token_lifetime: Duration) -> Self {
308        let mut policy = Self {
309            inner: json!({
310                SCOPE_KEY: bucket.into().to_string(),
311            }),
312        };
313        policy.token_lifetime(upload_token_lifetime);
314        policy
315    }
316
317    /// 为指定的存储空间和对象名称生成的上传策略
318    ///
319    /// 允许用户以指定的对象名称上传文件到指定的存储空间。
320    /// 上传客户端不能指定与上传策略冲突的对象名称。
321    ///
322    /// 上传策略根据给出的客户端配置指定上传凭证有效期
323    pub fn new_policy_for_object(
324        bucket: impl Into<BucketName>,
325        object: impl Into<ObjectName>,
326        upload_token_lifetime: Duration,
327    ) -> Self {
328        let mut policy = Self {
329            inner: json!({
330                SCOPE_KEY: bucket.into().to_string() + ":" + object.into().as_str(),
331            }),
332        };
333        policy.token_lifetime(upload_token_lifetime);
334        policy
335    }
336
337    /// 为指定的存储空间和对象名称前缀生成的上传策略
338    ///
339    /// 允许用户以指定的对象名称前缀上传文件到指定的存储空间。
340    /// 上传客户端指定包含该前缀的对象名称。
341    ///
342    /// 上传策略根据给出的客户端配置指定上传凭证有效期
343    pub fn new_policy_for_objects_with_prefix(
344        bucket: impl Into<BucketName>,
345        prefix: impl AsRef<str>,
346        upload_token_lifetime: Duration,
347    ) -> Self {
348        let mut policy = Self {
349            inner: json!({
350                SCOPE_KEY: bucket.into().to_string() + ":" + prefix.as_ref(),
351                IS_PREFIXAL_SCOPE_KEY: 1,
352            }),
353        };
354        policy.token_lifetime(upload_token_lifetime);
355        policy
356    }
357
358    /// 指定上传凭证有效期
359    pub fn token_lifetime(&mut self, lifetime: Duration) -> &mut Self {
360        self.set(
361            DEADLINE_KEY.into(),
362            JsonValue::Number(
363                SystemTime::now()
364                    .checked_add(lifetime)
365                    .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
366                    .map(|t| t.as_secs())
367                    .unwrap_or(u64::max_value())
368                    .into(),
369            ),
370        )
371    }
372
373    /// 指定上传凭证过期时间
374    pub fn token_deadline(&mut self, deadline: SystemTime) -> &mut Self {
375        self.set(
376            DEADLINE_KEY.into(),
377            JsonValue::Number(
378                deadline
379                    .duration_since(SystemTime::UNIX_EPOCH)
380                    .ok()
381                    .map(|t| t.as_secs())
382                    .unwrap_or(u64::max_value())
383                    .into(),
384            ),
385        )
386    }
387
388    /// 仅允许创建新的对象,不允许覆盖和修改同名对象
389    pub fn insert_only(&mut self) -> &mut Self {
390        self.set(INSERT_ONLY_KEY.into(), JsonValue::Number(1.into()))
391    }
392
393    /// 启用 MIME 类型自动检测
394    pub fn enable_mime_detection(&mut self) -> &mut Self {
395        self.set(DETECT_MIME_KEY.into(), JsonValue::Number(1.into()))
396    }
397
398    /// 禁用 MIME 类型自动检测
399    pub fn disable_mime_detection(&mut self) -> &mut Self {
400        self.unset(DETECT_MIME_KEY)
401    }
402
403    /// 设置文件类型
404    pub fn file_type(&mut self, file_type: FileType) -> &mut Self {
405        self.set(FILE_TYPE_KEY.into(), JsonValue::Number(u8::from(file_type).into()))
406    }
407
408    /// Web 端文件上传成功后,浏览器执行 303 跳转的 URL
409    ///
410    /// 通常用于表单上传。
411    /// 文件上传成功后会跳转到 `<return_url>?upload_ret=<queryString>`,
412    /// `<queryString>` 包含 `return_body()` 内容。
413    /// 如不设置 `return_url`,则直接将 `return_body()` 的内容返回给客户端
414    pub fn return_url(&mut self, url: impl Into<String>) -> &mut Self {
415        self.set(RETURN_URL_KEY.into(), JsonValue::String(url.into()))
416    }
417
418    /// 上传成功后,自定义七牛云最终返回给上传端(在指定 `return_url()` 时是携带在跳转路径参数中)的数据
419    ///
420    /// 支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)。
421    /// `return_body` 要求是合法的 JSON 文本。
422    /// 例如 `{"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}`
423    pub fn return_body(&mut self, body: impl Into<String>) -> &mut Self {
424        self.set(RETURN_BODY_KEY.into(), JsonValue::String(body.into()))
425    }
426
427    /// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL 列表,`Host`,回调请求的内容以及其 `Content-Type`
428    ///
429    /// 七牛服务器会在上传成功后逐一回调 URL 直到有一个成功为止
430    ///
431    /// 如果给出的 `host` 为空字符串,则使用默认的 `Host`
432    ///
433    /// `body` 参数必须不能为空,支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)
434    //
435    /// `body_type` 参数表示 `body` 参数的 `Content-Type`,如果为空,则为默认的 `application/x-www-form-urlencoded`
436    pub fn callback<V: AsRef<[S]>, S: AsRef<str>>(
437        &mut self,
438        urls: V,
439        host: impl Into<String>,
440        body: impl Into<String>,
441        body_type: impl Into<String>,
442    ) -> &mut Self {
443        self.set(CALLBACK_URL_KEY.into(), JsonValue::String(join_str_slice(urls, ";")));
444        {
445            let callback_host = host.into();
446            if callback_host.is_empty() {
447                self.unset(CALLBACK_HOST_KEY);
448            } else {
449                self.set(CALLBACK_HOST_KEY.into(), JsonValue::String(callback_host));
450            }
451        }
452        self.set(CALLBACK_BODY_KEY.into(), JsonValue::String(body.into()));
453        {
454            let callback_body_type = body_type.into();
455            if callback_body_type.is_empty() {
456                self.unset(CALLBACK_BODY_TYPE_KEY);
457            } else {
458                self.set(CALLBACK_BODY_TYPE_KEY.into(), JsonValue::String(callback_body_type));
459            }
460        }
461        self
462    }
463
464    /// 自定义对象名称
465    ///
466    /// 支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)。
467    /// `force` 为 `false` 时,`save_as` 字段仅当用户上传的时候没有主动指定对象名时起作用,
468    /// `force` 为 `true` 时,将强制按 `save_as` 字段的内容命名
469    pub fn save_as(&mut self, save_as: impl Into<String>, force: bool) -> &mut Self {
470        self.set(SAVE_KEY_KEY.into(), JsonValue::String(save_as.into()));
471        if force {
472            self.set(FORCE_SAVE_KEY_KEY.into(), JsonValue::Bool(true));
473        } else {
474            self.unset(FORCE_SAVE_KEY_KEY);
475        }
476        self
477    }
478
479    /// 限定上传文件尺寸的范围
480    ///
481    /// 单位为字节
482    pub fn file_size_limitation(&mut self, size: impl RangeBounds<u64>) -> &mut Self {
483        match size.start_bound() {
484            Bound::Included(&s) => {
485                self.set(FSIZE_MIN_KEY.into(), JsonValue::Number(s.into()));
486            }
487            Bound::Excluded(&s) => {
488                self.set(FSIZE_MIN_KEY.into(), JsonValue::Number((s + 1).into()));
489            }
490            Bound::Unbounded => {
491                self.unset(FSIZE_MIN_KEY);
492            }
493        }
494        match size.end_bound() {
495            Bound::Included(&s) => {
496                self.set(FSIZE_LIMIT_KEY.into(), JsonValue::Number(s.into()));
497            }
498            Bound::Excluded(&s) => {
499                self.set(FSIZE_LIMIT_KEY.into(), JsonValue::Number((s - 1).into()));
500            }
501            Bound::Unbounded => {
502                self.unset(FSIZE_LIMIT_KEY);
503            }
504        }
505        self
506    }
507
508    /// 限定用户上传的文件类型
509    ///
510    /// 指定本字段值,七牛服务器会侦测文件内容以判断 MIME 类型,再用判断值跟指定值进行匹配,
511    /// 匹配成功则允许上传,匹配失败则返回 403 状态码
512    pub fn mime_types<V: AsRef<[S]>, S: AsRef<str>>(&mut self, content_types: V) -> &mut Self {
513        self.set(
514            MIME_LIMIT_KEY.into(),
515            JsonValue::String(join_str_slice(content_types, ";")),
516        )
517    }
518
519    /// 对象生命周期
520    ///
521    /// 精确到天
522    pub fn object_lifetime(&mut self, lifetime: Duration) -> &mut Self {
523        let lifetime_secs = lifetime.as_secs();
524        let secs_one_day = 60 * 60 * 24;
525
526        self.set(
527            DELETE_AFTER_DAYS_KEY.into(),
528            lifetime_secs
529                .checked_add(secs_one_day)
530                .and_then(|s| s.checked_sub(1))
531                .and_then(|s| s.checked_div(secs_one_day))
532                .map(|s| s.into())
533                .unwrap_or_else(|| JsonValue::Number(u64::max_value().into())),
534        )
535    }
536
537    /// 直接设置上传策略的键值对
538    #[inline]
539    pub fn set(&mut self, k: String, v: JsonValue) -> &mut Self {
540        self.inner.as_object_mut().unwrap().insert(k, v);
541        self
542    }
543
544    /// 直接删除上传策略的键
545    #[inline]
546    pub fn unset<Q>(&mut self, k: &Q) -> &mut Self
547    where
548        String: Borrow<Q>,
549        Q: ?Sized + Ord + Eq + Hash,
550    {
551        self.inner.as_object_mut().unwrap().remove(k);
552        self
553    }
554
555    /// 生成上传策略
556    pub fn build(&self) -> UploadPolicy {
557        UploadPolicy {
558            inner: self.inner.clone(),
559        }
560    }
561
562    /// 根据七牛认证信息直接生成上传凭证
563    pub fn build_token(&self, credential: Credential, opts: ToStringOptions) -> StaticUploadTokenProvider {
564        self.build().into_static_upload_token_provider(credential, opts)
565    }
566
567    /// 重置上传策略构建器
568    ///
569    /// 重置构建器使得构建器可以被多次复用
570    pub fn reset(&mut self) {
571        let immutable_keys = [SCOPE_KEY, DEADLINE_KEY, IS_PREFIXAL_SCOPE_KEY];
572        self.inner = JsonValue::Object(
573            self.inner
574                .as_object()
575                .unwrap()
576                .iter()
577                .filter_map(|(k, v)| {
578                    immutable_keys
579                        .iter()
580                        .find(|&ik| k == ik)
581                        .map(|_| (k.to_owned(), v.to_owned()))
582                })
583                .collect(),
584        );
585    }
586
587    #[allow(dead_code)]
588    fn assert() {
589        assert_impl!(Send: Self);
590        assert_impl!(Sync: Self);
591    }
592}
593
594impl fmt::Debug for UploadPolicyBuilder {
595    #[inline]
596    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
597        self.inner.fmt(f)
598    }
599}
600
601impl<'p> From<&'p UploadPolicy> for Cow<'p, UploadPolicy> {
602    #[inline]
603    fn from(policy: &'p UploadPolicy) -> Self {
604        Cow::Borrowed(policy)
605    }
606}
607
608impl From<UploadPolicy> for Cow<'_, UploadPolicy> {
609    #[inline]
610    fn from(policy: UploadPolicy) -> Self {
611        Cow::Owned(policy)
612    }
613}
614
615impl<'p> From<Cow<'p, UploadPolicy>> for UploadPolicy {
616    #[inline]
617    fn from(policy: Cow<'p, UploadPolicy>) -> Self {
618        match policy {
619            Cow::Borrowed(policy) => policy.to_owned(),
620            Cow::Owned(policy) => policy,
621        }
622    }
623}
624
625fn join_str_slice<V: AsRef<[S]>, S: AsRef<str>, Sep: AsRef<str>>(slice: V, sep: Sep) -> String {
626    let mut iter = slice.as_ref().iter().map(|s| s.as_ref());
627    let mut joined = String::new();
628    if let Some(first) = iter.next() {
629        joined.push_str(first);
630        joined = iter.fold(joined, |mut joined, s| {
631            joined.push_str(sep.as_ref());
632            joined.push_str(s);
633            joined
634        })
635    }
636    joined
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642    use anyhow::Result;
643    use mime::APPLICATION_WWW_FORM_URLENCODED;
644    use serde_json::json;
645
646    #[test]
647    fn test_build_upload_policy_for_bucket() -> Result<()> {
648        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600)).build();
649        let now = SystemTime::now();
650        let one_hour_later = now + Duration::from_secs(60 * 60);
651        assert_eq!(policy.bucket(), Some("test_bucket"));
652        assert_eq!(policy.key(), None);
653        assert!(
654            one_hour_later.duration_since(SystemTime::UNIX_EPOCH)?
655                - policy
656                    .token_deadline()
657                    .unwrap()
658                    .duration_since(SystemTime::UNIX_EPOCH)?
659                < Duration::from_secs(5)
660        );
661
662        assert_eq!(policy.keys().len(), 2);
663        assert_eq!(policy.get("scope"), Some(&json!("test_bucket")));
664        assert!(
665            one_hour_later.duration_since(SystemTime::UNIX_EPOCH)?
666                - Duration::from_secs(policy.get("deadline").unwrap().as_u64().unwrap())
667                < Duration::from_secs(5)
668        );
669        assert!(policy.get("isPrefixalScope").is_none());
670        Ok(())
671    }
672
673    #[test]
674    fn test_build_upload_policy_for_object() -> Result<()> {
675        let policy =
676            UploadPolicyBuilder::new_policy_for_object("test_bucket", "test:object", Duration::from_secs(3600)).build();
677        let now = SystemTime::now();
678        let one_hour_later = now + Duration::from_secs(60 * 60);
679        assert_eq!(policy.bucket(), Some("test_bucket"));
680        assert_eq!(policy.key(), Some("test:object"));
681        assert!(!policy.use_prefixal_object_key());
682        assert!(
683            one_hour_later.duration_since(SystemTime::UNIX_EPOCH)?
684                - policy
685                    .token_deadline()
686                    .unwrap()
687                    .duration_since(SystemTime::UNIX_EPOCH)?
688                < Duration::from_secs(5)
689        );
690
691        assert_eq!(policy.keys().len(), 2);
692        assert_eq!(policy.get("scope"), Some(&json!("test_bucket:test:object")));
693        assert!(
694            one_hour_later.duration_since(SystemTime::UNIX_EPOCH)?
695                - Duration::from_secs(policy.get("deadline").unwrap().as_u64().unwrap())
696                < Duration::from_secs(5)
697        );
698        assert!(policy.get("isPrefixalScope").is_none());
699        Ok(())
700    }
701
702    #[test]
703    fn test_build_upload_policy_for_objects_with_prefix() -> Result<()> {
704        let policy = UploadPolicyBuilder::new_policy_for_objects_with_prefix(
705            "test_bucket",
706            "test:object",
707            Duration::from_secs(3600),
708        )
709        .build();
710        let now = SystemTime::now();
711        let one_hour_later = now + Duration::from_secs(60 * 60);
712        assert_eq!(policy.bucket(), Some("test_bucket"));
713        assert_eq!(policy.key(), Some("test:object"));
714        assert!(policy.use_prefixal_object_key());
715        assert!(
716            one_hour_later.duration_since(SystemTime::UNIX_EPOCH)?
717                - policy
718                    .token_deadline()
719                    .unwrap()
720                    .duration_since(SystemTime::UNIX_EPOCH)?
721                < Duration::from_secs(5)
722        );
723
724        assert_eq!(policy.keys().len(), 3);
725        assert_eq!(policy.get("scope"), Some(&json!("test_bucket:test:object")));
726        assert!(
727            one_hour_later.duration_since(SystemTime::UNIX_EPOCH)?
728                - Duration::from_secs(policy.get("deadline").unwrap().as_u64().unwrap())
729                < Duration::from_secs(5)
730        );
731        assert_eq!(policy.get("isPrefixalScope"), Some(&json!(1)));
732        Ok(())
733    }
734
735    #[test]
736    fn test_build_upload_policy_with_deadline() -> Result<()> {
737        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
738            .token_deadline(SystemTime::now())
739            .build();
740        assert!(
741            SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
742                - policy
743                    .token_deadline()
744                    .unwrap()
745                    .duration_since(SystemTime::UNIX_EPOCH)?
746                < Duration::from_secs(5)
747        );
748
749        assert!(
750            SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
751                - Duration::from_secs(policy.get("deadline").unwrap().as_u64().unwrap())
752                < Duration::from_secs(5)
753        );
754        Ok(())
755    }
756
757    #[test]
758    fn test_build_upload_policy_with_lifetime() -> Result<()> {
759        let one_day = Duration::from_secs(60 * 60 * 24);
760        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
761            .token_lifetime(one_day)
762            .build();
763        let now = SystemTime::now();
764        let tomorrow = now + one_day;
765        assert!(
766            tomorrow.duration_since(SystemTime::UNIX_EPOCH)?
767                - policy
768                    .token_deadline()
769                    .unwrap()
770                    .duration_since(SystemTime::UNIX_EPOCH)?
771                < Duration::from_secs(5)
772        );
773
774        assert!(
775            tomorrow.duration_since(SystemTime::UNIX_EPOCH)?
776                - Duration::from_secs(policy.get("deadline").unwrap().as_u64().unwrap())
777                < Duration::from_secs(5)
778        );
779        Ok(())
780    }
781
782    #[test]
783    fn test_build_upload_policy_with_insert_only() -> Result<()> {
784        {
785            let policy = UploadPolicyBuilder::new_policy_for_object("test_bucket", "test", Duration::from_secs(3600))
786                .insert_only()
787                .build();
788            assert!(policy.is_insert_only());
789            assert_eq!(policy.get("insertOnly"), Some(&json!(1)));
790        }
791
792        {
793            let policy = UploadPolicyBuilder::new_policy_for_object("test_bucket", "test", Duration::from_secs(3600))
794                .set("insertOnly".to_owned(), json!(0))
795                .build();
796            assert!(!policy.is_insert_only());
797            assert_eq!(policy.get("insertOnly"), Some(&json!(0)));
798        }
799        Ok(())
800    }
801
802    #[test]
803    fn test_build_upload_policy_with_mime_detection() -> Result<()> {
804        {
805            let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
806                .enable_mime_detection()
807                .build();
808            assert!(policy.mime_detection_enabled());
809            assert_eq!(policy.get("detectMime"), Some(&json!(1)));
810        }
811        {
812            let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
813                .set("detectMime".to_owned(), json!(0))
814                .build();
815            assert!(!policy.mime_detection_enabled());
816            assert_eq!(policy.get("detectMime"), Some(&json!(0)));
817        }
818        Ok(())
819    }
820
821    #[test]
822    fn test_build_upload_policy_with_normal_storage() -> Result<()> {
823        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
824            .file_type(FileType::Standard)
825            .build();
826        assert_eq!(policy.file_type(), Some(FileType::Standard));
827        assert_eq!(policy.get("fileType"), Some(&json!(0)));
828        Ok(())
829    }
830
831    #[test]
832    fn test_build_upload_policy_with_infrequent_storage() -> Result<()> {
833        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
834            .file_type(FileType::InfrequentAccess)
835            .build();
836        assert_eq!(policy.file_type(), Some(FileType::InfrequentAccess));
837        assert_eq!(policy.get("fileType"), Some(&json!(1)));
838        Ok(())
839    }
840
841    #[test]
842    fn test_build_upload_policy_with_return_url() -> Result<()> {
843        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
844            .return_url("http://www.qiniu.io/test")
845            .build();
846        assert_eq!(policy.return_url(), Some("http://www.qiniu.io/test"));
847        assert_eq!(policy.get("returnUrl"), Some(&json!("http://www.qiniu.io/test")));
848        Ok(())
849    }
850
851    #[test]
852    fn test_build_upload_policy_with_return_body() -> Result<()> {
853        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
854            .return_body("datadatadata")
855            .build();
856        assert_eq!(policy.return_body(), Some("datadatadata"));
857        assert_eq!(policy.get("returnBody"), Some(&json!("datadatadata")));
858        Ok(())
859    }
860
861    #[test]
862    fn test_build_upload_policy_with_callback() -> Result<()> {
863        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
864            .callback(
865                ["https://1.1.1.1", "https://2.2.2.2", "https://3.3.3.3"],
866                "www.qiniu.com",
867                "a=b&c=d",
868                "",
869            )
870            .build();
871        assert_eq!(
872            policy.callback_urls().map(|urls| urls.collect::<Vec<&str>>()),
873            Some(vec!["https://1.1.1.1", "https://2.2.2.2", "https://3.3.3.3"])
874        );
875        assert_eq!(policy.callback_host(), Some("www.qiniu.com"));
876        assert_eq!(policy.callback_body(), Some("a=b&c=d"));
877        assert_eq!(policy.callback_body_type(), None);
878        assert_eq!(
879            policy.get("callbackUrl"),
880            Some(&json!("https://1.1.1.1;https://2.2.2.2;https://3.3.3.3"))
881        );
882        assert_eq!(policy.get("callbackHost"), Some(&json!("www.qiniu.com")));
883        assert_eq!(policy.get("callbackBody"), Some(&json!("a=b&c=d")));
884        assert!(policy.get("callbackBodyType").is_none());
885        Ok(())
886    }
887
888    #[test]
889    fn test_build_upload_policy_with_callback_body_with_body_type() -> Result<()> {
890        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
891            .callback(
892                ["https://1.1.1.1", "https://2.2.2.2", "https://3.3.3.3"],
893                "www.qiniu.com",
894                "a=b&c=d",
895                APPLICATION_WWW_FORM_URLENCODED.as_ref(),
896            )
897            .build();
898        assert_eq!(
899            policy.callback_urls().map(|urls| urls.collect::<Vec<&str>>()),
900            Some(vec!["https://1.1.1.1", "https://2.2.2.2", "https://3.3.3.3"])
901        );
902        assert_eq!(policy.callback_host(), Some("www.qiniu.com"));
903        assert_eq!(policy.callback_body(), Some("a=b&c=d"));
904        assert_eq!(
905            policy.callback_body_type(),
906            Some(APPLICATION_WWW_FORM_URLENCODED.as_ref())
907        );
908        assert_eq!(
909            policy.get("callbackUrl"),
910            Some(&json!("https://1.1.1.1;https://2.2.2.2;https://3.3.3.3"))
911        );
912        assert_eq!(policy.get("callbackHost"), Some(&json!("www.qiniu.com")));
913        assert_eq!(policy.get("callbackBody"), Some(&json!("a=b&c=d")));
914        assert_eq!(
915            policy.get("callbackBodyType"),
916            Some(&json!(APPLICATION_WWW_FORM_URLENCODED.as_ref()))
917        );
918        Ok(())
919    }
920
921    #[test]
922    fn test_build_upload_policy_with_save_key() -> Result<()> {
923        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
924            .save_as("target_file", false)
925            .build();
926        assert_eq!(policy.save_key(), Some("target_file"));
927        assert!(!policy.is_save_key_forced());
928        assert_eq!(policy.get("saveKey"), Some(&json!("target_file")));
929        assert!(policy.get("forceSaveKey").is_none());
930        Ok(())
931    }
932
933    #[test]
934    fn test_build_upload_policy_with_save_key_by_force() -> Result<()> {
935        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
936            .save_as("target_file", true)
937            .build();
938        assert_eq!(policy.save_key(), Some("target_file"));
939        assert!(policy.is_save_key_forced());
940        assert_eq!(policy.get("saveKey"), Some(&json!("target_file")));
941        assert_eq!(policy.get("forceSaveKey"), Some(&json!(true)));
942        Ok(())
943    }
944
945    #[test]
946    fn test_build_upload_policy_with_file_size_exclusive_limit() -> Result<()> {
947        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
948            .file_size_limitation(15..20)
949            .build();
950        assert_eq!(policy.file_size_limitation(), (Some(15), Some(19)));
951        assert_eq!(policy.get("fsizeMin"), Some(&json!(15)));
952        assert_eq!(policy.get("fsizeLimit"), Some(&json!(19)));
953        Ok(())
954    }
955
956    #[test]
957    fn test_build_upload_policy_with_file_size_inclusive_limit() -> Result<()> {
958        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
959            .file_size_limitation(15..=20)
960            .build();
961        assert_eq!(policy.file_size_limitation(), (Some(15), Some(20)));
962        assert_eq!(policy.get("fsizeMin"), Some(&json!(15)));
963        assert_eq!(policy.get("fsizeLimit"), Some(&json!(20)));
964        Ok(())
965    }
966
967    #[test]
968    fn test_build_upload_policy_with_file_size_max_limit() -> Result<()> {
969        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
970            .file_size_limitation(..20)
971            .build();
972        assert_eq!(policy.file_size_limitation(), (None, Some(19)));
973        assert!(policy.get("fsizeMin").is_none());
974        assert_eq!(policy.get("fsizeLimit"), Some(&json!(19)));
975        Ok(())
976    }
977
978    #[test]
979    fn test_build_upload_policy_with_file_size_min_limit() -> Result<()> {
980        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
981            .file_size_limitation(15..)
982            .build();
983        assert_eq!(policy.file_size_limitation(), (Some(15), None));
984        assert_eq!(policy.get("fsizeMin"), Some(&json!(15)));
985        assert!(policy.get("fsizeLimit").is_none());
986        Ok(())
987    }
988
989    #[test]
990    fn test_build_upload_policy_with_mime() -> Result<()> {
991        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
992            .mime_types(["image/jpeg", "image/png"])
993            .build();
994        assert_eq!(
995            policy.mime_types().map(|ops| ops.collect::<Vec<&str>>()),
996            Some(vec!["image/jpeg", "image/png"])
997        );
998        assert_eq!(policy.get("mimeLimit"), Some(&json!("image/jpeg;image/png")));
999        Ok(())
1000    }
1001
1002    #[test]
1003    fn test_build_upload_policy_with_object_lifetime() -> Result<()> {
1004        let one_hundred_days = Duration::from_secs(100 * 24 * 60 * 60);
1005        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
1006            .object_lifetime(one_hundred_days)
1007            .build();
1008        assert_eq!(policy.object_lifetime(), Some(one_hundred_days));
1009
1010        assert_eq!(policy.get("deleteAfterDays"), Some(&json!(100)));
1011        Ok(())
1012    }
1013
1014    #[test]
1015    fn test_build_upload_policy_with_short_object_lifetime() -> Result<()> {
1016        let one_hundred_secs = Duration::from_secs(100);
1017        let one_day = Duration::from_secs(24 * 60 * 60);
1018        let policy = UploadPolicyBuilder::new_policy_for_bucket("test_bucket", Duration::from_secs(3600))
1019            .object_lifetime(one_hundred_secs)
1020            .build();
1021        assert_eq!(policy.object_lifetime(), Some(one_day));
1022
1023        assert_eq!(policy.get("deleteAfterDays"), Some(&json!(1)));
1024        Ok(())
1025    }
1026}