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.
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::multimap::{Multimap, MultimapExt};
use crate::s3::response::{
@ -96,10 +96,11 @@ impl ToS3Request for UploadPartCopy {
if self.upload_id.is_empty() {
return Err(Error::InvalidUploadId("upload ID cannot be empty".into()));
}
if !(1..=10000).contains(&self.part_number) {
return Err(Error::InvalidPartNumber(
"part number must be between 1 and 10000".into(),
));
if !(1..=MAX_MULTIPART_COUNT).contains(&self.part_number) {
return Err(Error::InvalidPartNumber(format!(
"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 !(1..=10000).contains(&part_number) {
return Err(Error::InvalidPartNumber(
"part number must be between 1 and 10000".into(),
));
if !(1..=MAX_MULTIPART_COUNT).contains(&part_number) {
return Err(Error::InvalidPartNumber(format!(
"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 crate::s3::client::MAX_MULTIPART_COUNT;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::DeleteError;
use crate::s3::types::ListEntry;
@ -36,6 +37,12 @@ use crate::s3::{
};
// 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
/// key and version_id via the From trait.
#[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.
impl From<&str> for ObjectToDelete {
fn from(key: &str) -> Self {
impl<K: ValidKey> From<K> for ObjectToDelete {
fn from(key: K) -> Self {
Self {
key: key.to_owned(),
key: key.into(),
version_id: None,
}
}
}
/// A tuple of key and version_id can be converted into a DeleteObject.
impl From<(&str, &str)> for ObjectToDelete {
fn from((key, version_id): (&str, &str)) -> Self {
impl<K: ValidKey> From<(K, &str)> for ObjectToDelete {
fn from((key, version_id): (K, &str)) -> Self {
Self {
key: key.to_string(),
key: key.into(),
version_id: Some(version_id.to_string()),
}
}
}
/// A tuple of key and option version_id can be converted into a DeleteObject.
impl From<(&str, Option<&str>)> for ObjectToDelete {
fn from((key, version_id): (&str, Option<&str>)) -> Self {
impl<K: ValidKey> From<(K, Option<&str>)> for ObjectToDelete {
fn from((key, version_id): (K, Option<&str>)) -> Self {
Self {
key: key.to_string(),
key: key.into(),
version_id: version_id.map(|v| v.to_string()),
}
}
@ -358,7 +365,7 @@ impl RemoveObjects {
let mut objects = Vec::new();
while let Some(object) = self.objects.items.next().await {
objects.push(object);
if objects.len() >= 1000 {
if objects.len() >= MAX_MULTIPART_COUNT as usize {
break;
}
}

View File

@ -25,10 +25,10 @@ impl Client {
/// This is a lower-level API that performs a non-multipart object upload.
///
/// 🛈 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,
bucket: S,
object: S,
bucket: S1,
object: S2,
data: SegmentedBytes,
offset_bytes: u64,
) -> AppendObject {
@ -44,10 +44,10 @@ impl Client {
/// 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
/// 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,
bucket: S,
object: S,
bucket: S1,
object: S2,
content: C,
) -> AppendObjectContent {
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
/// 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())
}

View File

@ -113,10 +113,10 @@ impl Client {
/// Creates a PutObjectContent request builder to upload data to MinIO/S3.
/// 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,
bucket: S,
object: S,
bucket: S1,
object: S2,
content: C,
) -> PutObjectContent {
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);
/// }
/// ```
pub fn set_object_retention<S: Into<String>>(
pub fn set_object_retention<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S,
object: S,
bucket: S1,
object: S2,
) -> SetObjectRetention {
SetObjectRetention::new(self.clone(), bucket.into(), object.into())
}

View File

@ -23,7 +23,11 @@ impl Client {
///
/// 🛈 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())
}
}

View File

@ -41,7 +41,11 @@ impl Client {
/// 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())
}
}

View File

@ -178,13 +178,6 @@ pub enum Error {
NoClientProvided,
TagDecodingError(String, String),
ContentLengthUnknown,
//TODO are the following still needed?
NoSuchTagSet,
ReplicationConfigurationNotFoundError,
NoSuchObjectLockConfiguration,
NoSuchBucketPolicy,
NoSuchBucket,
}
impl std::error::Error for Error {}
@ -354,13 +347,6 @@ impl fmt::Display for Error {
error_message, input
),
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_version(&mut self, version: Option<String>);
#[must_use]
#[must_use]
fn take_version(self) -> Option<String>;
/// Converts multimap to HTTP query string

View File

@ -41,7 +41,7 @@ impl FromS3Response for AppendObjectResponse {
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
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") {
Some(v) => v.to_str()?.to_string().trim_matches('"').to_string(),

View File

@ -53,7 +53,8 @@ impl FromS3Response for GetBucketVersioningResponse {
"Enabled" => VersioningStatus::Enabled,
_ => 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 {
headers,

View File

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

View File

@ -42,7 +42,7 @@ impl FromS3Response for GetObjectLockConfigResponse {
) -> Result<Self, Error> {
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 root = Element::parse(body.reader())?;

View File

@ -208,7 +208,7 @@ impl FromS3Response for ListObjectsV1Response {
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
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 xmltree_root = xmltree::Element::parse(body.reader())?;
let root = Element::from(&xmltree_root);

View File

@ -37,7 +37,7 @@ impl FromS3Response for ObjectPromptResponse {
) -> Result<Self, Error> {
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 prompt_response: String = String::from_utf8(body.to_vec())?;

View File

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

View File

@ -493,7 +493,7 @@ pub mod xml {
impl Element<'_> {
pub fn name(&self) -> &str {
self.inner.name.as_str()
&self.inner.name
}
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.etag, etag);
client
.remove_object(&test_bucket, object_name.as_str())
.remove_object(&test_bucket, &object_name)
.send()
.await
.unwrap();

View File

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

View File

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