use std::collections::HashMap;
use async_trait::async_trait;
use reqwest::{
header::{HeaderMap, HeaderValue},
Method,
};
use urlencoding::encode;
use crate::{
client::Client,
error::{status_to_response, ObsError},
model::{
bucket::copy_object::CopyObjectResult,
object::{NextPosition, ObjectMeta},
},
};
#[async_trait]
pub trait ObjectTrait {
async fn put_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
object: &[u8],
) -> Result<(), ObsError>;
async fn copy_object<S1, S2, S3>(
&self,
bucket: S1,
src: S2,
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 get_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
) -> Result<bytes::Bytes, ObsError>;
async fn get_object_metadata<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
) -> Result<ObjectMeta, ObsError>;
async fn append_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
appended: &[u8],
position: u64,
) -> Result<NextPosition, ObsError>;
}
#[async_trait]
impl ObjectTrait for Client {
async fn put_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
object: &[u8],
) -> Result<(), ObsError> {
let mut with_headers = HeaderMap::new();
with_headers.insert(
"Content-Length",
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.text().await?;
Ok(())
}
async fn append_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
appended: &[u8],
position: u64,
) -> Result<NextPosition, ObsError> {
let mut params = HashMap::new();
params.insert("append".to_string(), "".into());
params.insert("position".into(), position.to_string());
let mut with_headers = HeaderMap::new();
with_headers.insert(
"Content-Length",
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 status = resp.status();
let headers = resp.headers().clone();
let text = resp.text().await;
dbg!(text);
if status.is_success() {
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),
Err(_) => None,
}
} else {
None
};
Ok(next_position as NextPosition)
} else {
Err(ObsError::Response {
status,
message: "response error".into(),
})
}
}
async fn copy_object<S1, S2, S3>(
&self,
bucket: S1,
src: S2,
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(©_source).unwrap(),
);
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)
}
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?;
Ok(())
}
async fn get_object<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
) -> Result<bytes::Bytes, ObsError> {
let resp = self
.do_action(Method::GET, bucket, key, None, None, None::<String>)
.await?
.bytes()
.await?;
Ok(resp)
}
async fn get_object_metadata<S: AsRef<str> + Send>(
&self,
bucket: S,
key: S,
) -> Result<ObjectMeta, ObsError> {
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 {
data.insert(key.as_str(), val.to_str().unwrap());
}
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)?;
Ok(data)
}
}