Add: delete_objects api

This commit is contained in:
kingzcheung 2023-08-11 23:46:27 +08:00
parent 1d98890964
commit 22943fffe4
6 changed files with 203 additions and 79 deletions

View File

@ -27,6 +27,7 @@ serde = { version = "1.0.162", features = ["derive"] }
serde_json = "1.0.96"
async-trait = "0.1.68"
lazy_static = "1.4.0"
md-5 = "0.10.5"
[target.'cfg(unix)'.dependencies]
dotenvy = "0.15.7"

View File

@ -1,4 +1,4 @@
use reqwest::{StatusCode};
use reqwest::StatusCode;
use serde::Deserialize;
use thiserror::Error;

View File

@ -0,0 +1,74 @@
use serde::{ Serialize, Deserialize };
// #[derive(Debug, Serialize, Deserialize, PartialEq)]
// pub struct DeleteRequest {
// #[serde(rename = "Delete")]
// pub delete: Delete,
// }
pub trait Boolean {
fn to_bool(&self) -> bool;
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum ResponseMode {
Quiet,
Verbose
}
impl Boolean for ResponseMode {
fn to_bool(&self) -> bool {
match self {
ResponseMode::Quiet => true,
ResponseMode::Verbose => false,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Delete {
#[serde(rename = "Quiet")]
pub quiet: bool,
#[serde(rename = "$value")]
pub item: Vec<Item>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum Item {
Object(Object),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Object {
#[serde(rename = "Key")]
pub key_name: String,
#[serde(rename = "VersionId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub version_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DeleteResult {
#[serde(rename = "Deleted")]
pub deleted: Deleted,
#[serde(rename = "Error")]
pub error: Error,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Deleted {
#[serde(rename = "Key")]
pub key_name: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Error {
#[serde(rename = "Key")]
pub key_name: String,
#[serde(rename = "Code")]
pub code: String,
#[serde(rename = "Message")]
pub message: String,
}

View File

@ -2,6 +2,7 @@ use serde::{Serialize, Deserialize};
pub mod object;
pub mod bucket;
pub mod delete_object;
// #[derive(Serialize, Deserialize,Debug)]
// pub struct ErrorResponse {

View File

@ -1,18 +1,17 @@
use std::collections::HashMap;
use base64::{engine::general_purpose, Engine};
use md5::{Md5, Digest};
use async_trait::async_trait;
use reqwest::{
header::{HeaderMap, HeaderValue},
Method,
};
use reqwest::{ header::{ HeaderMap, HeaderValue }, Method };
use urlencoding::encode;
use crate::{
client::Client,
error::{status_to_response, ObsError},
error::{ status_to_response, ObsError },
model::{
bucket::copy_object::CopyObjectResult,
object::{NextPosition, ObjectMeta},
object::{ NextPosition, ObjectMeta },
delete_object::{ Delete, Object, Item, ResponseMode, Boolean },
},
};
@ -23,7 +22,7 @@ pub trait ObjectTrait {
&self,
bucket: S,
key: S,
object: &[u8],
object: &[u8]
) -> Result<(), ObsError>;
/// 复制对象
@ -31,28 +30,34 @@ pub trait ObjectTrait {
&self,
bucket: S1,
src: S2,
dest: S3,
) -> Result<CopyObjectResult, ObsError>
where
S1: AsRef<str> + Send,
S2: AsRef<str> + Send,
S3: AsRef<str> + Send;
dest: S3
)
-> Result<CopyObjectResult, ObsError>
where S1: AsRef<str> + Send, S2: AsRef<str> + Send, S3: AsRef<str> + Send;
/// 删除对象
async fn delete_object<S: AsRef<str> + Send>(&self, bucket: S, key: S) -> Result<(), ObsError>;
/// 批量删除对象
async fn delete_objects<S: AsRef<str> + Send, K: IntoIterator<Item = S> + Send>(
&self,
bucket: S,
keys: K,
response_mode: ResponseMode,
) -> Result<(), ObsError>;
/// 获取对象内容
async fn get_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
key: S
) -> Result<bytes::Bytes, ObsError>;
/// 获取对象元数据
async fn get_object_metadata<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
key: S
) -> Result<ObjectMeta, ObsError>;
/// 追加写对象
@ -61,7 +66,7 @@ pub trait ObjectTrait {
bucket: S,
key: S,
appended: &[u8],
position: u64,
position: u64
) -> Result<NextPosition, ObsError>;
}
@ -72,23 +77,21 @@ impl ObjectTrait for Client {
&self,
bucket: S,
key: S,
object: &[u8],
object: &[u8]
) -> Result<(), ObsError> {
let mut with_headers = HeaderMap::new();
with_headers.insert(
"Content-Length",
HeaderValue::from_str(format!("{}", object.len()).as_str()).unwrap(),
HeaderValue::from_str(format!("{}", object.len()).as_str()).unwrap()
);
let resp = self
.do_action(
Method::PUT,
bucket,
key,
Some(with_headers),
None,
Some(object.to_owned()),
)
.await?;
let resp = self.do_action(
Method::PUT,
bucket,
key,
Some(with_headers),
None,
Some(object.to_owned())
).await?;
let _ = resp.text().await?;
Ok(())
@ -99,7 +102,7 @@ impl ObjectTrait for Client {
bucket: S,
key: S,
appended: &[u8],
position: u64,
position: u64
) -> Result<NextPosition, ObsError> {
let mut params = HashMap::new();
params.insert("append".to_string(), "".into());
@ -107,23 +110,20 @@ impl ObjectTrait for Client {
let mut with_headers = HeaderMap::new();
with_headers.insert(
"Content-Length",
HeaderValue::from_str(format!("{}", appended.len()).as_str()).unwrap(),
HeaderValue::from_str(format!("{}", appended.len()).as_str()).unwrap()
);
let resp = self
.do_action(
Method::POST,
bucket,
key,
Some(with_headers),
Some(params),
Some(appended.to_owned()),
)
.await?;
let resp = self.do_action(
Method::POST,
bucket,
key,
Some(with_headers),
Some(params),
Some(appended.to_owned())
).await?;
let status = resp.status();
let headers = resp.headers().clone();
if status.is_success() {
let next_position = if let Some(next) = headers.get("x-obs-next-append-position")
{
let next_position = if let Some(next) = headers.get("x-obs-next-append-position") {
let next = String::from_utf8_lossy(next.as_bytes()).to_string();
match next.parse::<u64>() {
Ok(u) => Some(u),
@ -146,33 +146,26 @@ impl ObjectTrait for Client {
&self,
bucket: S1,
src: S2,
dest: S3,
) -> Result<CopyObjectResult, ObsError>
where
S1: AsRef<str> + Send,
S2: AsRef<str> + Send,
S3: AsRef<str> + Send,
dest: S3
)
-> Result<CopyObjectResult, ObsError>
where S1: AsRef<str> + Send, S2: AsRef<str> + Send, S3: AsRef<str> + Send
{
let mut with_headers = HeaderMap::new();
let dest = dest.as_ref().trim_start_matches('/');
let src = src.as_ref().trim_start_matches('/');
let src = encode(src);
let copy_source = format!("/{}/{}", bucket.as_ref(), src);
with_headers.insert(
"x-obs-copy-source",
HeaderValue::from_str(&copy_source).unwrap(),
);
with_headers.insert("x-obs-copy-source", HeaderValue::from_str(&copy_source).unwrap());
let resp = self
.do_action(
Method::PUT,
bucket,
dest,
Some(with_headers),
None,
None::<String>,
)
.await?;
let resp = self.do_action(
Method::PUT,
bucket,
dest,
Some(with_headers),
None,
None::<String>
).await?;
let status = resp.status();
let text = resp.text().await?;
status_to_response::<CopyObjectResult>(status, text)
@ -194,9 +187,51 @@ impl ObjectTrait for Client {
/// 删除对象
async fn delete_object<S: AsRef<str> + Send>(&self, bucket: S, key: S) -> Result<(), ObsError> {
let _resp = self
.do_action(Method::DELETE, bucket, key, None, None, None::<String>)
.await?;
let _resp = self.do_action(Method::DELETE, bucket, key, None, None, None::<String>).await?;
Ok(())
}
/// 批量删除对象
async fn delete_objects<S: AsRef<str> + Send, K: IntoIterator<Item = S> + Send>(
&self,
bucket: S,
keys: K,
response_mode: ResponseMode,
) -> Result<(), ObsError> {
let mut with_headers = HeaderMap::new();
let mut params = HashMap::new();
params.insert("delete".to_string(), "".to_string());
let body = Delete {
quiet: response_mode.to_bool(),
item: keys
.into_iter()
.map(|key|
Item::Object(Object { key_name: key.as_ref().to_owned(), version_id: None })
)
.collect::<Vec<Item>>(),
};
let body = serde_xml_rs::to_string(&body)?;
let mut hasher = Md5::new();
hasher.update(body.as_bytes());
let result = hasher.finalize();
let val = general_purpose::STANDARD.encode(result);
with_headers.insert("Content-MD5", HeaderValue::from_str(val.as_str()).unwrap());
with_headers.insert(
"Content-Length",
HeaderValue::from_str(format!("{}", body.as_bytes().len()).as_str()).unwrap()
);
let _resp = self.do_action(
Method::POST,
bucket,
"",
Some(with_headers),
Some(params),
Some(body)
).await?;
// dbg!(_resp.text().await?);
Ok(())
}
@ -204,13 +239,11 @@ impl ObjectTrait for Client {
async fn get_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
key: S
) -> Result<bytes::Bytes, ObsError> {
let resp = self
.do_action(Method::GET, bucket, key, None, None, None::<String>)
.await?
.bytes()
.await?;
.do_action(Method::GET, bucket, key, None, None, None::<String>).await?
.bytes().await?;
Ok(resp)
}
@ -219,11 +252,9 @@ impl ObjectTrait for Client {
async fn get_object_metadata<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
key: S
) -> Result<ObjectMeta, ObsError> {
let resp = self
.do_action(Method::HEAD, bucket, key, None, None, None::<String>)
.await?;
let resp = self.do_action(Method::HEAD, bucket, key, None, None, None::<String>).await?;
let headers = resp.headers();
let mut data = HashMap::with_capacity(headers.len());
for (key, val) in headers {
@ -232,8 +263,9 @@ impl ObjectTrait for Client {
let header_str = serde_json::to_string(&data).map_err(|_e| ObsError::ParseOrConvert)?;
let data: ObjectMeta =
serde_json::from_str(&header_str).map_err(|_e| ObsError::ParseOrConvert)?;
let data: ObjectMeta = serde_json
::from_str(&header_str)
.map_err(|_e| ObsError::ParseOrConvert)?;
Ok(data)
}

View File

@ -1,5 +1,5 @@
mod common;
use huaweicloud_sdk_rust_obs::{error::ObsError, object::ObjectTrait};
use huaweicloud_sdk_rust_obs::{error::ObsError, object::ObjectTrait, model::delete_object::ResponseMode};
use crate::common::*;
@ -70,3 +70,19 @@ async fn test_append_object() -> Result<(), ObsError> {
// obs.delete_object(DEFAULT_BUCKET_NAME, key).await?;
Ok(())
}
#[tokio::test]
async fn test_delete_objects() -> Result<(), ObsError> {
let objects = vec![
"obs-client-test-delete-object.txt",
"obs-client-test-delete-object2.txt",
];
let obs = create_obs_client()?;
for obj in &objects {
obs.put_object(DEFAULT_BUCKET_NAME, obj, b"test delete text").await?;
}
obs.delete_objects(DEFAULT_BUCKET_NAME, objects, ResponseMode::Verbose).await?;
Ok(())
}