use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::Arc;
use crate::error::{InnerErrorCode, MeowError};
use super::{CompletionRequest, PresignedCompletionBodyBuilder, PresignedUploadPart};
#[derive(Clone)]
pub struct PresignedMultipartUploadPlan {
pub upload_id: Option<String>,
pub metadata: BTreeMap<String, String>,
pub total_size: u64,
pub chunk_size: u64,
pub parts: Vec<PresignedUploadPart>,
pub complete_request: Option<CompletionRequest>,
pub abort_request: Option<CompletionRequest>,
pub complete_body_builder: Option<Arc<dyn PresignedCompletionBodyBuilder>>,
pub refresh_before_secs: u64,
}
impl fmt::Debug for PresignedMultipartUploadPlan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PresignedMultipartUploadPlan")
.field("upload_id", &self.upload_id)
.field("metadata", &self.metadata)
.field("total_size", &self.total_size)
.field("chunk_size", &self.chunk_size)
.field("parts", &self.parts)
.field("complete_request", &self.complete_request)
.field("abort_request", &self.abort_request)
.field(
"complete_body_builder",
&self.complete_body_builder.as_ref().map(|_| "<builder>"),
)
.field("refresh_before_secs", &self.refresh_before_secs)
.finish()
}
}
impl PresignedMultipartUploadPlan {
pub fn new(total_size: u64, chunk_size: u64, parts: Vec<PresignedUploadPart>) -> Self {
Self {
upload_id: None,
metadata: BTreeMap::new(),
total_size,
chunk_size,
parts,
complete_request: None,
abort_request: None,
complete_body_builder: None,
refresh_before_secs: 60,
}
}
pub fn with_upload_id(mut self, upload_id: impl Into<String>) -> Self {
self.upload_id = Some(upload_id.into());
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn with_complete_request(mut self, req: CompletionRequest) -> Self {
self.complete_request = Some(req);
self
}
pub fn with_complete_body_builder(
mut self,
builder: Arc<dyn PresignedCompletionBodyBuilder>,
) -> Self {
self.complete_body_builder = Some(builder);
self
}
pub fn with_abort_request(mut self, req: CompletionRequest) -> Self {
self.abort_request = Some(req);
self
}
pub fn with_refresh_before_secs(mut self, secs: u64) -> Self {
self.refresh_before_secs = secs;
self
}
pub fn validate(&self) -> Result<(), MeowError> {
if self.chunk_size == 0 {
return Err(MeowError::from_code_str(
InnerErrorCode::ParameterEmpty,
"presigned plan chunk_size must be greater than zero",
));
}
if self.total_size > 0 && self.parts.is_empty() {
return Err(MeowError::from_code_str(
InnerErrorCode::ParameterEmpty,
"presigned plan parts must not be empty for non-empty upload",
));
}
let mut offsets = BTreeSet::new();
for part in &self.parts {
if part.size == 0 {
return Err(MeowError::from_code(
InnerErrorCode::InvalidRange,
format!("presigned part {} has zero size", part.part_number),
));
}
let end = part.offset.checked_add(part.size).ok_or_else(|| {
MeowError::from_code(
InnerErrorCode::InvalidRange,
format!("presigned part {} range overflow", part.part_number),
)
})?;
if end > self.total_size {
return Err(MeowError::from_code(
InnerErrorCode::InvalidRange,
format!(
"presigned part {} out of range: end={} total={}",
part.part_number, end, self.total_size
),
));
}
if !offsets.insert(part.offset) {
return Err(MeowError::from_code(
InnerErrorCode::InvalidRange,
format!("duplicate presigned part offset: {}", part.offset),
));
}
}
Ok(())
}
pub(crate) fn part_for_offset(&self, offset: u64) -> Option<&PresignedUploadPart> {
self.parts.iter().find(|p| p.offset == offset)
}
}