use super::Client;
use super::sign_v4::HTTPVerb;
use super::utils::{
get_request_header, get_request_header_with_bucket_region, into_request_failed_error,
parse_xml_response,
};
use crate::oss::Error;
use bon::Builder;
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};
use std::collections::HashMap;
use url::Url;
#[serde_with::skip_serializing_none]
#[derive(Builder, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PutBucket<'a> {
#[builder(start_fn)]
#[serde(skip_serializing)]
pub(crate) client: &'a Client,
#[serde(skip_serializing)]
pub(crate) bucket_name: &'a str,
#[serde(skip_serializing)]
pub(crate) storage_class: Option<&'a str>,
#[serde(skip_serializing)]
pub(crate) data_redundancy_type: Option<&'a str>,
x_oss_acl: Option<&'a str>,
x_oss_resource_group_id: Option<&'a str>,
x_oss_bucket_tagging: Option<&'a str>,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct CreateBucketConfiguration<'a> {
storage_class: Option<&'a str>,
data_redundancy_type: Option<&'a str>,
}
impl PutBucket<'_> {
pub async fn send(&self) -> Result<(), Error> {
let client = self.client;
let request_url =
Url::parse(&format!("https://{}.{}", self.bucket_name, client.endpoint)).unwrap();
let req_header_map = serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
let header = get_request_header_with_bucket_region(
client,
req_header_map,
&request_url,
HTTPVerb::Put,
&self.client.region,
Some(self.bucket_name),
);
let req_xml = {
let create_conf = CreateBucketConfiguration {
storage_class: self.storage_class,
data_redundancy_type: self.data_redundancy_type,
};
quick_xml::se::to_string(&create_conf).unwrap()
};
let resp = client
.http_client
.put(request_url)
.headers(header)
.body(req_xml)
.send()
.await?;
if !resp.status().is_success() {
return Err(into_request_failed_error(resp).await);
}
Ok(())
}
}
#[serde_as]
#[serde_with::skip_serializing_none]
#[derive(Builder, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ListObjectsV2<'a> {
#[builder(start_fn)]
#[serde(skip_serializing)]
pub(crate) client: &'a Client,
delimiter: Option<&'a str>,
start_after: Option<&'a str>,
continuation_token: Option<&'a str>,
#[serde_as(as = "Option<DisplayFromStr>")]
max_keys: Option<u16>,
prefix: Option<&'a str>,
encoding_type: Option<&'a str>,
#[serde_as(as = "Option<DisplayFromStr>")]
fetch_owner: Option<bool>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListBucketResult {
pub contents: Option<Vec<Content>>,
pub common_prefixes: Option<CommonPrefixes>,
pub delimiter: String,
pub encoding_type: Option<String>,
pub is_truncated: bool,
pub start_after: Option<String>,
pub max_keys: u16,
pub name: String,
pub prefix: String,
pub continuation_token: Option<u32>,
pub key_count: u32,
pub next_continuation_token: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct CommonPrefixes {
pub prefix: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Content {
pub owner: Option<Owner>,
pub e_tag: String,
pub key: String,
pub last_modified: String,
pub size: u32,
pub storage_class: String,
pub restore_info: Option<String>,
pub r#type: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Owner {
pub display_name: String,
#[serde(rename = "ID")]
pub id: String,
}
impl ListObjectsV2<'_> {
pub async fn send(&self) -> Result<ListBucketResult, Error> {
let mut query_map: HashMap<String, String> =
serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
query_map.insert("list-type".to_owned(), "2".to_owned());
let client = self.client;
let sign_url = Url::parse_with_params(
&format!("https://{}.{}/", client.bucket, client.endpoint),
query_map,
)
.unwrap();
let header = get_request_header(client, HashMap::new(), &sign_url, HTTPVerb::Get);
let resp = client
.http_client
.get(sign_url)
.headers(header)
.send()
.await?;
let res = parse_xml_response(resp).await?;
Ok(res)
}
}
#[derive(Builder)]
pub struct GetBucketInfo<'a> {
#[builder(start_fn)]
pub(crate) client: &'a Client,
pub(crate) bucket: &'a str,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BucketInfo {
pub bucket: Bucket,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Bucket {
pub creation_date: String,
pub extranet_endpoint: String,
pub intranet_endpoint: String,
pub location: String,
pub storage_class: String,
pub name: String,
pub resource_group_id: String,
pub owner: Owner,
pub access_control_list: AccessControlList,
pub data_redundancy_type: String,
pub versioning: Option<String>,
pub cross_region_replication: String,
pub transfer_acceleration: String,
pub access_monitor: String,
pub bucket_policy: BucketPolicy,
pub comment: String,
pub server_side_encryption_rule: ServerSideEncryptionRule,
pub block_public_access: bool,
}
#[derive(Deserialize, Debug)]
pub struct ServerSideEncryptionRule {
#[serde(rename = "SSEAlgorithm")]
pub sse_algorithm: String,
pub kms_master_key_id: Option<String>,
pub kms_data_encryption: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AccessControlList {
pub grant: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BucketPolicy {
pub log_bucket: String,
pub log_prefix: String,
}
impl GetBucketInfo<'_> {
pub async fn send(&self) -> Result<BucketInfo, Error> {
let client = self.client;
let request_url = Url::parse_with_params(
&format!("https://{}.{}", self.bucket, client.endpoint),
[("bucketInfo", "")],
)
.unwrap();
let header_map = get_request_header(client, HashMap::new(), &request_url, HTTPVerb::Get);
let resp = client
.http_client
.get(request_url)
.headers(header_map)
.send()
.await?;
let res = parse_xml_response(resp).await?;
Ok(res)
}
}
#[derive(Builder)]
pub struct GetBucketLocation<'a> {
#[builder(start_fn)]
pub(crate) client: &'a Client,
pub(crate) bucket: &'a str,
}
#[derive(Deserialize)]
struct LocationConstraint {
#[serde(rename = "$text")]
field: String,
}
impl GetBucketLocation<'_> {
pub async fn send(&self) -> Result<String, Error> {
let client = self.client;
let request_url = Url::parse_with_params(
&format!("https://{}.{}", self.bucket, client.endpoint),
[("location", "")],
)
.unwrap();
let header_map = get_request_header(client, HashMap::new(), &request_url, HTTPVerb::Get);
let resp = client
.http_client
.get(request_url)
.headers(header_map)
.send()
.await?;
let res = parse_xml_response::<LocationConstraint>(resp).await?;
Ok(res.field)
}
}
#[derive(Builder)]
pub struct GetBucketStat<'a> {
#[builder(start_fn)]
pub(crate) client: &'a Client,
pub(crate) bucket: &'a str,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BucketStat {
pub storage: u64,
pub object_count: u32,
pub multipart_upload_count: u32,
pub delete_marker_count: u32,
pub live_channel_count: u32,
pub last_modified_time: u64,
pub standard_storage: u64,
pub standard_object_count: u32,
pub infrequent_access_storage: u64,
pub infrequent_access_real_storage: u64,
pub infrequent_access_object_count: u32,
pub archive_storage: u64,
pub archive_real_storage: u64,
pub archive_object_count: u32,
pub cold_archive_storage: u64,
pub cold_archive_real_storage: u64,
pub cold_archive_object_count: u32,
pub deep_cold_archive_storage: u64,
pub deep_cold_archive_real_storage: u64,
pub deep_cold_archive_object_count: u32,
}
impl GetBucketStat<'_> {
pub async fn send(&self) -> Result<BucketStat, Error> {
let client = self.client;
let request_url = Url::parse_with_params(
&format!("https://{}.{}", self.bucket, client.endpoint),
[("stat", "")],
)
.unwrap();
let header_map = get_request_header(client, HashMap::new(), &request_url, HTTPVerb::Get);
let resp = client
.http_client
.get(request_url)
.headers(header_map)
.send()
.await?;
let res = parse_xml_response(resp).await?;
Ok(res)
}
}
impl Client {
pub fn put_bucket(&self) -> PutBucketBuilder<'_> {
PutBucket::builder(self)
}
pub fn list_objects_v2(&self) -> ListObjectsV2Builder<'_> {
ListObjectsV2::builder(self)
}
pub fn get_bucket_info(&self) -> GetBucketInfoBuilder<'_> {
GetBucketInfo::builder(self)
}
pub fn get_bucket_location(&self) -> GetBucketLocationBuilder<'_> {
GetBucketLocation::builder(self)
}
pub fn get_bucket_stat(&self) -> GetBucketStatBuilder<'_> {
GetBucketStat::builder(self)
}
}