qiniu_upload_token/
upload_token.rs

1use super::{UploadPolicy, UploadPolicyBuilder};
2use anyhow::{Error as AnyError, Result as AnyResult};
3use auto_impl::auto_impl;
4use dyn_clonable::clonable;
5use once_cell::sync::OnceCell;
6use qiniu_credential::{AccessKey, CredentialProvider};
7use qiniu_utils::{base64, BucketName, ObjectName};
8use std::{
9    borrow::Cow,
10    convert::Infallible,
11    fmt::{self, Debug, Display},
12    io::Error as IoError,
13    ops::{Deref, DerefMut},
14    str::FromStr,
15    sync::{Arc, RwLock},
16    time::{Duration, Instant},
17};
18use thiserror::Error;
19
20#[cfg(feature = "async")]
21use {
22    futures::lock::Mutex as AsyncMutex,
23    std::{future::Future, pin::Pin},
24};
25
26/// 上传凭证获取接口
27///
28/// 可以阅读 <https://developer.qiniu.com/kodo/manual/1208/upload-token> 了解七牛安全机制。
29#[clonable]
30#[auto_impl(&, &mut, Box, Rc, Arc)]
31pub trait UploadTokenProvider: Clone + Debug + Sync + Send {
32    /// 从上传凭证内获取 AccessKey
33    ///
34    /// 该方法的异步版本为 [`Self::async_access_key`]。
35    fn access_key(&self, opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey>;
36
37    /// 异步从上传凭证内获取 AccessKey
38    #[inline]
39    #[cfg(feature = "async")]
40    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
41    fn async_access_key(&self, opts: GetAccessKeyOptions) -> AsyncParseResult<'_, GotAccessKey> {
42        Box::pin(async move { self.access_key(opts) })
43    }
44
45    /// 从上传凭证内获取上传策略
46    ///
47    /// 该方法的异步版本为 [`Self::async_policy`]。
48    fn policy(&self, opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy>;
49
50    /// 异步从上传凭证内获取上传策略
51    #[inline]
52    #[cfg(feature = "async")]
53    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
54    fn async_policy(&self, opts: GetPolicyOptions) -> AsyncParseResult<'_, GotUploadPolicy> {
55        Box::pin(async move { self.policy(opts) })
56    }
57
58    /// 生成字符串
59    ///
60    /// 该方法的异步版本为 [`Self::async_to_token_string`]。
61    fn to_token_string(&self, opts: ToStringOptions) -> ToStringResult<Cow<'_, str>>;
62
63    /// 异步生成字符串
64    #[inline]
65    #[cfg(feature = "async")]
66    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
67    fn async_to_token_string(&self, opts: ToStringOptions) -> AsyncToStringResult<'_, Cow<'_, str>> {
68        Box::pin(async move { self.to_token_string(opts) })
69    }
70}
71
72/// 获取 Access Key 的选项
73#[derive(Copy, Clone, Debug, Default)]
74pub struct GetAccessKeyOptions {}
75
76impl GetAccessKeyOptions {}
77
78/// 获取上传策略的选项
79#[derive(Copy, Clone, Debug, Default)]
80pub struct GetPolicyOptions {}
81
82impl GetPolicyOptions {}
83
84/// 获取上传凭证的选项
85#[derive(Copy, Clone, Debug, Default)]
86pub struct ToStringOptions {}
87
88impl ToStringOptions {}
89
90/// 获取的 Access Key
91///
92/// 该数据结构目前和 Access Key 相同,可以和 Access Key 相互转换,但之后可能会添加更多字段
93#[derive(Debug)]
94pub struct GotAccessKey(AccessKey);
95
96impl From<GotAccessKey> for AccessKey {
97    #[inline]
98    fn from(result: GotAccessKey) -> Self {
99        result.into_access_key()
100    }
101}
102
103impl From<AccessKey> for GotAccessKey {
104    #[inline]
105    fn from(result: AccessKey) -> Self {
106        Self(result)
107    }
108}
109
110impl GotAccessKey {
111    /// 获取 Access Key
112    #[inline]
113    pub fn access_key(&self) -> &AccessKey {
114        &self.0
115    }
116
117    /// 获取 Access Key 的可变引用
118    #[inline]
119    pub fn access_key_mut(&mut self) -> &mut AccessKey {
120        &mut self.0
121    }
122
123    /// 转换为 Access Key
124    #[inline]
125    pub fn into_access_key(self) -> AccessKey {
126        self.0
127    }
128}
129
130impl Deref for GotAccessKey {
131    type Target = AccessKey;
132
133    #[inline]
134    fn deref(&self) -> &Self::Target {
135        &self.0
136    }
137}
138
139impl DerefMut for GotAccessKey {
140    #[inline]
141    fn deref_mut(&mut self) -> &mut Self::Target {
142        &mut self.0
143    }
144}
145
146/// 获取的上传策略
147///
148/// 该数据结构目前和上传策略相同,可以和上传策略相互转换,但之后可能会添加更多字段
149#[derive(Debug, Clone)]
150pub struct GotUploadPolicy<'a>(Cow<'a, UploadPolicy>);
151
152impl<'a> From<GotUploadPolicy<'a>> for Cow<'a, UploadPolicy> {
153    #[inline]
154    fn from(result: GotUploadPolicy<'a>) -> Self {
155        result.0
156    }
157}
158
159impl From<GotUploadPolicy<'_>> for UploadPolicy {
160    #[inline]
161    fn from(result: GotUploadPolicy<'_>) -> Self {
162        result.into_upload_policy()
163    }
164}
165
166impl<'a> From<Cow<'a, UploadPolicy>> for GotUploadPolicy<'a> {
167    #[inline]
168    fn from(policy: Cow<'a, UploadPolicy>) -> Self {
169        Self(policy)
170    }
171}
172
173impl<'a> From<&'a UploadPolicy> for GotUploadPolicy<'a> {
174    #[inline]
175    fn from(policy: &'a UploadPolicy) -> Self {
176        Self::from(Cow::Borrowed(policy))
177    }
178}
179
180impl From<UploadPolicy> for GotUploadPolicy<'_> {
181    #[inline]
182    fn from(policy: UploadPolicy) -> Self {
183        Self::from(Cow::Owned(policy))
184    }
185}
186
187impl GotUploadPolicy<'_> {
188    /// 获取上传策略
189    #[inline]
190    pub fn upload_policy(&self) -> &UploadPolicy {
191        &self.0
192    }
193
194    /// 获取上传策略的可变引用
195    #[inline]
196    pub fn upload_policy_mut(&mut self) -> &mut UploadPolicy {
197        self.0.to_mut()
198    }
199
200    /// 转换为上传策略
201    #[inline]
202    pub fn into_upload_policy(self) -> UploadPolicy {
203        self.0.into_owned()
204    }
205}
206
207impl Deref for GotUploadPolicy<'_> {
208    type Target = UploadPolicy;
209
210    #[inline]
211    fn deref(&self) -> &Self::Target {
212        &self.0
213    }
214}
215
216impl DerefMut for GotUploadPolicy<'_> {
217    #[inline]
218    fn deref_mut(&mut self) -> &mut Self::Target {
219        self.0.to_mut()
220    }
221}
222
223/// 上传凭证获取接口扩展
224///
225/// 提供存储空间名称解析方法
226pub trait UploadTokenProviderExt: UploadTokenProvider {
227    /// 获取上传凭证中的存储空间名称
228    ///
229    /// 该方法的异步版本为 [`Self::async_bucket_name`]。
230    fn bucket_name(&self, opts: GetPolicyOptions) -> ParseResult<BucketName> {
231        self.policy(opts).and_then(|policy| {
232            policy
233                .bucket()
234                .map_or(Err(ParseError::InvalidUploadTokenFormat), |bucket_name| {
235                    Ok(BucketName::from(bucket_name))
236                })
237        })
238    }
239
240    /// 异步获取上传凭证中的存储空间名称
241    #[cfg(feature = "async")]
242    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
243    fn async_bucket_name(&self, opts: GetPolicyOptions) -> AsyncParseResult<'_, BucketName> {
244        Box::pin(async move {
245            self.async_policy(opts).await.and_then(|policy| {
246                policy
247                    .bucket()
248                    .map_or(Err(ParseError::InvalidUploadTokenFormat), |bucket_name| {
249                        Ok(BucketName::from(bucket_name))
250                    })
251            })
252        })
253    }
254}
255
256impl<T: UploadTokenProvider> UploadTokenProviderExt for T {}
257
258/// 静态上传凭证提供者
259///
260/// 根据已经被生成好的上传凭证字符串生成上传凭证获取接口的实例,可以将上传凭证解析为 Access Token 和上传策略
261#[derive(Clone)]
262pub struct StaticUploadTokenProvider {
263    upload_token: Box<str>,
264    policy: OnceCell<UploadPolicy>,
265    access_key: OnceCell<AccessKey>,
266}
267
268impl StaticUploadTokenProvider {
269    /// 构建一个静态上传凭证,只需要传入静态的上传凭证字符串即可
270    pub fn new(upload_token: impl Into<String>) -> Self {
271        Self {
272            upload_token: upload_token.into().into_boxed_str(),
273            policy: OnceCell::new(),
274            access_key: OnceCell::new(),
275        }
276    }
277
278    /// 获取上传凭证字符串
279    #[inline]
280    pub fn into_string(self) -> String {
281        self.upload_token.into_string()
282    }
283
284    pub(super) fn set_policy(&self, policy: UploadPolicy) {
285        self.policy.set(policy).ok();
286    }
287
288    pub(super) fn set_access_key(&self, access_key: AccessKey) {
289        self.access_key.set(access_key).ok();
290    }
291}
292
293impl Debug for StaticUploadTokenProvider {
294    #[inline]
295    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296        f.debug_struct("StaticUploadTokenProvider")
297            .field("upload_token", &self.upload_token)
298            .finish()
299    }
300}
301
302impl Display for StaticUploadTokenProvider {
303    #[inline]
304    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
305        Display::fmt(&self.upload_token, f)
306    }
307}
308
309impl UploadTokenProvider for StaticUploadTokenProvider {
310    fn access_key(&self, _opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
311        self.access_key
312            .get_or_try_init(|| {
313                self.upload_token
314                    .find(':')
315                    .map(|i| self.upload_token.split_at(i).0.to_owned().into())
316                    .ok_or(ParseError::InvalidUploadTokenFormat)
317            })
318            .map(|access_key| access_key.to_owned())
319            .map(GotAccessKey::from)
320    }
321
322    fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
323        self.policy
324            .get_or_try_init(|| {
325                let encoded_policy = self
326                    .upload_token
327                    .splitn(3, ':')
328                    .last()
329                    .ok_or(ParseError::InvalidUploadTokenFormat)?;
330                let decoded_policy =
331                    base64::decode(encoded_policy.as_bytes()).map_err(ParseError::Base64DecodeError)?;
332                UploadPolicy::from_json(decoded_policy).map_err(ParseError::JsonDecodeError)
333            })
334            .map(Cow::Borrowed)
335            .map(GotUploadPolicy::from)
336    }
337
338    #[inline]
339    fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
340        Ok(Cow::Borrowed(self.upload_token.as_ref()))
341    }
342}
343
344impl<T: Into<String>> From<T> for StaticUploadTokenProvider {
345    #[inline]
346    fn from(s: T) -> Self {
347        Self::new(s.into())
348    }
349}
350
351impl FromStr for StaticUploadTokenProvider {
352    type Err = Infallible;
353
354    #[inline]
355    fn from_str(s: &str) -> Result<Self, Self::Err> {
356        Ok(Self::new(s))
357    }
358}
359
360/// 根据上传凭证生成上传策略
361///
362/// 通过 [`UploadPolicy::into_dynamic_upload_token_provider()`] 创建
363#[derive(Debug, Clone)]
364pub struct FromUploadPolicy<C: Clone> {
365    upload_policy: UploadPolicy,
366    credential: C,
367}
368
369impl<C: Clone> FromUploadPolicy<C> {
370    /// 基于上传策略和认证信息生成上传凭证实例
371    #[inline]
372    pub fn new(upload_policy: UploadPolicy, credential: C) -> Self {
373        Self {
374            upload_policy,
375            credential,
376        }
377    }
378
379    /// 同时返回构建时提供的上传策略和认证信息提供者
380    #[inline]
381    pub fn split(self) -> (UploadPolicy, C) {
382        (self.upload_policy, self.credential)
383    }
384}
385
386impl<C: CredentialProvider + Clone> UploadTokenProvider for FromUploadPolicy<C> {
387    fn access_key(&self, _opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
388        Ok(self
389            .credential
390            .get(Default::default())?
391            .into_credential()
392            .split()
393            .0
394            .into())
395    }
396
397    #[inline]
398    fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
399        Ok(Cow::Borrowed(&self.upload_policy).into())
400    }
401
402    fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
403        Ok(Cow::Owned(
404            self.credential
405                .get(Default::default())?
406                .sign_with_data(self.upload_policy.as_json().as_bytes()),
407        ))
408    }
409}
410
411type OnPolicyGeneratedCallback = Arc<dyn Fn(&mut UploadPolicyBuilder) -> AnyResult<()> + Sync + Send + 'static>;
412
413/// 基于存储空间的动态生成
414///
415/// 根据存储空间的快速生成上传凭证实例
416#[derive(Clone)]
417pub struct BucketUploadTokenProvider<C: Clone> {
418    bucket: BucketName,
419    upload_token_lifetime: Duration,
420    credential: C,
421    on_policy_generated: Option<OnPolicyGeneratedCallback>,
422}
423
424impl<C: Clone> BucketUploadTokenProvider<C> {
425    /// 基于存储空间和认证信息动态生成上传凭证实例
426    #[inline]
427    pub fn new(bucket: impl Into<BucketName>, upload_token_lifetime: Duration, credential: C) -> Self {
428        Self::builder(bucket, upload_token_lifetime, credential).build()
429    }
430
431    /// 创建存储空间上传凭证构建器
432    #[inline]
433    pub fn builder(
434        bucket: impl Into<BucketName>,
435        upload_token_lifetime: Duration,
436        credential: C,
437    ) -> BucketUploadTokenProviderBuilder<C> {
438        BucketUploadTokenProviderBuilder {
439            inner: Self {
440                bucket: bucket.into(),
441                upload_token_lifetime,
442                credential,
443                on_policy_generated: None,
444            },
445        }
446    }
447
448    fn make_policy(&self) -> AnyResult<UploadPolicy> {
449        let mut builder =
450            UploadPolicyBuilder::new_policy_for_bucket(self.bucket.to_string(), self.upload_token_lifetime);
451        if let Some(on_policy_generated) = self.on_policy_generated.as_ref() {
452            on_policy_generated(&mut builder)?;
453        }
454        Ok(builder.build())
455    }
456}
457
458impl<C: Clone> Debug for BucketUploadTokenProvider<C> {
459    #[inline]
460    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
461        f.debug_struct("BucketUploadTokenProvider")
462            .field("bucket", &self.bucket)
463            .field("upload_token_lifetime", &self.upload_token_lifetime)
464            .finish()
465    }
466}
467
468impl<C: CredentialProvider + Clone> UploadTokenProvider for BucketUploadTokenProvider<C> {
469    #[inline]
470    fn access_key(&self, _opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
471        Ok(self
472            .credential
473            .get(Default::default())?
474            .into_credential()
475            .split()
476            .0
477            .into())
478    }
479
480    fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
481        Ok(self.make_policy()?.into())
482    }
483
484    fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
485        Ok(Cow::Owned(
486            self.credential
487                .get(Default::default())?
488                .sign_with_data(self.make_policy()?.as_json().as_bytes()),
489        ))
490    }
491}
492
493/// 存储空间上传凭证构建器
494#[derive(Clone)]
495pub struct BucketUploadTokenProviderBuilder<C: Clone> {
496    inner: BucketUploadTokenProvider<C>,
497}
498
499impl<C: Clone> BucketUploadTokenProviderBuilder<C> {
500    /// 设置上传凭证回调函数
501    #[inline]
502    #[must_use]
503    pub fn on_policy_generated(
504        mut self,
505        callback: impl Fn(&mut UploadPolicyBuilder) -> AnyResult<()> + Sync + Send + 'static,
506    ) -> Self {
507        self.inner.on_policy_generated = Some(Arc::new(callback));
508        self
509    }
510
511    /// 构造存储空间上传凭证
512    #[inline]
513    pub fn build(self) -> BucketUploadTokenProvider<C> {
514        self.inner
515    }
516}
517
518impl<C: Clone> Debug for BucketUploadTokenProviderBuilder<C> {
519    #[inline]
520    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
521        f.debug_struct("BucketUploadTokenProviderBuilder")
522            .field("bucket", &self.inner.bucket)
523            .field("upload_token_lifetime", &self.inner.upload_token_lifetime)
524            .finish()
525    }
526}
527
528/// 基于对象的动态生成
529///
530/// 根据对象的快速生成上传凭证实例
531#[derive(Clone)]
532pub struct ObjectUploadTokenProvider<C: Clone> {
533    bucket: BucketName,
534    object: ObjectName,
535    upload_token_lifetime: Duration,
536    credential: C,
537    on_policy_generated: Option<OnPolicyGeneratedCallback>,
538}
539
540impl<C: Clone> ObjectUploadTokenProvider<C> {
541    /// 基于存储空间和对象名称和认证信息动态生成上传凭证实例
542    #[inline]
543    pub fn new(
544        bucket: impl Into<BucketName>,
545        object: impl Into<ObjectName>,
546        upload_token_lifetime: Duration,
547        credential: C,
548    ) -> Self {
549        Self::builder(bucket, object, upload_token_lifetime, credential).build()
550    }
551
552    /// 创建对象上传凭证构建器
553    #[inline]
554    pub fn builder(
555        bucket: impl Into<BucketName>,
556        object: impl Into<ObjectName>,
557        upload_token_lifetime: Duration,
558        credential: C,
559    ) -> ObjectUploadTokenProviderBuilder<C> {
560        ObjectUploadTokenProviderBuilder {
561            inner: Self {
562                bucket: bucket.into(),
563                object: object.into(),
564                upload_token_lifetime,
565                credential,
566                on_policy_generated: None,
567            },
568        }
569    }
570
571    fn make_policy(&self) -> AnyResult<UploadPolicy> {
572        let mut builder = UploadPolicyBuilder::new_policy_for_object(
573            self.bucket.to_string(),
574            self.object.to_string(),
575            self.upload_token_lifetime,
576        );
577        if let Some(on_policy_generated) = self.on_policy_generated.as_ref() {
578            on_policy_generated(&mut builder)?;
579        }
580        Ok(builder.build())
581    }
582}
583
584impl<C: Clone> Debug for ObjectUploadTokenProvider<C> {
585    #[inline]
586    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
587        f.debug_struct("ObjectUploadTokenProvider")
588            .field("bucket", &self.bucket)
589            .field("object", &self.object)
590            .field("upload_token_lifetime", &self.upload_token_lifetime)
591            .finish()
592    }
593}
594
595impl<C: CredentialProvider + Clone> UploadTokenProvider for ObjectUploadTokenProvider<C> {
596    fn access_key(&self, _opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
597        Ok(self
598            .credential
599            .get(Default::default())?
600            .into_credential()
601            .split()
602            .0
603            .into())
604    }
605
606    fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
607        Ok(self.make_policy()?.into())
608    }
609
610    fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
611        Ok(Cow::Owned(
612            self.credential
613                .get(Default::default())?
614                .sign_with_data(self.make_policy()?.as_json().as_bytes()),
615        ))
616    }
617}
618
619/// 对象上传凭证构建器
620#[derive(Clone)]
621pub struct ObjectUploadTokenProviderBuilder<C: Clone> {
622    inner: ObjectUploadTokenProvider<C>,
623}
624
625impl<C: Clone> ObjectUploadTokenProviderBuilder<C> {
626    /// 设置上传凭证回调函数
627    #[inline]
628    #[must_use]
629    pub fn on_policy_generated(
630        mut self,
631        callback: impl Fn(&mut UploadPolicyBuilder) -> AnyResult<()> + Sync + Send + 'static,
632    ) -> Self {
633        self.inner.on_policy_generated = Some(Arc::new(callback));
634        self
635    }
636
637    /// 构建对象上传凭证
638    #[inline]
639    pub fn build(self) -> ObjectUploadTokenProvider<C> {
640        self.inner
641    }
642}
643
644impl<C: Clone> Debug for ObjectUploadTokenProviderBuilder<C> {
645    #[inline]
646    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
647        f.debug_struct("ObjectUploadTokenProviderBuilder")
648            .field("bucket", &self.inner.bucket)
649            .field("object", &self.inner.object)
650            .field("upload_token_lifetime", &self.inner.upload_token_lifetime)
651            .finish()
652    }
653}
654
655#[derive(Debug)]
656struct Cache<T> {
657    cached_at: Instant,
658    value: T,
659}
660
661#[derive(Debug, Default, Clone)]
662struct SyncCache(Arc<SyncCacheInner>);
663
664#[derive(Debug, Default)]
665struct SyncCacheInner {
666    access_key: RwLock<Option<Cache<AccessKey>>>,
667    upload_policy: RwLock<Option<Cache<UploadPolicy>>>,
668    upload_token: RwLock<Option<Cache<String>>>,
669}
670
671#[cfg(feature = "async")]
672#[derive(Debug, Default, Clone)]
673struct AsyncCache(Arc<AsyncCacheInner>);
674
675#[cfg(feature = "async")]
676#[derive(Debug, Default)]
677struct AsyncCacheInner {
678    access_key: AsyncMutex<Option<Cache<AccessKey>>>,
679    upload_policy: AsyncMutex<Option<Cache<UploadPolicy>>>,
680    upload_token: AsyncMutex<Option<Cache<String>>>,
681}
682
683/// 缓存生成的上传凭证
684///
685/// 内部存储另一个上传凭证获取接口的实例,该结构为之提供指定时间内的缓存,避免每次都要重新生成新的上传凭证。
686#[derive(Debug, Clone)]
687pub struct CachedUploadTokenProvider<P: Clone> {
688    inner_provider: P,
689    cache_lifetime: Duration,
690    sync_cache: SyncCache,
691
692    #[cfg(feature = "async")]
693    async_cache: AsyncCache,
694}
695
696impl<P: Clone> CachedUploadTokenProvider<P> {
697    /// 创建上传凭证缓存,需要提供另一个上传凭证获取接口的实例,和需要缓存的时长
698    #[inline]
699    pub fn new(inner_provider: P, cache_lifetime: Duration) -> Self {
700        Self {
701            inner_provider,
702            cache_lifetime,
703            sync_cache: Default::default(),
704
705            #[cfg(feature = "async")]
706            async_cache: Default::default(),
707        }
708    }
709}
710
711macro_rules! sync_method {
712    ($provider:expr, $cache_field:ident, $opts_field:ident, $opts_type:ty, $method_name:ident, $return_type:ty) => {{
713        let guard = $provider.sync_cache.0.$cache_field.read().unwrap();
714        return if let Some(cache) = &*guard {
715            if cache.cached_at.elapsed() < $provider.cache_lifetime {
716                Ok(cache.value.to_owned().into())
717            } else {
718                drop(guard);
719                update_cache(&$provider, $opts_field)
720            }
721        } else {
722            drop(guard);
723            update_cache(&$provider, $opts_field)
724        };
725
726        #[allow(unused_lifetimes)]
727        fn update_cache(
728            provider: &CachedUploadTokenProvider<impl UploadTokenProvider + Clone>,
729            opts: $opts_type,
730        ) -> $return_type {
731            let mut guard = provider.sync_cache.0.$cache_field.write().unwrap();
732            if let Some(cache) = &*guard {
733                if cache.cached_at.elapsed() < provider.cache_lifetime {
734                    return Ok(cache.value.to_owned().into());
735                }
736            }
737            match provider.inner_provider.$method_name(opts) {
738                Ok(value) => {
739                    *guard = Some(Cache {
740                        cached_at: Instant::now(),
741                        value: value.to_owned().into(),
742                    });
743                    Ok(value)
744                }
745                Err(err) => Err(err),
746            }
747        }
748    }};
749}
750
751#[cfg(feature = "async")]
752macro_rules! async_method {
753    ($provider:expr, $cache_field:ident, $opts_field:ident, $method_name:ident) => {{
754        Box::pin(async move {
755            let mut cache = $provider.async_cache.0.$cache_field.lock().await;
756            if let Some(cache) = &*cache {
757                if cache.cached_at.elapsed() < $provider.cache_lifetime {
758                    return Ok(cache.value.to_owned().into());
759                }
760            }
761            match $provider.inner_provider.$method_name($opts_field).await {
762                Ok(value) => {
763                    *cache = Some(Cache {
764                        cached_at: Instant::now(),
765                        value: value.to_owned().into(),
766                    });
767                    Ok(value)
768                }
769                Err(err) => Err(err),
770            }
771        })
772    }};
773}
774
775impl<P: UploadTokenProvider + Clone> UploadTokenProvider for CachedUploadTokenProvider<P> {
776    fn access_key(&self, opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
777        sync_method!(
778            self,
779            access_key,
780            opts,
781            GetAccessKeyOptions,
782            access_key,
783            ParseResult<GotAccessKey>
784        )
785    }
786
787    fn policy(&self, opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
788        sync_method!(
789            self,
790            upload_policy,
791            opts,
792            GetPolicyOptions,
793            policy,
794            ParseResult<GotUploadPolicy<'_>>
795        )
796    }
797
798    fn to_token_string(&self, opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
799        sync_method!(
800            self,
801            upload_token,
802            opts,
803            ToStringOptions,
804            to_token_string,
805            ToStringResult<Cow<str>>
806        )
807    }
808
809    #[cfg(feature = "async")]
810    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
811    fn async_access_key(&self, opts: GetAccessKeyOptions) -> AsyncParseResult<'_, GotAccessKey> {
812        async_method!(self, access_key, opts, async_access_key)
813    }
814
815    #[cfg(feature = "async")]
816    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
817    fn async_policy(&self, opts: GetPolicyOptions) -> AsyncParseResult<'_, GotUploadPolicy<'_>> {
818        async_method!(self, upload_policy, opts, async_policy)
819    }
820
821    #[cfg(feature = "async")]
822    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
823    fn async_to_token_string(&self, opts: ToStringOptions) -> AsyncToStringResult<'_, Cow<'_, str>> {
824        async_method!(self, upload_token, opts, async_to_token_string)
825    }
826}
827
828/// 上传凭证解析错误
829#[derive(Error, Debug)]
830#[non_exhaustive]
831pub enum ParseError {
832    /// 上传凭证格式错误
833    #[error("Invalid upload token format")]
834    InvalidUploadTokenFormat,
835    /// 上传凭证 Base64 解码错误
836    #[error("Base64 decode error: {0}")]
837    Base64DecodeError(#[from] base64::DecodeError),
838    /// 上传凭证 JSON 解析错误
839    #[error("JSON decode error: {0}")]
840    JsonDecodeError(#[from] serde_json::Error),
841    /// 上传凭证获取认证信息错误
842    #[error("Credential get error: {0}")]
843    CredentialGetError(#[from] IoError),
844    /// `on_policy_generated` 回调函数错误
845    #[error("on_policy_generated callback error: {0}")]
846    CallbackError(#[from] AnyError),
847}
848
849/// 上传凭证解析结果
850pub type ParseResult<T> = Result<T, ParseError>;
851
852/// 生成上传凭证字符串错误
853#[derive(Error, Debug)]
854#[non_exhaustive]
855pub enum ToStringError {
856    /// 上传凭证获取认证信息错误
857    #[error("Credential get error: {0}")]
858    CredentialGetError(#[from] IoError),
859    /// 生成上传凭证回调函数错误
860    #[error("Generate Upload Policy Callback error: {0}")]
861    CallbackError(#[from] AnyError),
862}
863
864/// 生成上传凭证字符串结果
865pub type ToStringResult<T> = Result<T, ToStringError>;
866
867#[cfg(feature = "async")]
868type AsyncParseResult<'a, T> = Pin<Box<dyn Future<Output = ParseResult<T>> + 'a + Send>>;
869
870#[cfg(feature = "async")]
871type AsyncToStringResult<'a, T> = Pin<Box<dyn Future<Output = ToStringResult<T>> + 'a + Send>>;
872
873#[cfg(test)]
874mod tests {
875    use super::{super::UploadPolicyBuilder, *};
876    use async_std as _;
877    use qiniu_credential::Credential;
878    use std::{boxed::Box, error::Error, result::Result};
879    use structopt as _;
880
881    #[test]
882    fn test_build_upload_token_from_upload_policy() -> Result<(), Box<dyn Error>> {
883        let policy =
884            UploadPolicyBuilder::new_policy_for_object("test_bucket", "test:file", Duration::from_secs(3600)).build();
885        let provider = FromUploadPolicy::new(policy, get_credential());
886        let token = provider.to_token_string(Default::default())?;
887        assert!(token.starts_with(get_credential().get(Default::default())?.access_key().as_str()));
888        let token: StaticUploadTokenProvider = token.parse()?;
889        let policy = token.policy(Default::default())?;
890        assert_eq!(policy.bucket(), Some("test_bucket"));
891        assert_eq!(policy.key(), Some("test:file"));
892        Ok(())
893    }
894
895    #[test]
896    fn test_build_upload_token_for_bucket() -> Result<(), Box<dyn Error>> {
897        let provider = BucketUploadTokenProvider::builder("test_bucket", Duration::from_secs(3600), get_credential())
898            .on_policy_generated(|policy| {
899                policy.return_body("{\"key\":$(key)}");
900                Ok(())
901            })
902            .build();
903
904        let token = provider.to_token_string(Default::default())?;
905        assert!(token.starts_with(get_credential().get(Default::default())?.access_key().as_str()));
906
907        let policy = provider.policy(Default::default())?;
908        assert_eq!(policy.bucket(), Some("test_bucket"));
909        assert_eq!(policy.key(), None);
910        assert_eq!(policy.return_body(), Some("{\"key\":$(key)}"));
911
912        Ok(())
913    }
914
915    #[cfg(feature = "async")]
916    mod async_test {
917        use super::*;
918
919        #[async_std::test]
920        async fn test_async_build_upload_token_from_upload_policy() -> Result<(), Box<dyn Error>> {
921            let policy =
922                UploadPolicyBuilder::new_policy_for_object("test_bucket", "test:file", Duration::from_secs(3600))
923                    .build();
924            let provider = FromUploadPolicy::new(policy, get_credential());
925            let token = provider.async_to_token_string(Default::default()).await?;
926            assert!(token.starts_with(
927                get_credential()
928                    .async_get(Default::default())
929                    .await?
930                    .access_key()
931                    .as_str()
932            ));
933            let token: StaticUploadTokenProvider = token.parse()?;
934            let get_policy_from_size_options = Default::default();
935            let policy = token.async_policy(get_policy_from_size_options).await?;
936            assert_eq!(policy.bucket(), Some("test_bucket"));
937            assert_eq!(policy.key(), Some("test:file"));
938            Ok(())
939        }
940    }
941
942    fn get_credential() -> Credential {
943        Credential::new("abcdefghklmnopq", "1234567890")
944    }
945}