Skip to content

Commit

Permalink
feat(file): 将 File trait 改名为 Files,另外新增 File trait
Browse files Browse the repository at this point in the history
File trait 用于单个文件的类型,在进行上传等操作时,不需要指定路径

Files trait 用于文件集合的类型,在进行操作时,需要指定具体的文件路径,例如:
use aliyun_oss_client::file::Files;
client.delete_object(ObjectPath::new("abc.jpg").unwrap());

FileAs trait 用于比较便捷的操作:例如:
use aliyun_oss_client::file::FileAs;
client.delete_object_as("abc.jpg");

break change
  • Loading branch information
tu6ge committed Feb 8, 2023
1 parent 9518293 commit 023c320
Show file tree
Hide file tree
Showing 12 changed files with 587 additions and 302 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,23 @@ let client = aliyun_oss_client::Client::new("key1".into(), "secret1".into(), "qi
# set_var("ALIYUN_ENDPOINT", "qingdao");
# set_var("ALIYUN_BUCKET", "foo4");
# let client = aliyun_oss_client::Client::from_env().unwrap();
use aliyun_oss_client::file::File;
use aliyun_oss_client::file::FileAs;
client
.put_file("examples/bg2015071010.png", "examples/bg2015071010.png")
.put_file_as("examples/bg2015071010.png", "examples/bg2015071010.png")
.await;

// or 上传文件内容
let file_content = std::fs::read("examples/bg2015071010.png").unwrap();
client
.put_content(file_content, "examples/bg2015071010.png", |_| {
.put_content_as(file_content, "examples/bg2015071010.png", |_| {
Some("image/png")
})
.await;

// or 自定义上传文件 Content-Type
let file_content = std::fs::read("examples/bg2015071010.png").unwrap();
client
.put_content_base(file_content, "image/png", "examples/bg2015071010.png")
.put_content_base_as(file_content, "image/png", "examples/bg2015071010.png")
.await;
# }
```
Expand All @@ -160,8 +160,8 @@ let client = aliyun_oss_client::Client::new("key1".into(), "secret1".into(), "qi
# set_var("ALIYUN_ENDPOINT", "qingdao");
# set_var("ALIYUN_BUCKET", "foo4");
# let client = aliyun_oss_client::Client::from_env().unwrap();
use aliyun_oss_client::file::File;
client.delete_object("examples/bg2015071010.png").await;
use aliyun_oss_client::file::FileAs;
client.delete_object_as("examples/bg2015071010.png").await;
# }
```

Expand Down Expand Up @@ -249,7 +249,7 @@ println!("next object list: {:?}", result.next());
# set_var("ALIYUN_ENDPOINT", "qingdao");
# set_var("ALIYUN_BUCKET", "foo4");
# let client = aliyun_oss_client::ClientRc::from_env().unwrap();
use aliyun_oss_client::file::blocking::File;
use aliyun_oss_client::file::blocking::Files;
client
.put_file("examples/bg2015071010.png", "examples/bg2015071010.png");
Expand Down
61 changes: 61 additions & 0 deletions examples/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::{fs, path::Path};

use aliyun_oss_client::{
config::ObjectPath,
file::{File, FileError, Files},
BucketName, Client, EndPoint, KeyId, KeySecret,
};

struct MyObject {
path: ObjectPath,
}

impl MyObject {
const KEY_ID: KeyId = KeyId::from_static("xxxxxxxxxxxxxxxx");
const KEY_SECRET: KeySecret = KeySecret::from_static("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
const END_POINT: EndPoint = EndPoint::CnShanghai;
const BUCKET: BucketName = unsafe { BucketName::from_static2("xxxxxx") };

fn new(path: &Path) -> Result<MyObject, FileError> {
Ok(MyObject {
path: path.try_into()?,
})
}
}

impl File for MyObject {
type Client = Client;
fn get_path(&self) -> ObjectPath {
self.path.clone()
}

fn oss_client(&self) -> Self::Client {
Client::new(
Self::KEY_ID,
Self::KEY_SECRET,
Self::END_POINT,
Self::BUCKET,
)
}
}

#[tokio::main]
async fn main() -> Result<(), FileError> {
for entry in fs::read_dir("examples")? {
let path = entry?.path();
let path = path.as_path();

if !path.is_file() {
continue;
}

let obj = MyObject::new(path)?;
let content = fs::read(path)?;

let res = obj.put_oss(content, Client::DEFAULT_CONTENT_TYPE).await?;

println!("result status: {}", res.status());
}

Ok(())
}
69 changes: 69 additions & 0 deletions examples/files.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::fs;

use aliyun_oss_client::builder::{BuilderError, RequestBuilder};
use aliyun_oss_client::file::{AlignBuilder, FileError, Files};
use aliyun_oss_client::types::CanonicalizedResource;
use aliyun_oss_client::{BucketName, Client, EndPoint, HeaderName, HeaderValue, Method};
use reqwest::Url;

struct MyClient;

#[derive(Debug)]
struct MyError(String);

impl From<FileError> for MyError {
fn from(value: FileError) -> Self {
Self(value.to_string())
}
}

struct MyPath(String);

impl AlignBuilder for MyClient {
fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
&self,
method: Method,
url: Url,
resource: CanonicalizedResource,
headers: H,
) -> Result<RequestBuilder, BuilderError> {
dotenv::dotenv().ok();
Client::from_env()?.builder_with_header(method, url, resource, headers)
}
}

impl Files for MyClient {
type Err = MyError;
type Path = MyPath;
fn get_url(&self, path: Self::Path) -> Result<(Url, CanonicalizedResource), Self::Err> {
use aliyun_oss_client::config::OssFullUrl;

dotenv::dotenv().ok();
let bucket = std::env::var("ALIYUN_BUCKET").unwrap();

let end_point = EndPoint::CnShanghai;
let bucket = BucketName::new(bucket).unwrap();

let resource = format!("/{}/{}", bucket, path.0);

let p = path
.0
.try_into()
.map_err(|_| MyError("路径格式错误".to_string()))?;
let url = Url::from_oss(&end_point, &bucket, &p);

Ok((url, CanonicalizedResource::new(resource)))
}
}

#[tokio::main]
async fn main() {
let client = MyClient {};

let file = fs::read("rustfmt.toml").unwrap();
let res = client
.put_content_base(file, "application/json", MyPath("rustfmt.toml".to_string()))
.await;

println!("{res:?}");
}
15 changes: 15 additions & 0 deletions oss_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,18 @@ pub fn array2query(attr: TokenStream, input: TokenStream) -> TokenStream {
update_count(&mut item, attr.count);
TokenStream::from(quote!(#item))
}

mod path_where;

/// # 为 `OP` 自动生成 `where` 语句
///
/// ```rust,ignore
/// where:
/// OP: TryInto<ObjectPath> + Send + Sync,
/// <OP as TryInto<ObjectPath>>::Error: Into<Self::Error>,
/// ```
#[proc_macro_attribute]
pub fn path_where(_attr: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as path_where::GenWhere);
TokenStream::from(quote!(#item))
}
41 changes: 41 additions & 0 deletions oss_derive/src/path_where.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream, Result},
parse_quote,
visit_mut::{self, VisitMut},
TraitItemMethod, WhereClause,
};

pub(crate) struct GenWhere(TraitItemMethod);

impl Parse for GenWhere {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))
}
}

impl ToTokens for GenWhere {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let mut item = self.0.clone();
AppendWhere.visit_trait_item_method_mut(&mut item);

item.to_tokens(tokens);
}
}

