use anyhow::{bail, Context as _, Result};
use crate::client::ObjectstoreClient;
use crate::response::get_content_text;
use derive_builder::Builder;
use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use serde_aux::field_attributes::{deserialize_bool_from_anything, deserialize_default_from_null};
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct Link {
pub rel: String,
pub href: String,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct MinMaxGovernor {
pub enforce_retention: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub minimum_fixed_retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub maximum_fixed_retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub minimum_variable_retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub maximum_variable_retention: i64,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct MetaData {
pub key_data_type: String,
pub key_value: String,
pub metadata_type: String,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct SearchMetaData {
pub is_enabled: bool,
pub meta_data: Vec<MetaData>,
pub max_keys: i32,
pub md_tokens: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct Tag {
pub key: String,
pub value: String,
}
#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize)]
#[builder(setter(skip))]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"), rename(serialize = "object_bucket_create"))]
pub struct Bucket {
#[builder(setter(into))]
pub name: String,
pub id: String,
pub link: Link,
#[builder(setter(into))]
pub namespace: String,
pub replication: String,
pub locked: bool,
pub fs_acess_enabled: bool,
pub soft_quota: String,
pub created: String,
pub is_stale_allowed: bool,
pub object_lock_with_ado_allowed: bool,
pub is_tso_read_only: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_object_lock_retention_mode: String,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_object_lock_retention_years: i32,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_object_lock_retention_days: i32,
pub default_retention: i64,
pub block_size_in_g_b: i64,
pub auto_commit_period: i64,
pub notification_size_in_g_b: i64,
pub block_size_in_count: i64,
pub notification_size_in_count: i64,
#[builder(setter(skip = false), default = "false")]
#[serde(deserialize_with = "deserialize_bool_from_anything")]
pub is_encryption_enabled: bool,
pub retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_group: String,
pub default_group_file_read_permission: bool,
pub default_group_file_write_permission: bool,
pub default_group_file_execute_permission: bool,
pub default_group_dir_read_permission: bool,
default_group_dir_write_permission: bool,
pub default_group_dir_execute_permission: bool,
pub min_max_governor: MinMaxGovernor,
pub audit_delete_expiration: i64,
pub enable_advanced_metadata_search: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub advanced_metadata_search_target_name: String,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub advanced_metadata_search_target_stream: String,
pub is_empty_bucket_in_progress: bool,
pub meta_data: SearchMetaData,
pub local_object_metadata_reads: bool,
pub apitype: String,
pub bucket_owner: String,
#[serde(rename = "TagSet")]
pub tag_set: Vec<Tag>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateBucketResponse {
pub name: String,
pub id: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListBucketsResponse {
pub object_bucket_list: Vec<Bucket>,
pub filter: String,
pub next_marker: Option<String>,
pub max_buckets: Option<u32>,
pub next_page_link: Option<String>,
}
impl Bucket {
pub(crate) fn create(
client: &mut ObjectstoreClient,
bucket: Bucket,
) -> Result<String> {
let request_url = format!(
"{}object/bucket.json",
client.endpoint,
);
let body = serde_xml_rs::to_string(&bucket).unwrap();
let resp = client
.management_client
.http_client
.post(request_url)
.header(ACCEPT, "application/json")
.header(AUTHORIZATION, client.management_client.access_token.as_ref().unwrap())
.header(CONTENT_TYPE, "application/xml")
.body(body)
.send()?;
let text = get_content_text(resp)?;
let resp: CreateBucketResponse = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise CreateBucketResponse. Body was: \"{}\"",
text
)
})?;
Ok(resp.name)
}
pub(crate) fn get(
client: &mut ObjectstoreClient,
name: &str,
namespace: &str,
) -> Result<Bucket> {
let request_url = format!(
"{}object/bucket/{}/info.json?namespace={}",
client.endpoint,
name,
namespace,
);
let resp = client
.management_client
.http_client
.get(request_url)
.header(ACCEPT, "application/json")
.header(AUTHORIZATION, client.management_client.access_token.as_ref().unwrap())
.send()?;
let text = get_content_text(resp)?;
let resp: Bucket = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise GetBucket. Body was: \"{}\"",
text
)
})?;
Ok(resp)
}
pub(crate) fn delete(
client: &mut ObjectstoreClient,
name: &str,
namespace: &str,
empty_bucket: bool,
) -> Result<()> {
let request_url = format!(
"{}object/bucket/{}/deactivate.json?namespace={}&emptyBucket={}",
client.endpoint,
name,
namespace,
empty_bucket,
);
let resp = client
.management_client
.http_client
.post(request_url)
.header(ACCEPT, "application/json")
.header(AUTHORIZATION, client.management_client.access_token.as_ref().unwrap())
.send()?;
if !resp.status().is_success() {
bail!("Request failed: {}", resp.text()?);
} else if resp.status().as_u16() == 202 {
bail!("Deletion ongoing");
}
Ok(())
}
pub(crate) fn list(
client: &mut ObjectstoreClient,
namespace: &str,
name_prefix: &str,
) -> Result<Vec<Bucket>> {
let prefix = if name_prefix.is_empty() {
"".to_string()
} else {
format!("&name={}", name_prefix)
};
let request_url = format!(
"{}object/bucket.json?namespace={}{}",
client.endpoint,
namespace,
prefix,
);
let resp = client
.management_client
.http_client
.get(request_url)
.header(ACCEPT, "application/json")
.header(AUTHORIZATION, client.management_client.access_token.as_ref().unwrap())
.send()?;
let text = get_content_text(resp)?;
let mut resp: ListBucketsResponse = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise ListBuckestResponse. Body was: \"{}\"",
text
)
})?;
let mut buckets: Vec<Bucket> = vec![];
buckets.extend(resp.object_bucket_list);
while let Some(marker) = resp.next_marker {
let request_url = format!(
"{}object/bucket.json?namespace={}{}&marker={}",
client.endpoint,
namespace,
prefix,
marker,
);
let response = client
.management_client
.http_client
.get(request_url)
.header(ACCEPT, "application/json")
.header(AUTHORIZATION, client.management_client.access_token.as_ref().unwrap())
.send()?;
let text = get_content_text(response)?;
resp = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise ListBucketsResponse. Body was: \"{}\"",
text
)
})?;
buckets.extend(resp.object_bucket_list);
}
Ok(buckets)
}
}