From 3965c77cfe0b6d29e2489601d940e5c89ead9d6b Mon Sep 17 00:00:00 2001 From: tu6ge Date: Thu, 13 Apr 2023 11:27:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E8=A7=A3=E5=86=B3=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=B9=8B=E9=97=B4=E7=9A=84=E4=BE=9D=E8=B5=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auth GenCanonicalizedResource 自己计算签名时,不依赖 type 模块的特殊类型 object bucket query 等 所有 feature 都加载 crate::types 模块,只有 core feature 才加载 crate::types::object 和 crate::types::core 模块 --- src/auth.rs | 53 +-- src/auth/test.rs | 50 ++- src/lib.rs | 15 +- src/traits.rs | 11 +- src/types.rs | 809 ++-------------------------------------------- src/types/core.rs | 778 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 873 insertions(+), 843 deletions(-) create mode 100644 src/types/core.rs diff --git a/src/auth.rs b/src/auth.rs index 07bb5c3..b4a7fad 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -23,11 +23,11 @@ use crate::{ types::{ - object::ObjectPathInner, CanonicalizedResource, ContentMd5, ContentType, Date, - InnerCanonicalizedResource, InnerContentMd5, InnerDate, InnerKeyId, InnerKeySecret, KeyId, - KeySecret, + CanonicalizedResource, ContentMd5, ContentType, Date, InnerCanonicalizedResource, + InnerContentMd5, InnerDate, InnerKeyId, InnerKeySecret, KeyId, KeySecret, + CONTINUATION_TOKEN, }, - BucketName, EndPoint, Query, + BucketName, EndPoint, }; use chrono::Utc; #[cfg(test)] @@ -38,8 +38,8 @@ use http::{ }; #[cfg(test)] use mockall::automock; -use std::convert::TryInto; use std::fmt::Display; +use std::{borrow::Cow, convert::TryInto}; #[cfg(test)] mod test; @@ -636,12 +636,12 @@ pub trait GenCanonicalizedResource { /// 根据 Url 的 query 计算 [`Query`] /// /// [`Query`]: crate::types::Query - fn oss_query(&self) -> Query; + fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource; /// 根据 Url 的 path 计算当前使用的 [`ObjectPathInner`] /// /// [`ObjectPathInner`]: crate::config::ObjectPathInner - fn object_path(&self) -> Option; + fn object_path(&self) -> Option>; } /// Oss 域名的几种状态 @@ -683,9 +683,9 @@ impl GenCanonicalizedResource for Url { Some(BUCKET_INFO), )), // 查 object_list - Some(q) if q.ends_with(LIST_TYPE2) || q.contains(LIST_TYPE2_AND) => Some( - CanonicalizedResource::from_bucket_query2(&bucket, &self.oss_query()), - ), + Some(q) if q.ends_with(LIST_TYPE2) || q.contains(LIST_TYPE2_AND) => { + Some(self.object_list_resource(&bucket)) + } // 其他情况待定 _ => todo!("Unable to obtain can information based on existing query information"), }; @@ -693,7 +693,7 @@ impl GenCanonicalizedResource for Url { // 获取 ObjectPath 失败,返回 None,否则根据 ObjectPath 计算 CanonicalizedResource self.object_path() - .map(|path| CanonicalizedResource::from_object((bucket.as_ref(), path.as_ref()), [])) + .map(|path| CanonicalizedResource::from_object_without_query(bucket.as_ref(), path)) } fn oss_host(&self) -> OssHost { @@ -730,22 +730,35 @@ impl GenCanonicalizedResource for Url { } } - fn oss_query(&self) -> Query { - self.query_pairs() - .filter(|(_, val)| !val.is_empty()) - .collect() + fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource { + let mut query = self.query_pairs().filter(|(_, val)| !val.is_empty()); + match query.find(|(key, _)| key == CONTINUATION_TOKEN) { + Some((_, token)) => CanonicalizedResource::new(format!( + "/{}/?continuation-token={}", + bucket.as_ref(), + token + )), + None => CanonicalizedResource::new(format!("/{}/", bucket.as_ref())), + } } - fn object_path(&self) -> Option { + fn object_path(&self) -> Option> { use percent_encoding::percent_decode; + if self.path().ends_with('/') { + return None; + } + let input = if self.path().starts_with('/') { &self.path()[1..] } else { self.path() } .as_bytes(); - ObjectPathInner::new(percent_decode(input).decode_utf8().ok()?).ok() + percent_decode(input) + .decode_utf8() + .ok() + .map(|str| str.to_owned()) } } @@ -758,11 +771,11 @@ impl GenCanonicalizedResource for Request { self.url().oss_host() } - fn oss_query(&self) -> Query { - self.url().oss_query() + fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource { + self.url().object_list_resource(bucket) } - fn object_path(&self) -> Option { + fn object_path(&self) -> Option> { self.url().object_path() } } diff --git a/src/auth/test.rs b/src/auth/test.rs index fecb6c0..db3b549 100644 --- a/src/auth/test.rs +++ b/src/auth/test.rs @@ -678,11 +678,7 @@ mod tests_canonicalized_resource { use http::Method; use reqwest::Url; - use crate::{ - auth::OssHost, - types::{object::ObjectPathInner, CanonicalizedResource}, - BucketName, - }; + use crate::{auth::OssHost, types::CanonicalizedResource, BucketName}; use super::super::GenCanonicalizedResource; @@ -760,41 +756,36 @@ mod tests_canonicalized_resource { } #[test] - fn test_oss_query() { - use crate::{QueryKey, QueryValue}; + fn test_object_list_resource() { let url: Url = "https://example.com/path1?delimiter=5".parse().unwrap(); - let query = url.oss_query(); - assert!(query[QueryKey::Delimiter] == QueryValue::new("5")); + let bucket = "abc".parse().unwrap(); + let resource = url.object_list_resource(&bucket); + assert!(resource == "/abc/"); + + let url: Url = "https://example.com/path1?continuation-token=bar&delimiter=5" + .parse() + .unwrap(); + let bucket = "abc".parse().unwrap(); + let resource = url.object_list_resource(&bucket); + assert!(resource == "/abc/?continuation-token=bar"); } #[test] fn test_object_path() { let url: Url = "https://example.com/path1".parse().unwrap(); - assert_eq!( - url.object_path(), - Some(ObjectPathInner::new("path1").unwrap()) - ); + assert_eq!(url.object_path().unwrap(), "path1"); let url: Url = "https://example.com/path1/object2".parse().unwrap(); - assert_eq!( - url.object_path(), - Some(ObjectPathInner::new("path1/object2").unwrap()) - ); + assert_eq!(url.object_path().unwrap(), "path1/object2"); let url: Url = "https://example.com/路径/object2".parse().unwrap(); - assert_eq!( - url.object_path(), - Some(ObjectPathInner::new("路径/object2").unwrap()) - ); + assert_eq!(url.object_path().unwrap(), "路径/object2"); let url: Url = "https://example.com/path1/object2?foo=bar".parse().unwrap(); - assert_eq!( - url.object_path(), - Some(ObjectPathInner::new("path1/object2").unwrap()) - ); + assert_eq!(url.object_path().unwrap(), "path1/object2"); let url: Url = "https://example.com/path1/".parse().unwrap(); - assert_eq!(url.object_path(), None); + assert!(url.object_path().is_none()); } #[test] @@ -813,7 +804,12 @@ mod tests_canonicalized_resource { request.url().canonicalized_resource(), ); assert_eq!(request.oss_host(), request.url().oss_host(),); - assert_eq!(request.oss_query(), request.url().oss_query(),); + + let bucket = "abc".parse().unwrap(); + assert_eq!( + request.object_list_resource(&bucket), + request.url().object_list_resource(&bucket), + ); assert_eq!(request.object_path(), request.url().object_path(),); } } diff --git a/src/lib.rs b/src/lib.rs index 2a34c80..874efe4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,23 +231,24 @@ let client = aliyun_oss_client::Client::new( extern crate test; /// 库内置类型的定义模块 -#[cfg(any(feature = "core", feature = "auth", test))] pub mod types; -#[cfg(any(feature = "core", test))] +#[cfg(feature = "core")] use builder::ClientWithMiddleware; -#[cfg(any(feature = "core", test))] +#[cfg(feature = "core")] use config::Config; /// 重新导出 http 库的一些方法,便于开发者调用 lib 未提供的 api -#[cfg(any(feature = "core", test))] +#[cfg(feature = "core")] pub use http::{ header::{HeaderMap, HeaderName, HeaderValue}, Method, }; -#[cfg(any(feature = "core", test))] + +#[cfg(feature = "core")] pub use types::object::{ObjectDir, ObjectPath}; -#[cfg(any(feature = "core", feature = "auth", test))] -pub use types::{BucketName, EndPoint, KeyId, KeySecret, Query, QueryKey, QueryValue}; +pub use types::{BucketName, EndPoint, KeyId, KeySecret}; +#[cfg(feature = "core")] +pub use types::{Query, QueryKey, QueryValue}; /// # 验证模块 /// 包含了签名验证的一些方法,header 以及参数的封装 diff --git a/src/traits.rs b/src/traits.rs index 7e25ec4..23754cd 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -98,6 +98,7 @@ use std::num::ParseIntError; use quick_xml::{events::Event, Reader}; +use crate::types::InvalidEndPoint; #[cfg(feature = "core")] use crate::{ errors::OssError, @@ -387,6 +388,8 @@ pub trait ListError: StdError + 'static {} impl ListError for ParseIntError {} +impl ListError for InvalidEndPoint {} + #[cfg(feature = "core")] impl ListError for InvalidObjectPath {} #[cfg(feature = "core")] @@ -438,6 +441,7 @@ impl From for InnerListError { impl InnerListError { #[cfg(test)] + #[allow(dead_code)] pub(crate) fn from_xml() -> Self { Self { kind: ListErrorKind::Xml(quick_xml::Error::TextNotFound), @@ -801,9 +805,12 @@ mod tests { #[test] fn test_list_from() { - let string = InvalidObjectPath { _priv: () }; + let string = InvalidEndPoint { _priv: () }; let err: InnerListError = string.into(); - assert_eq!(format!("{err}"), "invalid object path"); + assert_eq!( + format!("{err}"), + "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" + ); } #[test] diff --git a/src/types.rs b/src/types.rs index cbce237..9dc88af 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,17 +1,23 @@ use std::borrow::Cow; -use std::collections::HashMap; + use std::error::Error; use std::fmt::{self, Debug, Display, Formatter}; use std::str::FromStr; use chrono::{DateTime, Utc}; use http::header::{HeaderValue, InvalidHeaderValue, ToStrError}; -#[cfg(any(feature = "core", feature = "auth", test))] use url::Url; /// object 相关的类型 +#[cfg(feature = "core")] pub mod object; +/// 核心功能用到的类型 Query ContentRange 等 +#[cfg(feature = "core")] +pub mod core; +#[cfg(feature = "core")] +pub use self::core::{ContentRange, Query, QueryKey, QueryValue, UrlQuery}; + /// 阿里云 OSS 的签名 key #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct InnerKeyId<'a>(Cow<'a, str>); @@ -131,7 +137,6 @@ impl<'a> InnerKeySecret<'a> { /// # OSS 的可用区 /// [aliyun docs](https://help.aliyun.com/document_detail/31837.htm) -#[cfg(any(feature = "core", feature = "auth", test))] #[derive(Clone, Debug, PartialEq, Eq, Default)] #[non_exhaustive] pub enum EndPoint { @@ -171,7 +176,6 @@ const US_WEST1: &str = "us-west-1"; const US_EAST1: &str = "us-east-1"; const AP_SOUTH_EAST1: &str = "ap-southeast-1"; -#[cfg(any(feature = "core", feature = "auth", test))] impl AsRef for EndPoint { /// ``` /// # use aliyun_oss_client::types::EndPoint::*; @@ -205,7 +209,6 @@ impl AsRef for EndPoint { } } -#[cfg(any(feature = "core", feature = "auth", test))] impl Display for EndPoint { /// ``` /// # use aliyun_oss_client::types::EndPoint::*; @@ -224,7 +227,6 @@ const ZHANGJIAKOU_L: &str = "zhangjiakou"; const HONGKONG_L: &str = "hongkong"; const SHENZHEN_L: &str = "shenzhen"; -#[cfg(any(feature = "core", feature = "auth", test))] impl TryFrom for EndPoint { type Error = InvalidEndPoint; /// 字符串转 endpoint @@ -240,7 +242,6 @@ impl TryFrom for EndPoint { } } -#[cfg(any(feature = "core", feature = "auth", test))] impl<'a> TryFrom<&'a str> for EndPoint { type Error = InvalidEndPoint; /// 字符串字面量转 endpoint @@ -256,7 +257,6 @@ impl<'a> TryFrom<&'a str> for EndPoint { } } -#[cfg(any(feature = "core", feature = "auth", test))] impl FromStr for EndPoint { type Err = InvalidEndPoint; fn from_str(url: &str) -> Result { @@ -269,7 +269,6 @@ const OSS_DOMAIN_PREFIX: &str = "https://oss-"; const OSS_INTERNAL: &str = "-internal"; const OSS_DOMAIN_MAIN: &str = ".aliyuncs.com"; -#[cfg(any(feature = "core", feature = "auth", test))] impl<'a> EndPoint { /// 通过字符串字面值初始化 endpoint /// @@ -404,7 +403,7 @@ impl<'a> EndPoint { } } -#[cfg(all(test, any(feature = "core", feature = "auth")))] +#[cfg(test)] mod test_endpoint { use super::*; @@ -948,7 +947,7 @@ impl Default for InnerCanonicalizedResource<'_> { } } -#[cfg(feature = "core")] +#[cfg(any(feature = "core", feature = "auth"))] pub(crate) const CONTINUATION_TOKEN: &str = "continuation-token"; #[cfg(any(feature = "core", feature = "auth"))] pub(crate) const BUCKET_INFO: &str = "bucketInfo"; @@ -1006,13 +1005,13 @@ impl<'a> InnerCanonicalizedResource<'a> { /// 带查询条件的 /// /// 如果查询条件中有翻页的话,则忽略掉其他字段 - #[cfg(any(feature = "core", feature = "auth"))] + #[cfg(feature = "core")] #[inline] pub fn from_bucket_query>(bucket: B, query: &Query) -> Self { Self::from_bucket_query2(bucket.as_ref(), query) } - #[cfg(any(feature = "core", feature = "auth"))] + #[cfg(feature = "core")] #[doc(hidden)] pub fn from_bucket_query2(bucket: &BucketName, query: &Query) -> Self { match query.get(QueryKey::ContinuationToken) { @@ -1026,7 +1025,7 @@ impl<'a> InnerCanonicalizedResource<'a> { } /// 根据 OSS 存储对象(Object)查询签名参数 - #[cfg(any(feature = "core", feature = "auth"))] + #[cfg(any(feature = "core"))] pub(crate) fn from_object< Q: IntoIterator, B: AsRef, @@ -1047,6 +1046,14 @@ impl<'a> InnerCanonicalizedResource<'a> { )) } } + + #[cfg(any(feature = "auth"))] + pub(crate) fn from_object_without_query, P: AsRef>( + bucket: B, + path: P, + ) -> Self { + Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref())) + } } impl PartialEq<&str> for InnerCanonicalizedResource<'_> { @@ -1116,7 +1123,7 @@ mod tests_canonicalized_resource { ); } - #[cfg(any(feature = "core", feature = "auth"))] + #[cfg(feature = "core")] #[test] fn test_from_object() { use super::CanonicalizedResource; @@ -1129,775 +1136,3 @@ mod tests_canonicalized_resource { assert_eq!(resource, "/foo/bar?foo2=bar2"); } } - -//=================================================================================================== -/// 查询条件 -/// -/// ``` -/// use aliyun_oss_client::types::Query; -/// -/// let query: Query = [("abc", "def")].into_iter().collect(); -/// assert_eq!(query.len(), 1); -/// -/// let value = query.get("abc"); -/// assert!(value.is_some()); -/// let value = value.unwrap(); -/// assert_eq!(value.as_ref(), "def"); -/// -/// let str = query.to_oss_string(); -/// assert_eq!(str.as_str(), "list-type=2&abc=def"); -/// let str = query.to_url_query(); -/// assert_eq!(str.as_str(), "abc=def"); -/// ``` -#[derive(Clone, Debug, Default)] -pub struct Query { - inner: HashMap, -} - -impl AsMut> for Query { - fn as_mut(&mut self) -> &mut HashMap { - &mut self.inner - } -} - -impl AsRef> for Query { - fn as_ref(&self) -> &HashMap { - &self.inner - } -} - -impl Query { - /// Creates an empty `Query`. - /// - /// The hash map is initially created with a capacity of 0, so it will not allocate until it - /// is first inserted into. - pub fn new() -> Self { - Self { - inner: HashMap::new(), - } - } - - /// Creates an empty `Query` with at least the specified capacity. - pub fn with_capacity(capacity: usize) -> Self { - Self { - inner: HashMap::with_capacity(capacity), - } - } - - /// Inserts a key-value pair into the map. - pub fn insert( - &mut self, - key: impl Into, - value: impl Into, - ) -> Option { - self.as_mut().insert(key.into(), value.into()) - } - - /// Returns a reference to the value corresponding to the key. - pub fn get(&self, key: impl Into) -> Option<&QueryValue> { - self.as_ref().get(&key.into()) - } - - /// Returns the number of elements in the map. - pub fn len(&self) -> usize { - self.as_ref().len() - } - - /// Returns `true` if the map contains no elements. - pub fn is_empty(&self) -> bool { - self.as_ref().is_empty() - } - - /// Removes a key from the map, returning the value at the key if the key - /// was previously in the map. - pub fn remove(&mut self, key: impl Into) -> Option { - self.as_mut().remove(&key.into()) - } - - /// 将查询参数拼成 aliyun 接口需要的格式 - pub fn to_oss_string(&self) -> String { - let mut query_str = String::from("list-type=2"); - for (key, value) in self.as_ref().iter() { - query_str += "&"; - query_str += key.as_ref(); - query_str += "="; - query_str += value.as_ref(); - } - query_str - } - - /// 转化成 url 参数的形式 - /// a=foo&b=bar - pub fn to_url_query(&self) -> String { - self.as_ref() - .iter() - .map(|(k, v)| { - let mut res = String::with_capacity(k.as_ref().len() + v.as_ref().len() + 1); - res.push_str(k.as_ref()); - res.push('='); - res.push_str(v.as_ref()); - res - }) - .collect::>() - .join("&") - } -} - -impl Index for Query { - type Output = QueryValue; - - fn index(&self, index: QueryKey) -> &Self::Output { - self.get(index).expect("no found query key") - } -} - -impl IntoIterator for Query { - type Item = (QueryKey, QueryValue); - type IntoIter = std::vec::IntoIter; - /// # 使用 Vec 转 Query - /// 例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// # use aliyun_oss_client::QueryValue; - /// # use assert_matches::assert_matches; - /// let query = Query::from_iter(vec![("foo", "bar")]); - /// let list: Vec<_> = query.into_iter().collect(); - /// assert_eq!(list.len(), 1); - /// assert_matches!(&list[0].0, &QueryKey::Custom(_)); - /// let value: QueryValue = "bar".parse().unwrap(); - /// assert_eq!(list[0].1, value); - /// ``` - fn into_iter(self) -> Self::IntoIter { - self.inner.into_iter().collect::>().into_iter() - } -} - -impl FromIterator<(QueryKey, QueryValue)> for Query { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let mut map = Query::default(); - map.as_mut().extend(iter); - map - } -} - -impl<'a> FromIterator<(&'a str, &'a str)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query: Query = [("max-keys", "123")].into_iter().collect(); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter.into_iter().map(|(k, v)| { - ( - k.parse().expect("invalid QueryKey"), - v.parse().expect("invalid QueryValue"), - ) - }); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(Cow<'a, str>, Cow<'a, str>)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query: Query = [("max-keys", "123")].into_iter().collect(); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, Cow<'a, str>)>, - { - let inner = iter.into_iter().map(|(k, v)| { - ( - k.as_ref().parse().expect("invalid QueryKey"), - v.as_ref().parse().expect("invalid QueryValue"), - ) - }); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(&'a str, u8)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([("max-keys", 123u8)]); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter - .into_iter() - .map(|(k, v)| (k.parse().expect("invalid QueryKey"), v.into())); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(&'a str, u16)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([("max-keys", 123u16)]); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter - .into_iter() - .map(|(k, v)| (k.parse().expect("invalid QueryKey"), v.into())); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(QueryKey, &'a str)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([(QueryKey::MaxKeys, "123")]); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter - .into_iter() - .map(|(k, v)| (k, v.parse().expect("invalid QueryValue"))); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -macro_rules! impl_from_iter { - ($key:ty, $val:ty, $convert:expr) => { - impl FromIterator<($key, $val)> for Query { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter.into_iter().map($convert); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } - } - }; -} - -impl_from_iter!(QueryKey, u8, |(k, v)| (k, v.into())); -impl_from_iter!(QueryKey, u16, |(k, v)| (k, v.into())); - -#[cfg(test)] -mod tests_query_from_iter { - use super::*; - #[test] - fn test() { - let query = Query::from_iter([(QueryKey::MaxKeys, 123u8)]); - assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - - let query = Query::from_iter([(QueryKey::MaxKeys, 123u16)]); - assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); - assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); - } -} - -impl PartialEq for Query { - fn eq(&self, other: &Query) -> bool { - self.as_ref() == other.as_ref() - } -} - -/// 为 Url 拼接 [`Query`] 数据 -/// [`Query`]: crate::types::Query -#[cfg(feature = "core")] -pub trait UrlQuery { - /// 给 Url 结构体增加 `set_search_query` 方法 - fn set_search_query(&mut self, query: &Query); -} - -#[cfg(feature = "core")] -impl UrlQuery for Url { - /// 将查询参数拼接到 API 的 Url 上 - /// - /// # 例子 - /// ``` - /// use aliyun_oss_client::types::Query; - /// use aliyun_oss_client::types::UrlQuery; - /// use reqwest::Url; - /// - /// let query = Query::from_iter([("abc", "def")]); - /// let mut url = Url::parse("https://exapmle.com").unwrap(); - /// url.set_search_query(&query); - /// assert_eq!(url.as_str(), "https://exapmle.com/?list-type=2&abc=def"); - /// assert_eq!(url.query(), Some("list-type=2&abc=def")); - /// ``` - fn set_search_query(&mut self, query: &Query) { - self.set_query(Some(&query.to_oss_string())); - } -} - -/// 查询条件的键 -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum InnerQueryKey<'a> { - /// 对Object名字进行分组的字符。所有Object名字包含指定的前缀,第一次出现delimiter字符之间的Object作为一组元素(即CommonPrefixes) - /// 示例值 `/` - Delimiter, - - /// 设定从start-after之后按字母排序开始返回Object。 - /// start-after用来实现分页显示效果,参数的长度必须小于1024字节。 - /// 做条件查询时,即使start-after在列表中不存在,也会从符合start-after字母排序的下一个开始打印。 - StartAfter, - - /// 指定List操作需要从此token开始。您可从ListObjectsV2(GetBucketV2)结果中的NextContinuationToken获取此token。 - /// 用于分页,返回下一页的数据 - ContinuationToken, - - /// 指定返回Object的最大数。 - /// 取值:大于0小于等于1000 - MaxKeys, - - /// # 限定返回文件的Key必须以prefix作为前缀。 - /// 如果把prefix设为某个文件夹名,则列举以此prefix开头的文件,即该文件夹下递归的所有文件和子文件夹。 - /// - /// 在设置prefix的基础上,将delimiter设置为正斜线(/)时,返回值就只列举该文件夹下的文件,文件夹下的子文件夹名返回在CommonPrefixes中, - /// 子文件夹下递归的所有文件和文件夹不显示。 - /// - /// 例如,一个Bucket中有三个Object,分别为fun/test.jpg、fun/movie/001.avi和fun/movie/007.avi。如果设定prefix为fun/, - /// 则返回三个Object;如果在prefix设置为fun/的基础上,将delimiter设置为正斜线(/),则返回fun/test.jpg和fun/movie/。 - /// ## 要求 - /// - 参数的长度必须小于1024字节。 - /// - 设置prefix参数时,不能以正斜线(/)开头。如果prefix参数置空,则默认列举Bucket内的所有Object。 - /// - 使用prefix查询时,返回的Key中仍会包含prefix。 - Prefix, - - /// 对返回的内容进行编码并指定编码的类型。 - EncodingType, - - /// 指定是否在返回结果中包含owner信息。 - FetchOwner, - - /// 自定义 - Custom(Cow<'a, str>), -} - -/// 查询条件的键 -pub type QueryKey = InnerQueryKey<'static>; - -impl AsRef for InnerQueryKey<'_> { - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// # use std::borrow::Cow; - /// assert_eq!(QueryKey::Delimiter.as_ref(), "delimiter"); - /// assert_eq!(QueryKey::StartAfter.as_ref(), "start-after"); - /// assert_eq!(QueryKey::ContinuationToken.as_ref(), "continuation-token"); - /// assert_eq!(QueryKey::MaxKeys.as_ref(), "max-keys"); - /// assert_eq!(QueryKey::Prefix.as_ref(), "prefix"); - /// assert_eq!(QueryKey::EncodingType.as_ref(), "encoding-type"); - /// assert_eq!(QueryKey::Custom(Cow::Borrowed("abc")).as_ref(), "abc"); - /// ``` - fn as_ref(&self) -> &str { - use InnerQueryKey::*; - - match self { - Delimiter => "delimiter", - StartAfter => "start-after", - ContinuationToken => "continuation-token", - MaxKeys => "max-keys", - Prefix => "prefix", - EncodingType => "encoding-type", - // TODO - FetchOwner => unimplemented!("parse xml not support fetch owner"), - Custom(str) => str.as_ref(), - } - } -} - -impl Display for InnerQueryKey<'_> { - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// assert_eq!(format!("{}", QueryKey::Delimiter), "delimiter"); - /// ``` - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_ref()) - } -} - -impl From for InnerQueryKey<'_> { - fn from(s: String) -> Self { - Self::new(s) - } -} -impl<'a: 'b, 'b> From<&'a str> for InnerQueryKey<'b> { - fn from(date: &'a str) -> Self { - Self::new(date) - } -} - -impl FromStr for QueryKey { - type Err = InvalidQueryKey; - /// 示例 - /// ``` - /// # use aliyun_oss_client::types::QueryKey; - /// let value: QueryKey = "abc".into(); - /// assert!(value == QueryKey::from_static("abc")); - /// ``` - fn from_str(s: &str) -> Result { - Ok(Self::from_static(s)) - } -} - -/// 异常的查询条件键 -#[derive(Debug)] -pub struct InvalidQueryKey { - _priv: (), -} - -impl Error for InvalidQueryKey {} - -impl Display for InvalidQueryKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid query key") - } -} - -impl<'a> InnerQueryKey<'a> { - /// # Examples - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// # use assert_matches::assert_matches; - /// let key = QueryKey::new("delimiter"); - /// assert!(key == QueryKey::Delimiter); - /// assert!(QueryKey::new("start-after") == QueryKey::StartAfter); - /// assert!(QueryKey::new("continuation-token") == QueryKey::ContinuationToken); - /// assert!(QueryKey::new("max-keys") == QueryKey::MaxKeys); - /// assert!(QueryKey::new("prefix") == QueryKey::Prefix); - /// assert!(QueryKey::new("encoding-type") == QueryKey::EncodingType); - /// - /// let key = QueryKey::new("abc"); - /// assert_matches!(key, QueryKey::Custom(_)); - /// ``` - /// *`fetch-owner` 功能未实现,特殊说明* - pub fn new(val: impl Into>) -> Self { - use InnerQueryKey::*; - - let val = val.into(); - if val.contains("delimiter") { - Delimiter - } else if val.contains("start-after") { - StartAfter - } else if val.contains("continuation-token") { - ContinuationToken - } else if val.contains("max-keys") { - MaxKeys - } else if val.contains("prefix") { - Prefix - } else if val.contains("encoding-type") { - EncodingType - } else if val.contains("fetch-owner") { - unimplemented!("parse xml not support fetch owner"); - } else { - Custom(val) - } - } - - /// # Examples - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// # use assert_matches::assert_matches; - /// let key = QueryKey::from_static("delimiter"); - /// assert!(key == QueryKey::Delimiter); - /// assert!(QueryKey::from_static("start-after") == QueryKey::StartAfter); - /// assert!(QueryKey::from_static("continuation-token") == QueryKey::ContinuationToken); - /// assert!(QueryKey::from_static("max-keys") == QueryKey::MaxKeys); - /// assert!(QueryKey::from_static("prefix") == QueryKey::Prefix); - /// assert!(QueryKey::from_static("encoding-type") == QueryKey::EncodingType); - /// - /// let key = QueryKey::from_static("abc"); - /// assert_matches!(key, QueryKey::Custom(_)); - /// ``` - /// *`fetch-owner` 功能未实现,特殊说明* - pub fn from_static(val: &str) -> Self { - use InnerQueryKey::*; - - if val.contains("delimiter") { - Delimiter - } else if val.contains("start-after") { - StartAfter - } else if val.contains("continuation-token") { - ContinuationToken - } else if val.contains("max-keys") { - MaxKeys - } else if val.contains("prefix") { - Prefix - } else if val.contains("encoding-type") { - EncodingType - } else if val.contains("fetch-owner") { - unimplemented!("parse xml not support fetch owner"); - } else { - Custom(Cow::Owned(val.to_owned())) - } - } -} - -#[cfg(test)] -mod test_query_key { - use super::*; - - #[test] - #[should_panic] - fn test_fetch_owner() { - QueryKey::new("fetch-owner"); - } -} - -/// 查询条件的值 -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct InnerQueryValue<'a>(Cow<'a, str>); -/// 查询条件的值 -pub type QueryValue = InnerQueryValue<'static>; - -impl AsRef for InnerQueryValue<'_> { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Display for InnerQueryValue<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for InnerQueryValue<'_> { - fn from(s: String) -> Self { - Self(Cow::Owned(s)) - } -} -impl<'a: 'b, 'b> From<&'a str> for InnerQueryValue<'b> { - fn from(date: &'a str) -> Self { - Self::new(date) - } -} - -impl PartialEq<&str> for InnerQueryValue<'_> { - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.0 == other - } -} - -impl From for InnerQueryValue<'_> { - /// 数字转 Query 值 - /// - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([("max_keys", 100u8)]); - /// let query = Query::from_iter([(QueryKey::MaxKeys, 100u8)]); - /// ``` - fn from(num: u8) -> Self { - Self(Cow::Owned(num.to_string())) - } -} - -impl PartialEq for InnerQueryValue<'_> { - #[inline] - fn eq(&self, other: &u8) -> bool { - self.to_string() == other.to_string() - } -} - -impl From for InnerQueryValue<'_> { - /// 数字转 Query 值 - /// - /// ``` - /// use aliyun_oss_client::Query; - /// let query = Query::from_iter([("max_keys", 100u16)]); - /// ``` - fn from(num: u16) -> Self { - Self(Cow::Owned(num.to_string())) - } -} - -impl PartialEq for InnerQueryValue<'_> { - #[inline] - fn eq(&self, other: &u16) -> bool { - self.to_string() == other.to_string() - } -} - -impl From for QueryValue { - /// bool 转 Query 值 - /// - /// ``` - /// use aliyun_oss_client::Query; - /// let query = Query::from_iter([("abc", "false")]); - /// ``` - fn from(b: bool) -> Self { - if b { - Self::from_static("true") - } else { - Self::from_static("false") - } - } -} - -impl FromStr for InnerQueryValue<'_> { - type Err = InvalidQueryValue; - /// 示例 - /// ``` - /// # use aliyun_oss_client::types::QueryValue; - /// let value: QueryValue = "abc".parse().unwrap(); - /// assert!(value == "abc"); - /// ``` - fn from_str(s: &str) -> Result { - Ok(Self::from_static2(s)) - } -} - -/// 异常的查询值 -#[derive(Debug)] -pub struct InvalidQueryValue { - _priv: (), -} - -impl Error for InvalidQueryValue {} - -impl Display for InvalidQueryValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid query value") - } -} - -impl<'a> InnerQueryValue<'a> { - /// Creates a new `QueryValue` from the given string. - pub fn new(val: impl Into>) -> Self { - Self(val.into()) - } - - /// Const function that creates a new `QueryValue` from a static str. - pub const fn from_static(val: &'static str) -> Self { - Self(Cow::Borrowed(val)) - } - - /// Const function that creates a new `QueryValue` from a static str. - pub fn from_static2(val: &str) -> Self { - Self(Cow::Owned(val.to_owned())) - } -} - -use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; - -/// 用于指定返回内容的区域的 type -pub struct ContentRange { - start: Option, - end: Option, -} - -impl From> for ContentRange { - fn from(r: Range) -> Self { - Self { - start: Some(r.start), - end: Some(r.end), - } - } -} - -impl From for ContentRange { - fn from(_f: RangeFull) -> Self { - Self { - start: None, - end: None, - } - } -} - -impl From> for ContentRange { - fn from(f: RangeFrom) -> Self { - Self { - start: Some(f.start), - end: None, - } - } -} - -impl From> for ContentRange { - fn from(t: RangeTo) -> Self { - Self { - start: None, - end: Some(t.end), - } - } -} - -impl From for HeaderValue { - /// # 转化成 OSS 需要的格式 - /// @link [OSS 文档](https://help.aliyun.com/document_detail/31980.html) - /// - /// ``` - /// use reqwest::header::HeaderValue; - /// # use aliyun_oss_client::types::ContentRange; - /// fn abc>(range: R) -> HeaderValue { - /// range.into().into() - /// } - /// - /// assert_eq!(abc(..), HeaderValue::from_str("bytes=0-").unwrap()); - /// assert_eq!(abc(1..), HeaderValue::from_str("bytes=1-").unwrap()); - /// assert_eq!(abc(10..20), HeaderValue::from_str("bytes=10-20").unwrap()); - /// assert_eq!(abc(..20), HeaderValue::from_str("bytes=0-20").unwrap()); - /// ``` - fn from(con: ContentRange) -> HeaderValue { - let string = match (con.start, con.end) { - (Some(ref start), Some(ref end)) => format!("bytes={}-{}", start, end), - (Some(ref start), None) => format!("bytes={}-", start), - (None, Some(ref end)) => format!("bytes=0-{}", end), - (None, None) => "bytes=0-".to_string(), - }; - - HeaderValue::from_str(&string).unwrap_or_else(|_| { - panic!( - "content-range into header-value failed, content-range is : {}", - string - ) - }) - } -} diff --git a/src/types/core.rs b/src/types/core.rs new file mode 100644 index 0000000..5c12338 --- /dev/null +++ b/src/types/core.rs @@ -0,0 +1,778 @@ +use std::borrow::Cow; +use std::collections::HashMap; + +//=================================================================================================== +/// 查询条件 +/// +/// ``` +/// use aliyun_oss_client::types::Query; +/// +/// let query: Query = [("abc", "def")].into_iter().collect(); +/// assert_eq!(query.len(), 1); +/// +/// let value = query.get("abc"); +/// assert!(value.is_some()); +/// let value = value.unwrap(); +/// assert_eq!(value.as_ref(), "def"); +/// +/// let str = query.to_oss_string(); +/// assert_eq!(str.as_str(), "list-type=2&abc=def"); +/// let str = query.to_url_query(); +/// assert_eq!(str.as_str(), "abc=def"); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct Query { + inner: HashMap, +} + +impl AsMut> for Query { + fn as_mut(&mut self) -> &mut HashMap { + &mut self.inner + } +} + +impl AsRef> for Query { + fn as_ref(&self) -> &HashMap { + &self.inner + } +} + +impl Query { + /// Creates an empty `Query`. + /// + /// The hash map is initially created with a capacity of 0, so it will not allocate until it + /// is first inserted into. + pub fn new() -> Self { + Self { + inner: HashMap::new(), + } + } + + /// Creates an empty `Query` with at least the specified capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + inner: HashMap::with_capacity(capacity), + } + } + + /// Inserts a key-value pair into the map. + pub fn insert( + &mut self, + key: impl Into, + value: impl Into, + ) -> Option { + self.as_mut().insert(key.into(), value.into()) + } + + /// Returns a reference to the value corresponding to the key. + pub fn get(&self, key: impl Into) -> Option<&QueryValue> { + self.as_ref().get(&key.into()) + } + + /// Returns the number of elements in the map. + pub fn len(&self) -> usize { + self.as_ref().len() + } + + /// Returns `true` if the map contains no elements. + pub fn is_empty(&self) -> bool { + self.as_ref().is_empty() + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + pub fn remove(&mut self, key: impl Into) -> Option { + self.as_mut().remove(&key.into()) + } + + /// 将查询参数拼成 aliyun 接口需要的格式 + pub fn to_oss_string(&self) -> String { + let mut query_str = String::from("list-type=2"); + for (key, value) in self.as_ref().iter() { + query_str += "&"; + query_str += key.as_ref(); + query_str += "="; + query_str += value.as_ref(); + } + query_str + } + + /// 转化成 url 参数的形式 + /// a=foo&b=bar + pub fn to_url_query(&self) -> String { + self.as_ref() + .iter() + .map(|(k, v)| { + let mut res = String::with_capacity(k.as_ref().len() + v.as_ref().len() + 1); + res.push_str(k.as_ref()); + res.push('='); + res.push_str(v.as_ref()); + res + }) + .collect::>() + .join("&") + } +} + +impl Index for Query { + type Output = QueryValue; + + fn index(&self, index: QueryKey) -> &Self::Output { + self.get(index).expect("no found query key") + } +} + +impl IntoIterator for Query { + type Item = (QueryKey, QueryValue); + type IntoIter = std::vec::IntoIter; + /// # 使用 Vec 转 Query + /// 例子 + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// # use aliyun_oss_client::QueryValue; + /// # use assert_matches::assert_matches; + /// let query = Query::from_iter(vec![("foo", "bar")]); + /// let list: Vec<_> = query.into_iter().collect(); + /// assert_eq!(list.len(), 1); + /// assert_matches!(&list[0].0, &QueryKey::Custom(_)); + /// let value: QueryValue = "bar".parse().unwrap(); + /// assert_eq!(list[0].1, value); + /// ``` + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter().collect::>().into_iter() + } +} + +impl FromIterator<(QueryKey, QueryValue)> for Query { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut map = Query::default(); + map.as_mut().extend(iter); + map + } +} + +impl<'a> FromIterator<(&'a str, &'a str)> for Query { + /// 转化例子 + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// let query: Query = [("max-keys", "123")].into_iter().collect(); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + /// ``` + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let inner = iter.into_iter().map(|(k, v)| { + ( + k.parse().expect("invalid QueryKey"), + v.parse().expect("invalid QueryValue"), + ) + }); + + let mut map = Query::default(); + map.as_mut().extend(inner); + map + } +} + +impl<'a> FromIterator<(Cow<'a, str>, Cow<'a, str>)> for Query { + /// 转化例子 + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// let query: Query = [("max-keys", "123")].into_iter().collect(); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + /// ``` + fn from_iter(iter: I) -> Self + where + I: IntoIterator, Cow<'a, str>)>, + { + let inner = iter.into_iter().map(|(k, v)| { + ( + k.as_ref().parse().expect("invalid QueryKey"), + v.as_ref().parse().expect("invalid QueryValue"), + ) + }); + + let mut map = Query::default(); + map.as_mut().extend(inner); + map + } +} + +impl<'a> FromIterator<(&'a str, u8)> for Query { + /// 转化例子 + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// let query = Query::from_iter([("max-keys", 123u8)]); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + /// ``` + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let inner = iter + .into_iter() + .map(|(k, v)| (k.parse().expect("invalid QueryKey"), v.into())); + + let mut map = Query::default(); + map.as_mut().extend(inner); + map + } +} + +impl<'a> FromIterator<(&'a str, u16)> for Query { + /// 转化例子 + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// let query = Query::from_iter([("max-keys", 123u16)]); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + /// ``` + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let inner = iter + .into_iter() + .map(|(k, v)| (k.parse().expect("invalid QueryKey"), v.into())); + + let mut map = Query::default(); + map.as_mut().extend(inner); + map + } +} + +impl<'a> FromIterator<(QueryKey, &'a str)> for Query { + /// 转化例子 + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// let query = Query::from_iter([(QueryKey::MaxKeys, "123")]); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + /// assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + /// ``` + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let inner = iter + .into_iter() + .map(|(k, v)| (k, v.parse().expect("invalid QueryValue"))); + + let mut map = Query::default(); + map.as_mut().extend(inner); + map + } +} + +macro_rules! impl_from_iter { + ($key:ty, $val:ty, $convert:expr) => { + impl FromIterator<($key, $val)> for Query { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let inner = iter.into_iter().map($convert); + + let mut map = Query::default(); + map.as_mut().extend(inner); + map + } + } + }; +} + +impl_from_iter!(QueryKey, u8, |(k, v)| (k, v.into())); +impl_from_iter!(QueryKey, u16, |(k, v)| (k, v.into())); + +#[cfg(test)] +mod tests_query_from_iter { + use super::*; + #[test] + fn test() { + let query = Query::from_iter([(QueryKey::MaxKeys, 123u8)]); + assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + + let query = Query::from_iter([(QueryKey::MaxKeys, 123u16)]); + assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u8.into())); + assert_eq!(query.get(QueryKey::MaxKeys), Some(&123u16.into())); + } +} + +impl PartialEq for Query { + fn eq(&self, other: &Query) -> bool { + self.as_ref() == other.as_ref() + } +} + +/// 为 Url 拼接 [`Query`] 数据 +/// [`Query`]: crate::types::Query +pub trait UrlQuery { + /// 给 Url 结构体增加 `set_search_query` 方法 + fn set_search_query(&mut self, query: &Query); +} + +impl UrlQuery for Url { + /// 将查询参数拼接到 API 的 Url 上 + /// + /// # 例子 + /// ``` + /// use aliyun_oss_client::types::Query; + /// use aliyun_oss_client::types::UrlQuery; + /// use reqwest::Url; + /// + /// let query = Query::from_iter([("abc", "def")]); + /// let mut url = Url::parse("https://exapmle.com").unwrap(); + /// url.set_search_query(&query); + /// assert_eq!(url.as_str(), "https://exapmle.com/?list-type=2&abc=def"); + /// assert_eq!(url.query(), Some("list-type=2&abc=def")); + /// ``` + fn set_search_query(&mut self, query: &Query) { + self.set_query(Some(&query.to_oss_string())); + } +} + +/// 查询条件的键 +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum InnerQueryKey<'a> { + /// 对Object名字进行分组的字符。所有Object名字包含指定的前缀,第一次出现delimiter字符之间的Object作为一组元素(即CommonPrefixes) + /// 示例值 `/` + Delimiter, + + /// 设定从start-after之后按字母排序开始返回Object。 + /// start-after用来实现分页显示效果,参数的长度必须小于1024字节。 + /// 做条件查询时,即使start-after在列表中不存在,也会从符合start-after字母排序的下一个开始打印。 + StartAfter, + + /// 指定List操作需要从此token开始。您可从ListObjectsV2(GetBucketV2)结果中的NextContinuationToken获取此token。 + /// 用于分页,返回下一页的数据 + ContinuationToken, + + /// 指定返回Object的最大数。 + /// 取值:大于0小于等于1000 + MaxKeys, + + /// # 限定返回文件的Key必须以prefix作为前缀。 + /// 如果把prefix设为某个文件夹名,则列举以此prefix开头的文件,即该文件夹下递归的所有文件和子文件夹。 + /// + /// 在设置prefix的基础上,将delimiter设置为正斜线(/)时,返回值就只列举该文件夹下的文件,文件夹下的子文件夹名返回在CommonPrefixes中, + /// 子文件夹下递归的所有文件和文件夹不显示。 + /// + /// 例如,一个Bucket中有三个Object,分别为fun/test.jpg、fun/movie/001.avi和fun/movie/007.avi。如果设定prefix为fun/, + /// 则返回三个Object;如果在prefix设置为fun/的基础上,将delimiter设置为正斜线(/),则返回fun/test.jpg和fun/movie/。 + /// ## 要求 + /// - 参数的长度必须小于1024字节。 + /// - 设置prefix参数时,不能以正斜线(/)开头。如果prefix参数置空,则默认列举Bucket内的所有Object。 + /// - 使用prefix查询时,返回的Key中仍会包含prefix。 + Prefix, + + /// 对返回的内容进行编码并指定编码的类型。 + EncodingType, + + /// 指定是否在返回结果中包含owner信息。 + FetchOwner, + + /// 自定义 + Custom(Cow<'a, str>), +} + +/// 查询条件的键 +pub type QueryKey = InnerQueryKey<'static>; + +impl AsRef for InnerQueryKey<'_> { + /// ``` + /// # use aliyun_oss_client::QueryKey; + /// # use std::borrow::Cow; + /// assert_eq!(QueryKey::Delimiter.as_ref(), "delimiter"); + /// assert_eq!(QueryKey::StartAfter.as_ref(), "start-after"); + /// assert_eq!(QueryKey::ContinuationToken.as_ref(), "continuation-token"); + /// assert_eq!(QueryKey::MaxKeys.as_ref(), "max-keys"); + /// assert_eq!(QueryKey::Prefix.as_ref(), "prefix"); + /// assert_eq!(QueryKey::EncodingType.as_ref(), "encoding-type"); + /// assert_eq!(QueryKey::Custom(Cow::Borrowed("abc")).as_ref(), "abc"); + /// ``` + fn as_ref(&self) -> &str { + use InnerQueryKey::*; + + match self { + Delimiter => "delimiter", + StartAfter => "start-after", + ContinuationToken => "continuation-token", + MaxKeys => "max-keys", + Prefix => "prefix", + EncodingType => "encoding-type", + // TODO + FetchOwner => unimplemented!("parse xml not support fetch owner"), + Custom(str) => str.as_ref(), + } + } +} + +impl Display for InnerQueryKey<'_> { + /// ``` + /// # use aliyun_oss_client::QueryKey; + /// assert_eq!(format!("{}", QueryKey::Delimiter), "delimiter"); + /// ``` + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_ref()) + } +} + +impl From for InnerQueryKey<'_> { + fn from(s: String) -> Self { + Self::new(s) + } +} +impl<'a: 'b, 'b> From<&'a str> for InnerQueryKey<'b> { + fn from(date: &'a str) -> Self { + Self::new(date) + } +} + +impl FromStr for QueryKey { + type Err = InvalidQueryKey; + /// 示例 + /// ``` + /// # use aliyun_oss_client::types::QueryKey; + /// let value: QueryKey = "abc".into(); + /// assert!(value == QueryKey::from_static("abc")); + /// ``` + fn from_str(s: &str) -> Result { + Ok(Self::from_static(s)) + } +} + +/// 异常的查询条件键 +#[derive(Debug)] +pub struct InvalidQueryKey { + _priv: (), +} + +impl Error for InvalidQueryKey {} + +impl Display for InvalidQueryKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "invalid query key") + } +} + +impl<'a> InnerQueryKey<'a> { + /// # Examples + /// ``` + /// # use aliyun_oss_client::QueryKey; + /// # use assert_matches::assert_matches; + /// let key = QueryKey::new("delimiter"); + /// assert!(key == QueryKey::Delimiter); + /// assert!(QueryKey::new("start-after") == QueryKey::StartAfter); + /// assert!(QueryKey::new("continuation-token") == QueryKey::ContinuationToken); + /// assert!(QueryKey::new("max-keys") == QueryKey::MaxKeys); + /// assert!(QueryKey::new("prefix") == QueryKey::Prefix); + /// assert!(QueryKey::new("encoding-type") == QueryKey::EncodingType); + /// + /// let key = QueryKey::new("abc"); + /// assert_matches!(key, QueryKey::Custom(_)); + /// ``` + /// *`fetch-owner` 功能未实现,特殊说明* + pub fn new(val: impl Into>) -> Self { + use InnerQueryKey::*; + + let val = val.into(); + if val.contains("delimiter") { + Delimiter + } else if val.contains("start-after") { + StartAfter + } else if val.contains("continuation-token") { + ContinuationToken + } else if val.contains("max-keys") { + MaxKeys + } else if val.contains("prefix") { + Prefix + } else if val.contains("encoding-type") { + EncodingType + } else if val.contains("fetch-owner") { + unimplemented!("parse xml not support fetch owner"); + } else { + Custom(val) + } + } + + /// # Examples + /// ``` + /// # use aliyun_oss_client::QueryKey; + /// # use assert_matches::assert_matches; + /// let key = QueryKey::from_static("delimiter"); + /// assert!(key == QueryKey::Delimiter); + /// assert!(QueryKey::from_static("start-after") == QueryKey::StartAfter); + /// assert!(QueryKey::from_static("continuation-token") == QueryKey::ContinuationToken); + /// assert!(QueryKey::from_static("max-keys") == QueryKey::MaxKeys); + /// assert!(QueryKey::from_static("prefix") == QueryKey::Prefix); + /// assert!(QueryKey::from_static("encoding-type") == QueryKey::EncodingType); + /// + /// let key = QueryKey::from_static("abc"); + /// assert_matches!(key, QueryKey::Custom(_)); + /// ``` + /// *`fetch-owner` 功能未实现,特殊说明* + pub fn from_static(val: &str) -> Self { + use InnerQueryKey::*; + + if val.contains("delimiter") { + Delimiter + } else if val.contains("start-after") { + StartAfter + } else if val.contains("continuation-token") { + ContinuationToken + } else if val.contains("max-keys") { + MaxKeys + } else if val.contains("prefix") { + Prefix + } else if val.contains("encoding-type") { + EncodingType + } else if val.contains("fetch-owner") { + unimplemented!("parse xml not support fetch owner"); + } else { + Custom(Cow::Owned(val.to_owned())) + } + } +} + +#[cfg(test)] +mod test_query_key { + use super::*; + + #[test] + #[should_panic] + fn test_fetch_owner() { + QueryKey::new("fetch-owner"); + } +} + +/// 查询条件的值 +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct InnerQueryValue<'a>(Cow<'a, str>); +/// 查询条件的值 +pub type QueryValue = InnerQueryValue<'static>; + +impl AsRef for InnerQueryValue<'_> { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Display for InnerQueryValue<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for InnerQueryValue<'_> { + fn from(s: String) -> Self { + Self(Cow::Owned(s)) + } +} +impl<'a: 'b, 'b> From<&'a str> for InnerQueryValue<'b> { + fn from(date: &'a str) -> Self { + Self::new(date) + } +} + +impl PartialEq<&str> for InnerQueryValue<'_> { + #[inline] + fn eq(&self, other: &&str) -> bool { + &self.0 == other + } +} + +impl From for InnerQueryValue<'_> { + /// 数字转 Query 值 + /// + /// ``` + /// # use aliyun_oss_client::Query; + /// # use aliyun_oss_client::QueryKey; + /// let query = Query::from_iter([("max_keys", 100u8)]); + /// let query = Query::from_iter([(QueryKey::MaxKeys, 100u8)]); + /// ``` + fn from(num: u8) -> Self { + Self(Cow::Owned(num.to_string())) + } +} + +impl PartialEq for InnerQueryValue<'_> { + #[inline] + fn eq(&self, other: &u8) -> bool { + self.to_string() == other.to_string() + } +} + +impl From for InnerQueryValue<'_> { + /// 数字转 Query 值 + /// + /// ``` + /// use aliyun_oss_client::Query; + /// let query = Query::from_iter([("max_keys", 100u16)]); + /// ``` + fn from(num: u16) -> Self { + Self(Cow::Owned(num.to_string())) + } +} + +impl PartialEq for InnerQueryValue<'_> { + #[inline] + fn eq(&self, other: &u16) -> bool { + self.to_string() == other.to_string() + } +} + +impl From for QueryValue { + /// bool 转 Query 值 + /// + /// ``` + /// use aliyun_oss_client::Query; + /// let query = Query::from_iter([("abc", "false")]); + /// ``` + fn from(b: bool) -> Self { + if b { + Self::from_static("true") + } else { + Self::from_static("false") + } + } +} + +impl FromStr for InnerQueryValue<'_> { + type Err = InvalidQueryValue; + /// 示例 + /// ``` + /// # use aliyun_oss_client::types::QueryValue; + /// let value: QueryValue = "abc".parse().unwrap(); + /// assert!(value == "abc"); + /// ``` + fn from_str(s: &str) -> Result { + Ok(Self::from_static2(s)) + } +} + +/// 异常的查询值 +#[derive(Debug)] +pub struct InvalidQueryValue { + _priv: (), +} + +impl Error for InvalidQueryValue {} + +impl Display for InvalidQueryValue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "invalid query value") + } +} + +impl<'a> InnerQueryValue<'a> { + /// Creates a new `QueryValue` from the given string. + pub fn new(val: impl Into>) -> Self { + Self(val.into()) + } + + /// Const function that creates a new `QueryValue` from a static str. + pub const fn from_static(val: &'static str) -> Self { + Self(Cow::Borrowed(val)) + } + + /// Const function that creates a new `QueryValue` from a static str. + pub fn from_static2(val: &str) -> Self { + Self(Cow::Owned(val.to_owned())) + } +} + +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; +use std::str::FromStr; + +use http::HeaderValue; +use reqwest::Url; + +/// 用于指定返回内容的区域的 type +pub struct ContentRange { + start: Option, + end: Option, +} + +impl From> for ContentRange { + fn from(r: Range) -> Self { + Self { + start: Some(r.start), + end: Some(r.end), + } + } +} + +impl From for ContentRange { + fn from(_f: RangeFull) -> Self { + Self { + start: None, + end: None, + } + } +} + +impl From> for ContentRange { + fn from(f: RangeFrom) -> Self { + Self { + start: Some(f.start), + end: None, + } + } +} + +impl From> for ContentRange { + fn from(t: RangeTo) -> Self { + Self { + start: None, + end: Some(t.end), + } + } +} + +impl From for HeaderValue { + /// # 转化成 OSS 需要的格式 + /// @link [OSS 文档](https://help.aliyun.com/document_detail/31980.html) + /// + /// ``` + /// use reqwest::header::HeaderValue; + /// # use aliyun_oss_client::types::ContentRange; + /// fn abc>(range: R) -> HeaderValue { + /// range.into().into() + /// } + /// + /// assert_eq!(abc(..), HeaderValue::from_str("bytes=0-").unwrap()); + /// assert_eq!(abc(1..), HeaderValue::from_str("bytes=1-").unwrap()); + /// assert_eq!(abc(10..20), HeaderValue::from_str("bytes=10-20").unwrap()); + /// assert_eq!(abc(..20), HeaderValue::from_str("bytes=0-20").unwrap()); + /// ``` + fn from(con: ContentRange) -> HeaderValue { + let string = match (con.start, con.end) { + (Some(ref start), Some(ref end)) => format!("bytes={}-{}", start, end), + (Some(ref start), None) => format!("bytes={}-", start), + (None, Some(ref end)) => format!("bytes=0-{}", end), + (None, None) => "bytes=0-".to_string(), + }; + + HeaderValue::from_str(&string).unwrap_or_else(|_| { + panic!( + "content-range into header-value failed, content-range is : {}", + string + ) + }) + } +}