use std::future::Future;
use std::str::FromStr;
use http::{HeaderMap, Method, header};
use jiff::Timestamp;
use serde::{Deserialize, Deserializer, Serialize};
use crate::body::NoneBody;
use crate::error::Result;
use crate::ops::common::{ObjectType, ServerSideEncryption, StorageClass};
use crate::response::HeaderResponseProcessor;
use crate::{Client, Ops, Prepared, Request};
#[derive(Debug, Clone, Deserialize)]
pub struct RestoreInfo {
pub ongoing_request: bool,
pub expiry_date: Option<String>,
}
impl FromStr for RestoreInfo {
type Err = crate::error::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let ongoing_request_regex = regex::Regex::new(r#"ongoing-request="([^"]+)""#).unwrap();
let expiry_date_regex = regex::Regex::new(r#"expiry-date="([^"]+)""#).unwrap();
let mut ongoing_request = false;
let mut expiry_date = None;
for part in s.split(',') {
let part = part.trim();
if let Some(captures) = ongoing_request_regex.captures(part) {
ongoing_request = captures.get(1).unwrap().as_str() == "true";
} else if let Some(captures) = expiry_date_regex.captures(part) {
expiry_date = Some(captures.get(1).unwrap().as_str().to_string());
}
}
Ok(RestoreInfo {
ongoing_request,
expiry_date,
})
}
}
fn deserialize_content_length<'de, D>(deserializer: D) -> std::result::Result<u64, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
fn deserialize_datetime<'de, D>(deserializer: D) -> std::result::Result<Timestamp, D::Error>
where
D: Deserializer<'de>,
{
use jiff::fmt::rfc2822;
const RFC2822_PARSER: rfc2822::DateTimeParser = rfc2822::DateTimeParser::new();
let s = String::deserialize(deserializer)?;
let timestamp = RFC2822_PARSER
.parse_timestamp(s)
.map_err(serde::de::Error::custom)?;
Ok(timestamp)
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct HeadObjectResponse {
#[serde(deserialize_with = "deserialize_content_length")]
pub content_length: u64,
pub content_type: String,
#[serde(deserialize_with = "deserialize_datetime")]
pub date: Timestamp,
#[serde(deserialize_with = "deserialize_datetime")]
pub last_modified: Timestamp,
#[serde(rename = "etag")]
pub etag: Option<String>,
#[serde(rename = "x-oss-versionId")]
pub version_id: Option<String>,
#[serde(rename = "x-oss-object-type")]
pub object_type: Option<ObjectType>,
#[serde(rename = "x-oss-storage-class")]
pub storage_class: Option<StorageClass>,
#[serde(rename = "x-oss-server-side-encryption")]
pub server_side_encryption: Option<ServerSideEncryption>,
#[serde(rename = "x-oss-server-side-encryption-key-id")]
pub server_side_encryption_key_id: Option<String>,
#[serde(rename = "x-oss-next-append-position")]
pub next_append_position: Option<u64>,
#[serde(rename = "x-oss-hash-crc64ecma")]
pub hash_crc64ecma: Option<String>,
#[serde(rename = "x-oss-tagging-count")]
pub tagging_count: Option<u32>,
#[serde(rename = "x-oss-expiration")]
pub expiration: Option<String>,
#[serde(rename = "x-oss-restore")]
pub restore: Option<RestoreInfo>,
#[serde(rename = "x-oss-meta-source")]
pub source: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct HeadObjectParams {
pub version_id: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct HeadObjectOptions {
pub if_modified_since: Option<String>,
pub if_unmodified_since: Option<String>,
pub if_match: Option<String>,
pub if_none_match: Option<String>,
}
impl HeadObjectOptions {
fn into_headers(self) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
if let Some(if_modified_since) = &self.if_modified_since {
headers.insert(header::IF_MODIFIED_SINCE, if_modified_since.parse()?);
}
if let Some(if_unmodified_since) = &self.if_unmodified_since {
headers.insert(header::IF_UNMODIFIED_SINCE, if_unmodified_since.parse()?);
}
if let Some(if_match) = &self.if_match {
headers.insert(header::IF_MATCH, if_match.parse()?);
}
if let Some(if_none_match) = &self.if_none_match {
headers.insert(header::IF_NONE_MATCH, if_none_match.parse()?);
}
Ok(headers)
}
}
#[derive(Debug, Clone)]
pub struct HeadObjectRequestBuilder {
pub if_modified_since: Option<String>,
pub if_unmodified_since: Option<String>,
pub if_match: Option<String>,
pub if_none_match: Option<String>,
}
impl HeadObjectRequestBuilder {
pub fn new() -> Self {
Self {
if_modified_since: None,
if_unmodified_since: None,
if_match: None,
if_none_match: None,
}
}
pub fn if_modified_since(mut self, time: impl Into<String>) -> Self {
self.if_modified_since = Some(time.into());
self
}
pub fn if_unmodified_since(mut self, time: impl Into<String>) -> Self {
self.if_unmodified_since = Some(time.into());
self
}
pub fn if_match(mut self, etag: impl Into<String>) -> Self {
self.if_match = Some(etag.into());
self
}
pub fn if_none_match(mut self, etag: impl Into<String>) -> Self {
self.if_none_match = Some(etag.into());
self
}
pub fn build(self) -> HeadObjectOptions {
HeadObjectOptions {
if_modified_since: self.if_modified_since,
if_unmodified_since: self.if_unmodified_since,
if_match: self.if_match,
if_none_match: self.if_none_match,
}
}
}
impl Default for HeadObjectRequestBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct HeadObject {
pub object_name: String,
pub params: HeadObjectParams,
pub options: HeadObjectOptions,
}
impl Ops for HeadObject {
type Response = HeaderResponseProcessor<HeadObjectResponse>;
type Body = NoneBody;
type Query = HeadObjectParams;
fn prepare(self) -> Result<Prepared<HeadObjectParams>> {
Ok(Prepared {
method: Method::HEAD,
key: Some(self.object_name),
query: Some(self.params),
headers: Some(self.options.into_headers()?),
..Default::default()
})
}
}
pub trait HeadObjectOperations {
fn head_object(
&self,
object_name: impl Into<String>,
params: HeadObjectParams,
options: Option<HeadObjectOptions>,
) -> impl Future<Output = Result<HeadObjectResponse>>;
}
impl HeadObjectOperations for Client {
async fn head_object(
&self,
object_name: impl Into<String>,
params: HeadObjectParams,
options: Option<HeadObjectOptions>,
) -> Result<HeadObjectResponse> {
let ops = HeadObject {
object_name: object_name.into(),
params,
options: options.unwrap_or_default(),
};
self.request(ops).await
}
}