minor issues (#149)

This commit is contained in:
Henk-Jan Lebbink 2025-04-26 20:55:48 +02:00 committed by GitHub
parent 58d9203153
commit 1869cfeba7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 89 additions and 77 deletions

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::client::MAX_PART_SIZE; use crate::s3::client::{MAX_MULTIPART_COUNT, MAX_PART_SIZE};
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt}; use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::{ use crate::s3::response::{
@ -96,10 +96,11 @@ impl ToS3Request for UploadPartCopy {
if self.upload_id.is_empty() { if self.upload_id.is_empty() {
return Err(Error::InvalidUploadId("upload ID cannot be empty".into())); return Err(Error::InvalidUploadId("upload ID cannot be empty".into()));
} }
if !(1..=10000).contains(&self.part_number) { if !(1..=MAX_MULTIPART_COUNT).contains(&self.part_number) {
return Err(Error::InvalidPartNumber( return Err(Error::InvalidPartNumber(format!(
"part number must be between 1 and 10000".into(), "part number must be between 1 and {}",
)); MAX_MULTIPART_COUNT
)));
} }
} }

View File

@ -415,10 +415,11 @@ impl ToS3Request for UploadPart {
} }
} }
if let Some(part_number) = self.part_number { if let Some(part_number) = self.part_number {
if !(1..=10000).contains(&part_number) { if !(1..=MAX_MULTIPART_COUNT).contains(&part_number) {
return Err(Error::InvalidPartNumber( return Err(Error::InvalidPartNumber(format!(
"part number must be between 1 and 10000".into(), "part number must be between 1 and {}",
)); MAX_MULTIPART_COUNT
)));
} }
} }
} }

View File