struct AppendWhere;

impl VisitMut for AppendWhere {
fn visit_trait_item_method_mut(&mut self, item: &mut TraitItemMethod) {
visit_mut::visit_trait_item_method_mut(self, item);
}

fn visit_where_clause_mut(&mut self, i: &mut WhereClause) {
i.predicates
.push(parse_quote! {OP: TryInto<ObjectPath> + Send + Sync});
i.predicates
.push(parse_quote! {<OP as TryInto<ObjectPath>>::Error: Into<Self::Error>});

visit_mut::visit_where_clause_mut(self, i);
}
}
8 changes: 7 additions & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ use thiserror::Error;
use crate::auth::AuthError;
#[cfg(feature = "blocking")]
use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware;
use crate::{client::Client as AliClient, config::BucketBase};
use crate::{
client::Client as AliClient,
config::{BucketBase, InvalidConfig},
};
use reqwest::{Client, Request, Response};

pub trait PointerFamily
Expand Down Expand Up @@ -154,6 +157,9 @@ pub enum BuilderError {
#[cfg(feature = "auth")]
#[error("{0}")]
AuthError(#[from] AuthError),

#[error("{0}")]
Config(#[from] InvalidConfig),
}

#[async_trait]
Expand Down
58 changes: 56 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
borrow::Cow,
env::{self, VarError},
fmt::Display,
path::Path,
str::FromStr,
sync::Arc,
};
Expand All @@ -17,6 +18,7 @@ use thiserror::Error;
use crate::builder::RcPointer;
use crate::{
builder::{ArcPointer, PointerFamily},
object::Object,
types::{
BucketName, CanonicalizedResource, EndPoint, InvalidBucketName, InvalidEndPoint, KeyId,
KeySecret, QueryKey, QueryValue, UrlQuery,
Expand Down Expand Up @@ -615,19 +617,23 @@ impl ObjectPath {
/// assert!(ObjectPath::new("abc/").is_err());
/// assert!(ObjectPath::new(".abc").is_err());
/// assert!(ObjectPath::new("../abc").is_err());
/// assert!(ObjectPath::new(r"aaa\abc").is_err());
/// ```
pub fn new(val: impl Into<Cow<'static, str>>) -> Result<Self, InvalidObjectPath> {
let val = val.into();
if val.starts_with('/') || val.starts_with('.') || val.ends_with('/') {
return Err(InvalidObjectPath);
}
if !val.chars().all(|c| c != '\\') {
return Err(InvalidObjectPath);
}
Ok(Self(val))
}

/// Const function that creates a new `ObjectPath` from a static str.
/// ```
/// # use aliyun_oss_client::config::ObjectPath;
/// let path = unsafe{ ObjectPath::from_static("abc") };
/// let path = unsafe { ObjectPath::from_static("abc") };
/// assert!(path == "abc");
/// ```
pub const unsafe fn from_static(secret: &'static str) -> Self {
Expand Down Expand Up @@ -672,16 +678,39 @@ impl FromStr for ObjectPath {
/// assert!(ObjectPath::from_str("abc/").is_err());
/// assert!(ObjectPath::from_str(".abc").is_err());
/// assert!(ObjectPath::from_str("../abc").is_err());
/// ```
/// assert!(ObjectPath::from_str(r"aaa\abc").is_err());
/// ```
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with('/') || s.starts_with('.') || s.ends_with('/') {
return Err(InvalidObjectPath);
}

if !s.chars().all(|c| c != '\\') {
return Err(InvalidObjectPath);
}
Ok(Self(Cow::Owned(s.to_owned())))
}
}

impl TryFrom<&Path> for ObjectPath {
type Error = InvalidObjectPath;
fn try_from(value: &Path) -> Result<Self, Self::Error> {
let val = value.to_str().ok_or(InvalidObjectPath)?;
if std::path::MAIN_SEPARATOR != '/' {
val.replace(std::path::MAIN_SEPARATOR, "/").parse()
} else {
val.parse()
}
}
}

impl<T: PointerFamily> From<Object<T>> for ObjectPath {
#[inline]
fn from(obj: Object<T>) -> Self {
obj.base.path
}
}

#[derive(Debug, Error)]
pub struct InvalidObjectPath;

Expand All @@ -706,6 +735,31 @@ impl UrlObjectPath for Url {
}
}

pub trait OssFullUrl {
fn from_oss(endpoint: &EndPoint, bucket: &BucketName, path: &ObjectPath) -> Self;
}

impl OssFullUrl for Url {
fn from_oss(endpoint: &EndPoint, bucket: &BucketName, path: &ObjectPath) -> Self {
let mut end_url = endpoint.to_url();

let host = end_url.host_str();

let mut name_str = bucket.to_string() + ".";

let new_host = host.map(|h| {
name_str.push_str(h);
&*name_str
});
// 因为 endpoint 都是已知字符组成,bucket 也有格式要求,所以 unwrap 是安全的
end_url.set_host(new_host).unwrap();

end_url.set_object_path(&path);

end_url
}
}

/// 文件夹下的子文件夹名,子文件夹下递归的所有文件和文件夹不包含在这里。
pub type CommonPrefixes = Vec<ObjectPath>;

Expand Down
Loading

0 comments on commit 023c320

Please sign in to comment.