use std::{collections::BTreeMap, fmt, num::NonZeroU16};
use super::{ByteRange, Checksum, ChecksumAlgorithm, Conditions, ObjectKey, RequestIds};
use crate::stream::{ByteStream, ResponseStream};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ObjectMetadata {
pub e_tag: Option<String>,
pub content_length: u64,
pub content_type: Option<String>,
pub last_modified: Option<time::OffsetDateTime>,
pub version_id: Option<String>,
pub user_metadata: BTreeMap<String, String>,
pub checksum: Checksum,
pub request_ids: RequestIds,
}
pub struct PutObjectRequest {
pub key: ObjectKey,
pub body: ByteStream,
pub content_type: Option<String>,
pub user_metadata: BTreeMap<String, String>,
pub conditions: Conditions,
pub checksum_algorithm: Option<ChecksumAlgorithm>,
}
impl PutObjectRequest {
pub fn new(key: ObjectKey, body: ByteStream) -> Self {
Self {
key,
body,
content_type: None,
user_metadata: BTreeMap::new(),
conditions: Conditions::default(),
checksum_algorithm: None,
}
}
}
impl fmt::Debug for PutObjectRequest {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PutObjectRequest")
.field("key", &self.key)
.field("body", &"<stream>")
.field("content_type", &self.content_type)
.field("user_metadata", &self.user_metadata)
.field("conditions", &self.conditions)
.field("checksum_algorithm", &self.checksum_algorithm)
.finish()
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct PutObjectOutput {
pub e_tag: Option<String>,
pub version_id: Option<String>,
pub checksum: Checksum,
pub request_ids: RequestIds,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GetObjectRequest {
pub key: ObjectKey,
pub range: Option<ByteRange>,
pub conditions: Conditions,
pub version_id: Option<String>,
}
impl GetObjectRequest {
pub fn new(key: ObjectKey) -> Self {
Self {
key,
range: None,
conditions: Conditions::default(),
version_id: None,
}
}
}
pub struct GetObjectOutput {
pub metadata: ObjectMetadata,
pub body: ResponseStream,
pub content_range: Option<(u64, u64, Option<u64>)>,
}
impl fmt::Debug for GetObjectOutput {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("GetObjectOutput")
.field("metadata", &self.metadata)
.field("body", &"<stream>")
.field("content_range", &self.content_range)
.finish()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HeadObjectRequest {
pub key: ObjectKey,
pub conditions: Conditions,
pub version_id: Option<String>,
}
impl HeadObjectRequest {
pub fn new(key: ObjectKey) -> Self {
Self {
key,
conditions: Conditions::default(),
version_id: None,
}
}
}
pub type HeadObjectOutput = ObjectMetadata;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeleteObjectRequest {
pub key: ObjectKey,
pub version_id: Option<String>,
pub if_match: Option<String>,
}
impl DeleteObjectRequest {
pub fn new(key: ObjectKey) -> Self {
Self {
key,
version_id: None,
if_match: None,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DeleteObjectOutput {
pub delete_marker: bool,
pub version_id: Option<String>,
pub request_ids: RequestIds,
}
pub const MAX_DELETE_OBJECTS: usize = 1_000;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeleteObjectsRequest {
objects: Vec<DeleteObjectRequest>,
pub quiet: bool,
}
impl DeleteObjectsRequest {
pub fn new(objects: Vec<DeleteObjectRequest>) -> Result<Self, DeleteObjectsError> {
if objects.is_empty() {
return Err(DeleteObjectsError::Empty);
}
if objects.len() > MAX_DELETE_OBJECTS {
return Err(DeleteObjectsError::TooMany {
actual: objects.len(),
maximum: MAX_DELETE_OBJECTS,
});
}
Ok(Self {
objects,
quiet: false,
})
}
pub fn objects(&self) -> &[DeleteObjectRequest] {
&self.objects
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
pub enum DeleteObjectsError {
#[error("a multi-object delete request cannot be empty")]
Empty,
#[error("multi-object delete has {actual} entries; the maximum is {maximum}")]
TooMany {
actual: usize,
maximum: usize,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeletedObject {
pub key: ObjectKey,
pub version_id: Option<String>,
pub delete_marker: bool,
pub delete_marker_version_id: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeleteError {
pub key: String,
pub version_id: Option<String>,
pub code: Option<String>,
pub message: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DeleteObjectsOutput {
pub deleted: Vec<DeletedObject>,
pub errors: Vec<DeleteError>,
pub request_ids: RequestIds,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CopySource {
pub bucket: String,
pub key: ObjectKey,
pub version_id: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CopyObjectRequest {
pub source: CopySource,
pub destination: ObjectKey,
pub source_conditions: Conditions,
pub content_type: Option<String>,
pub user_metadata: Option<BTreeMap<String, String>>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct CopyObjectOutput {
pub e_tag: Option<String>,
pub last_modified: Option<time::OffsetDateTime>,
pub version_id: Option<String>,
pub checksum: Checksum,
pub request_ids: RequestIds,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PageSize(NonZeroU16);
impl PageSize {
pub const MAX: u16 = 1_000;
pub fn new(value: u16) -> Option<Self> {
NonZeroU16::new(value)
.filter(|value| value.get() <= Self::MAX)
.map(Self)
}
pub const fn get(self) -> u16 {
self.0.get()
}
}
impl Default for PageSize {
fn default() -> Self {
Self(NonZeroU16::new(Self::MAX).expect("page-size maximum must be non-zero"))
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ListObjectsV2Request {
pub prefix: Option<String>,
pub delimiter: Option<String>,
pub continuation_token: Option<String>,
pub start_after: Option<ObjectKey>,
pub max_keys: PageSize,
pub fetch_owner: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ListedObject {
pub key: ObjectKey,
pub last_modified: Option<time::OffsetDateTime>,
pub e_tag: Option<String>,
pub size: u64,
pub storage_class: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ListObjectsV2Output {
pub objects: Vec<ListedObject>,
pub common_prefixes: Vec<String>,
pub is_truncated: bool,
pub next_continuation_token: Option<String>,
pub key_count: Option<u32>,
pub request_ids: RequestIds,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delete_batches_are_strictly_bounded() {
assert_eq!(
DeleteObjectsRequest::new(Vec::new()).unwrap_err(),
DeleteObjectsError::Empty
);
let key = ObjectKey::new("key").unwrap();
let too_many = (0..=MAX_DELETE_OBJECTS)
.map(|_| DeleteObjectRequest::new(key.clone()))
.collect();
assert!(matches!(
DeleteObjectsRequest::new(too_many),
Err(DeleteObjectsError::TooMany { .. })
));
}
#[test]
fn page_sizes_reject_zero_and_values_over_service_limit() {
assert!(PageSize::new(0).is_none());
assert_eq!(PageSize::new(1).unwrap().get(), 1);
assert_eq!(PageSize::new(1_000).unwrap().get(), 1_000);
assert!(PageSize::new(1_001).is_none());
}
}