@ -23,6 +23,7 @@ use std::pin::Pin;
use tokio_stream::iter as stream_iter; use tokio_stream::iter as stream_iter;
use crate::s3::client::MAX_MULTIPART_COUNT;
use crate::s3::multimap::{Multimap, MultimapExt}; use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::DeleteError; use crate::s3::response::DeleteError;
use crate::s3::types::ListEntry; use crate::s3::types::ListEntry;
@ -36,6 +37,12 @@ use crate::s3::{
}; };
// region: object-to-delete // region: object-to-delete
pub trait ValidKey: Into<String> {}
impl ValidKey for String {}
impl ValidKey for &str {}
impl ValidKey for &String {}
/// Specify an object to be deleted. The object can be specified by key or by /// Specify an object to be deleted. The object can be specified by key or by
/// key and version_id via the From trait. /// key and version_id via the From trait.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -45,30 +52,30 @@ pub struct ObjectToDelete {
} }
/// A key can be converted into a DeleteObject. The version_id is set to None. /// A key can be converted into a DeleteObject. The version_id is set to None.
impl From<&str> for ObjectToDelete { impl<K: ValidKey> From<K> for ObjectToDelete {
fn from(key: &str) -> Self { fn from(key: K) -> Self {
Self { Self {
key: key.to_owned(), key: key.into(),
version_id: None, version_id: None,
} }
} }
} }
/// A tuple of key and version_id can be converted into a DeleteObject. /// A tuple of key and version_id can be converted into a DeleteObject.
impl From<(&str, &str)> for ObjectToDelete { impl<K: ValidKey> From<(K, &str)> for ObjectToDelete {
fn from((key, version_id): (&str, &str)) -> Self { fn from((key, version_id): (K, &str)) -> Self {
Self { Self {
key: key.to_string(), key: key.into(),
version_id: Some(version_id.to_string()), version_id: Some(version_id.to_string()),
} }
} }
} }
/// A tuple of key and option version_id can be converted into a DeleteObject. /// A tuple of key and option version_id can be converted into a DeleteObject.
impl From<(&str, Option<&str>)> for ObjectToDelete { impl<K: ValidKey> From<(K, Option<&str>)> for ObjectToDelete {
fn from((key, version_id): (&str, Option<&str>)) -> Self { fn from((key, version_id): (K, Option<&str>)) -> Self {
Self { Self {
key: key.to_string(), key: key.into(),
version_id: version_id.map(|v| v.to_string()), version_id: version_id.map(|v| v.to_string()),
} }
} }
@ -358,7 +365,7 @@ impl RemoveObjects {
let mut objects = Vec::new(); let mut objects = Vec::new();
while let Some(object) = self.objects.items.next().await { while let Some(object) = self.objects.items.next().await {
objects.push(object); objects.push(object);
if objects.len() >= 1000 { if objects.len() >= MAX_MULTIPART_COUNT as usize {
break; break;
} }
} }

View File

@ -25,10 +25,10 @@ impl Client {
/// This is a lower-level API that performs a non-multipart object upload. /// This is a lower-level API that performs a non-multipart object upload.
/// ///
/// 🛈 This operation is not supported for regular non-express buckets. /// 🛈 This operation is not supported for regular non-express buckets.
pub fn append_object<S: Into<String>>( pub fn append_object<S1: Into<String>, S2: Into<String>>(
&self, &self,
bucket: S, bucket: S1,
object: S, object: S2,
data: SegmentedBytes, data: SegmentedBytes,
offset_bytes: u64, offset_bytes: u64,
) -> AppendObject { ) -> AppendObject {
@ -44,10 +44,10 @@ impl Client {
/// Creates an AppendObjectContent request builder to append data to the end of an existing /// Creates an AppendObjectContent request builder to append data to the end of an existing
/// object. The content is streamed and appended to MinIO/S3. This is a higher-level API that /// object. The content is streamed and appended to MinIO/S3. This is a higher-level API that
/// handles multipart appends transparently. /// handles multipart appends transparently.
pub fn append_object_content<S: Into<String>, C: Into<ObjectContent>>( pub fn append_object_content<S1: Into<String>, S2: Into<String>, C: Into<ObjectContent>>(
&self, &self,
bucket: S, bucket: S1,
object: S, object: S2,
content: C, content: C,
) -> AppendObjectContent { ) -> AppendObjectContent {
AppendObjectContent::new(self.clone(), bucket.into(), object.into(), content) AppendObjectContent::new(self.clone(), bucket.into(), object.into(), content)

View File

@ -44,7 +44,11 @@ impl Client {
/// copy object is a high-order API that calls [`stat_object`] and based on the results calls /// copy object is a high-order API that calls [`stat_object`] and based on the results calls
/// either [`compose_object`] or [`copy_object_internal`] to copy the object. /// either [`compose_object`] or [`copy_object_internal`] to copy the object.
pub fn copy_object<S: Into<String>>(&self, bucket: S, object: S) -> CopyObject { pub fn copy_object<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> CopyObject {
CopyObject::new(self.clone(), bucket.into(), object.into()) CopyObject::new(self.clone(), bucket.into(), object.into())
} }

View File

@ -113,10 +113,10 @@ impl Client {
/// Creates a PutObjectContent request builder to upload data to MinIO/S3. /// Creates a PutObjectContent request builder to upload data to MinIO/S3.
/// The content is streamed, and this higher-level API handles multipart uploads transparently. /// The content is streamed, and this higher-level API handles multipart uploads transparently.
pub fn put_object_content<S: Into<String>, C: Into<ObjectContent>>( pub fn put_object_content<S1: Into<String>, S2: Into<String>, C: Into<ObjectContent>>(
&self, &self,
bucket: S, bucket: S1,
object: S, object: S2,
content: C, content: C,
) -> PutObjectContent { ) -> PutObjectContent {
PutObjectContent::new(self.clone(), bucket.into(), object.into(), content) PutObjectContent::new(self.clone(), bucket.into(), object.into(), content)

View File

@ -48,10 +48,10 @@ impl Client {
/// println!("set the object retention for object '{}'", resp.object); /// println!("set the object retention for object '{}'", resp.object);
/// } /// }
/// ``` /// ```
pub fn set_object_retention<S: Into<String>>( pub fn set_object_retention<S1: Into<String>, S2: Into<String>>(
&self, &self,
bucket: S, bucket: S1,
object: S, object: S2,
) -> SetObjectRetention { ) -> SetObjectRetention {
SetObjectRetention::new(self.clone(), bucket.into(), object.into()) SetObjectRetention::new(self.clone(), bucket.into(), object.into())
} }

View File

@ -23,7 +23,11 @@ impl Client {
/// ///
/// 🛈 This operation is not supported for express buckets. /// 🛈 This operation is not supported for express buckets.
/// ///
pub fn set_object_tags<S: Into<String>>(&self, bucket: S, object: S) -> SetObjectTags { pub fn set_object_tags<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> SetObjectTags {
SetObjectTags::new(self.clone(), bucket.into(), object.into()) SetObjectTags::new(self.clone(), bucket.into(), object.into())
} }
} }

View File

@ -41,7 +41,11 @@ impl Client {
/// println!("stat of object '{}' are {:#?}", resp.object, resp); /// println!("stat of object '{}' are {:#?}", resp.object, resp);
/// } /// }
/// ``` /// ```
pub fn stat_object<S: Into<String>>(&self, bucket: S, object: S) -> StatObject { pub fn stat_object<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> StatObject {
StatObject::new(self.clone(), bucket.into(), object.into()) StatObject::new(self.clone(), bucket.into(), object.into())
} }
} }

View File

@ -178,13 +178,6 @@ pub enum Error {
NoClientProvided, NoClientProvided,
TagDecodingError(String, String), TagDecodingError(String, String),
ContentLengthUnknown, ContentLengthUnknown,
//TODO are the following still needed?
NoSuchTagSet,
ReplicationConfigurationNotFoundError,
NoSuchObjectLockConfiguration,
NoSuchBucketPolicy,
NoSuchBucket,
} }
impl std::error::Error for Error {} impl std::error::Error for Error {}
@ -354,13 +347,6 @@ impl fmt::Display for Error {
error_message, input error_message, input
), ),
Error::ContentLengthUnknown => write!(f, "content length is unknown"), Error::ContentLengthUnknown => write!(f, "content length is unknown"),
Error::NoSuchTagSet => write!(f, "no such tag set"),
Error::ReplicationConfigurationNotFoundError => {
write!(f, "Replication configuration not found")
}
Error::NoSuchObjectLockConfiguration => write!(f, "no such object lock"),
Error::NoSuchBucketPolicy => write!(f, "no such bucket policy"),
Error::NoSuchBucket => write!(f, "no such bucket"),
} }
} }
} }

View File

@ -31,8 +31,8 @@ pub trait MultimapExt {
fn add_multimap(&mut self, other: Multimap); fn add_multimap(&mut self, other: Multimap);
fn add_version(&mut self, version: Option<String>); fn add_version(&mut self, version: Option<String>);
#[must_use]
#[must_use]
fn take_version(self) -> Option<String>; fn take_version(self) -> Option<String>;
/// Converts multimap to HTTP query string /// Converts multimap to HTTP query string

View File

@ -41,7 +41,7 @@ impl FromS3Response for AppendObjectResponse {
resp: Result<reqwest::Response, Error>, resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut resp = resp?; let mut resp = resp?;
let headers = mem::take(resp.headers_mut()); let headers: HeaderMap = mem::take(resp.headers_mut());
let etag: String = match headers.get("etag") { let etag: String = match headers.get("etag") {
Some(v) => v.to_str()?.to_string().trim_matches('"').to_string(), Some(v) => v.to_str()?.to_string().trim_matches('"').to_string(),

View File

@ -53,7 +53,8 @@ impl FromS3Response for GetBucketVersioningResponse {
"Enabled" => VersioningStatus::Enabled, "Enabled" => VersioningStatus::Enabled,
_ => VersioningStatus::Suspended, // Default case _ => VersioningStatus::Suspended, // Default case
}); });
let mfa_delete: Option<bool> = get_option_text(&root, "MFADelete").map(|v| v == "Enabled"); let mfa_delete: Option<bool> =
get_option_text(&root, "MFADelete").map(|v| v.eq_ignore_ascii_case("Enabled"));
Ok(Self { Ok(Self {
headers, headers,

View File

@ -44,7 +44,7 @@ impl FromS3Response for GetObjectResponse {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut resp = resp?; let mut resp = resp?;
let headers = mem::take(resp.headers_mut()); let headers: HeaderMap = mem::take(resp.headers_mut());
let etag: Option<String> = headers let etag: Option<String> = headers
.get("etag") .get("etag")

View File

@ -42,7 +42,7 @@ impl FromS3Response for GetObjectLockConfigResponse {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut resp = resp?; let mut resp = resp?;
let headers = mem::take(resp.headers_mut()); let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?; let body = resp.bytes().await?;
let root = Element::parse(body.reader())?; let root = Element::parse(body.reader())?;

View File

@ -208,7 +208,7 @@ impl FromS3Response for ListObjectsV1Response {
resp: Result<reqwest::Response, Error>, resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut resp = resp?; let mut resp = resp?;
let headers = mem::take(resp.headers_mut()); let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?; let body = resp.bytes().await?;
let xmltree_root = xmltree::Element::parse(body.reader())?; let xmltree_root = xmltree::Element::parse(body.reader())?;
let root = Element::from(&xmltree_root); let root = Element::from(&xmltree_root);

View File

@ -37,7 +37,7 @@ impl FromS3Response for ObjectPromptResponse {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut resp = resp?; let mut resp = resp?;
let headers = mem::take(resp.headers_mut()); let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?; let body = resp.bytes().await?;
let prompt_response: String = String::from_utf8(body.to_vec())?; let prompt_response: String = String::from_utf8(body.to_vec())?;

View File

@ -85,12 +85,6 @@ impl FromS3Response for CreateMultipartUploadResponse {
req: S3Request, req: S3Request,
resp: Result<reqwest::Response, Error>, resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let bucket = req
.bucket
.ok_or_else(|| Error::InvalidBucketName("no bucket specified".into()))?;
let object = req
.object
.ok_or_else(|| Error::InvalidObjectName("no object specified".into()))?;
let mut resp = resp?; let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut()); let headers: HeaderMap = mem::take(resp.headers_mut());
@ -99,11 +93,11 @@ impl FromS3Response for CreateMultipartUploadResponse {
let upload_id: String = let upload_id: String =
get_text(&root, "UploadId").map_err(|e| Error::InvalidUploadId(e.to_string()))?; get_text(&root, "UploadId").map_err(|e| Error::InvalidUploadId(e.to_string()))?;
Ok(CreateMultipartUploadResponse { Ok(Self {
headers, headers,
region: req.inner_region, region: req.inner_region,
bucket, bucket: take_bucket(req.bucket)?,
object, object: take_object(req.object)?,
upload_id, upload_id,
}) })
} }

View File

@ -493,7 +493,7 @@ pub mod xml {
impl Element<'_> { impl Element<'_> {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
self.inner.name.as_str() &self.inner.name
} }
pub fn get_child_text(&self, tag: &str) -> Option<String> { pub fn get_child_text(&self, tag: &str) -> Option<String> {

View File

@ -351,7 +351,7 @@ async fn append_object_content_3() {
assert_eq!(resp.size, sizes[idx] + initial_size); assert_eq!(resp.size, sizes[idx] + initial_size);
assert_eq!(resp.etag, etag); assert_eq!(resp.etag, etag);
client client
.remove_object(&test_bucket, object_name.as_str()) .remove_object(&test_bucket, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();

View File

@ -192,7 +192,7 @@ async fn put_object_content_2() {
assert_eq!(resp.size, sizes[idx]); assert_eq!(resp.size, sizes[idx]);
assert_eq!(resp.etag, etag); assert_eq!(resp.etag, etag);
client client
.remove_object(&test_bucket, object_name.as_str()) .remove_object(&test_bucket, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();

View File

@ -30,7 +30,8 @@ async fn put_object() {
let object_name = rand_object_name(); let object_name = rand_object_name();
let size = 16_u64; let size = 16_u64;
ctx.client let resp: PutObjectContentResponse = ctx
.client
.put_object_content( .put_object_content(
&bucket_name, &bucket_name,
&object_name, &object_name,
@ -39,7 +40,11 @@ async fn put_object() {
.send() .send()
.await .await
.unwrap(); .unwrap();
let resp = ctx assert_eq!(resp.bucket, bucket_name);
assert_eq!(resp.object, object_name);
assert_eq!(resp.object_size, size);
let resp: StatObjectResponse = ctx
.client .client
.stat_object(&bucket_name, &object_name) .stat_object(&bucket_name, &object_name)
.send() .send()
@ -47,18 +52,23 @@ async fn put_object() {
.unwrap(); .unwrap();
assert_eq!(resp.bucket, bucket_name); assert_eq!(resp.bucket, bucket_name);
assert_eq!(resp.object, object_name); assert_eq!(resp.object, object_name);
assert_eq!(resp.size as u64, size); assert_eq!(resp.size, size);
ctx.client
.remove_object(&bucket_name, object_name.as_str()) let resp: RemoveObjectResponse = ctx
.client
.remove_object(&bucket_name, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();
assert!(!resp.version_id.is_some());
// Validate delete succeeded. // Validate delete succeeded.
let resp = ctx let resp: Result<StatObjectResponse, Error> = ctx
.client .client
.stat_object(&bucket_name, &object_name) .stat_object(&bucket_name, &object_name)
.send() .send()
.await; .await;
match resp.err().unwrap() { match resp.err().unwrap() {
Error::S3Error(er) => { Error::S3Error(er) => {
assert_eq!(er.code, ErrorCode::NoSuchKey) assert_eq!(er.code, ErrorCode::NoSuchKey)
@ -94,7 +104,7 @@ async fn put_object_multipart() {
assert_eq!(resp.object, object_name); assert_eq!(resp.object, object_name);
assert_eq!(resp.size as u64, size); assert_eq!(resp.size as u64, size);
ctx.client ctx.client
.remove_object(&bucket_name, object_name.as_str()) .remove_object(&bucket_name, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -135,7 +145,7 @@ async fn put_object_content_1() {
); );
let resp: RemoveObjectResponse = ctx let resp: RemoveObjectResponse = ctx
.client .client
.remove_object(&bucket_name, object_name.as_str()) .remove_object(&bucket_name, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -175,7 +185,7 @@ async fn put_object_content_2() {
assert_eq!(resp.size, *size); assert_eq!(resp.size, *size);
assert_eq!(resp.etag, etag); assert_eq!(resp.etag, etag);
ctx.client ctx.client
.remove_object(&bucket_name, object_name.as_str()) .remove_object(&bucket_name, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -229,7 +239,7 @@ async fn put_object_content_3() {
assert_eq!(resp.size, sizes[idx]); assert_eq!(resp.size, sizes[idx]);
assert_eq!(resp.etag, etag); assert_eq!(resp.etag, etag);
client client
.remove_object(&test_bucket, object_name.as_str()) .remove_object(&test_bucket, &object_name)
.send() .send()
.await .await
.unwrap(); .unwrap();