mirror of
https://github.com/minio/minio-rs.git
synced 2025-12-06 15:26:51 +08:00
tests cleanup; cargo clippy fixes, minor doc updates (#177)
* Tests cleanup; cargo clippy fixes, minor doc updates * updated label checker workflow
This commit is contained in:
parent
e0a77fcb1a
commit
e244229490
10
.github/workflows/label-checker.yaml
vendored
10
.github/workflows/label-checker.yaml
vendored
@ -1,19 +1,17 @@
|
|||||||
name: Label Checker
|
name: Label Checker
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
- opened
|
- opened
|
||||||
- synchronize
|
- synchronize
|
||||||
- labeled
|
- labeled
|
||||||
- unlabeled
|
- unlabeled
|
||||||
|
jobs:
|
||||||
jobs:
|
|
||||||
|
|
||||||
check_labels:
|
check_labels:
|
||||||
name: Check for labels
|
name: Check for labels
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: docker://agilepathway/pull-request-label-checker:latest
|
- uses: docker://agilepathway/pull-request-label-checker:latest
|
||||||
with:
|
with:
|
||||||
one_of: highlight,breaking-change,security-fix,enhancement,bug
|
any_of: highlight,breaking-change,security-fix,enhancement,bug,cleanup-rewrite,regression-fix,codex
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -41,9 +41,10 @@ hmac = { version = "0.12.1", optional = true }
|
|||||||
hyper = { version = "1.6.0", features = ["full"] }
|
hyper = { version = "1.6.0", features = ["full"] }
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
md5 = "0.7.0"
|
md5 = "0.8.0"
|
||||||
multimap = "0.10.1"
|
multimap = "0.10.1"
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
|
url = "2.5.4"
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
ring = { version = "0.17.14", optional = true, default-features = false, features = ["alloc"] }
|
ring = { version = "0.17.14", optional = true, default-features = false, features = ["alloc"] }
|
||||||
|
|||||||
@ -14,6 +14,7 @@ chrono = "0.4.41"
|
|||||||
reqwest = "0.12.20"
|
reqwest = "0.12.20"
|
||||||
http = "1.3.1"
|
http = "1.3.1"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
uuid = { version = "1.17.0", features = ["v4"] }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "minio_common"
|
name = "minio_common"
|
||||||
|
|||||||
@ -15,16 +15,24 @@
|
|||||||
|
|
||||||
use http::{Response as HttpResponse, StatusCode};
|
use http::{Response as HttpResponse, StatusCode};
|
||||||
use minio::s3::error::Error;
|
use minio::s3::error::Error;
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::Standard;
|
||||||
|
use rand::{Rng, thread_rng};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn rand_bucket_name() -> String {
|
pub fn rand_bucket_name() -> String {
|
||||||
Alphanumeric
|
format!("test-bucket-{}", Uuid::new_v4())
|
||||||
.sample_string(&mut rand::thread_rng(), 8)
|
|
||||||
.to_lowercase()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rand_object_name() -> String {
|
pub fn rand_object_name() -> String {
|
||||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 20)
|
format!("test-object-{}", Uuid::new_v4())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rand_object_name_utf8(len: usize) -> String {
|
||||||
|
let rng = thread_rng();
|
||||||
|
rng.sample_iter::<char, _>(Standard)
|
||||||
|
.filter(|c| !c.is_control())
|
||||||
|
.take(len)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_bytes_from_response(v: Result<reqwest::Response, Error>) -> bytes::Bytes {
|
pub async fn get_bytes_from_response(v: Result<reqwest::Response, Error>) -> bytes::Bytes {
|
||||||
|
|||||||
@ -209,7 +209,7 @@ impl DeleteObjects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enable verbose mode (defaults to false). If enabled, the response will
|
/// Enable verbose mode (defaults to false). If enabled, the response will
|
||||||
/// include the keys of objects that were successfully deleted. Otherwise
|
/// include the keys of objects that were successfully deleted. Otherwise,
|
||||||
/// only objects that encountered an error are returned.
|
/// only objects that encountered an error are returned.
|
||||||
pub fn verbose_mode(mut self, verbose_mode: bool) -> Self {
|
pub fn verbose_mode(mut self, verbose_mode: bool) -> Self {
|
||||||
self.verbose_mode = verbose_mode;
|
self.verbose_mode = verbose_mode;
|
||||||
|
|||||||
@ -57,35 +57,33 @@ impl Client {
|
|||||||
bucket: S,
|
bucket: S,
|
||||||
) -> Result<DeleteBucketResponse, Error> {
|
) -> Result<DeleteBucketResponse, Error> {
|
||||||
let bucket: String = bucket.into();
|
let bucket: String = bucket.into();
|
||||||
if self.is_minio_express().await {
|
let is_express = self.is_minio_express().await;
|
||||||
let mut stream = self.list_objects(&bucket).to_stream().await;
|
|
||||||
|
|
||||||
while let Some(items) = stream.next().await {
|
let mut stream = self
|
||||||
let mut resp = self
|
.list_objects(&bucket)
|
||||||
.delete_objects_streaming(
|
.include_versions(!is_express)
|
||||||
&bucket,
|
.recursive(true)
|
||||||
items?.contents.into_iter().map(ObjectToDelete::from),
|
|
||||||
)
|
|
||||||
.to_stream()
|
.to_stream()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if is_express {
|
||||||
|
while let Some(items) = stream.next().await {
|
||||||
|
let object_names = items?.contents.into_iter().map(ObjectToDelete::from);
|
||||||
|
let mut resp = self
|
||||||
|
.delete_objects_streaming(&bucket, object_names)
|
||||||
|
.bypass_governance_mode(false) // Express does not support governance mode
|
||||||
|
.to_stream()
|
||||||
|
.await;
|
||||||
|
|
||||||
while let Some(item) = resp.next().await {
|
while let Some(item) = resp.next().await {
|
||||||
let _resp: DeleteObjectsResponse = item?;
|
let _resp: DeleteObjectsResponse = item?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut stream = self
|
|
||||||
.list_objects(&bucket)
|
|
||||||
.include_versions(true)
|
|
||||||
.recursive(true)
|
|
||||||
.to_stream()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
while let Some(items) = stream.next().await {
|
while let Some(items) = stream.next().await {
|
||||||
|
let object_names = items?.contents.into_iter().map(ObjectToDelete::from);
|
||||||
let mut resp = self
|
let mut resp = self
|
||||||
.delete_objects_streaming(
|
.delete_objects_streaming(&bucket, object_names)
|
||||||
&bucket,
|
|
||||||
items?.contents.into_iter().map(ObjectToDelete::from),
|
|
||||||
)
|
|
||||||
.bypass_governance_mode(true)
|
.bypass_governance_mode(true)
|
||||||
.to_stream()
|
.to_stream()
|
||||||
.await;
|
.await;
|
||||||
@ -117,16 +115,41 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let request: DeleteBucket = self.delete_bucket(bucket);
|
|
||||||
|
let request: DeleteBucket = self.delete_bucket(&bucket);
|
||||||
match request.send().await {
|
match request.send().await {
|
||||||
Ok(resp) => Ok(resp),
|
Ok(resp) => Ok(resp),
|
||||||
Err(Error::S3Error(e)) => {
|
Err(Error::S3Error(mut e)) => {
|
||||||
if e.code == ErrorCode::NoSuchBucket {
|
if matches!(e.code, ErrorCode::NoSuchBucket) {
|
||||||
Ok(DeleteBucketResponse {
|
Ok(DeleteBucketResponse {
|
||||||
request: Default::default(), //TODO consider how to handle this
|
request: Default::default(), //TODO consider how to handle this
|
||||||
body: Bytes::new(),
|
body: Bytes::new(),
|
||||||
headers: e.headers,
|
headers: e.headers,
|
||||||
})
|
})
|
||||||
|
} else if let ErrorCode::BucketNotEmpty(reason) = &e.code {
|
||||||
|
// for convenience, add the first 5 documents that were are still in the bucket
|
||||||
|
// to the error message
|
||||||
|
let mut stream = self
|
||||||
|
.list_objects(&bucket)
|
||||||
|
.include_versions(!is_express)
|
||||||
|
.recursive(true)
|
||||||
|
.to_stream()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut objs = Vec::new();
|
||||||
|
while let Some(items_result) = stream.next().await {
|
||||||
|
if let Ok(items) = items_result {
|
||||||
|
objs.extend(items.contents);
|
||||||
|
if objs.len() >= 5 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else: silently ignore the error and keep looping
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_reason = format!("{reason}: found content: {objs:?}");
|
||||||
|
e.code = ErrorCode::BucketNotEmpty(new_reason);
|
||||||
|
Err(Error::S3Error(e))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::S3Error(e))
|
Err(Error::S3Error(e))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ impl Client {
|
|||||||
|
|
||||||
/// Creates a [`DeleteObjectsStreaming`] request builder to delete a stream of objects from an S3 bucket.
|
/// Creates a [`DeleteObjectsStreaming`] request builder to delete a stream of objects from an S3 bucket.
|
||||||
///
|
///
|
||||||
/// to execute the request, call [`DeleteObjectsStreaming::to_stream()`](crate::s3::types::S3Api::send),
|
/// To execute the request, call [`DeleteObjectsStreaming::to_stream()`](crate::s3::types::S3Api::send),
|
||||||
/// which returns a [`Result`] containing a [`DeleteObjectsResponse`](crate::s3::response::DeleteObjectsResponse).
|
/// which returns a [`Result`] containing a [`DeleteObjectsResponse`](crate::s3::response::DeleteObjectsResponse).
|
||||||
pub fn delete_objects_streaming<S: Into<String>, D: Into<ObjectsStream>>(
|
pub fn delete_objects_streaming<S: Into<String>, D: Into<ObjectsStream>>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@ -44,7 +44,7 @@ pub enum ErrorCode {
|
|||||||
ResourceConflict,
|
ResourceConflict,
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
NotSupported,
|
NotSupported,
|
||||||
BucketNotEmpty,
|
BucketNotEmpty(String), // String contains optional reason msg
|
||||||
BucketAlreadyOwnedByYou,
|
BucketAlreadyOwnedByYou,
|
||||||
InvalidWriteOffset,
|
InvalidWriteOffset,
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ impl ErrorCode {
|
|||||||
"resourceconflict" => ErrorCode::ResourceConflict,
|
"resourceconflict" => ErrorCode::ResourceConflict,
|
||||||
"accessdenied" => ErrorCode::AccessDenied,
|
"accessdenied" => ErrorCode::AccessDenied,
|
||||||
"notsupported" => ErrorCode::NotSupported,
|
"notsupported" => ErrorCode::NotSupported,
|
||||||
"bucketnotempty" => ErrorCode::BucketNotEmpty,
|
"bucketnotempty" => ErrorCode::BucketNotEmpty("".to_string()),
|
||||||
"bucketalreadyownedbyyou" => ErrorCode::BucketAlreadyOwnedByYou,
|
"bucketalreadyownedbyyou" => ErrorCode::BucketAlreadyOwnedByYou,
|
||||||
"invalidwriteoffset" => ErrorCode::InvalidWriteOffset,
|
"invalidwriteoffset" => ErrorCode::InvalidWriteOffset,
|
||||||
|
|
||||||
|
|||||||
@ -480,5 +480,5 @@ impl LifecycleRule {
|
|||||||
fn parse_iso8601(date_str: &str) -> Result<chrono::DateTime<chrono::Utc>, Error> {
|
fn parse_iso8601(date_str: &str) -> Result<chrono::DateTime<chrono::Utc>, Error> {
|
||||||
chrono::DateTime::parse_from_rfc3339(date_str)
|
chrono::DateTime::parse_from_rfc3339(date_str)
|
||||||
.map(|dt| dt.with_timezone(&chrono::Utc))
|
.map(|dt| dt.with_timezone(&chrono::Utc))
|
||||||
.map_err(|_| Error::XmlError(format!("Invalid date format: {}", date_str)))
|
.map_err(|_| Error::XmlError(format!("Invalid date format: {date_str}")))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ impl FromS3Response for BucketExistsResponse {
|
|||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
exists: true,
|
exists: true,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucket => Ok(Self {
|
Err(Error::S3Error(e)) if matches!(e.code, ErrorCode::NoSuchBucket) => Ok(Self {
|
||||||
request,
|
request,
|
||||||
headers: e.headers,
|
headers: e.headers,
|
||||||
body: Bytes::new(),
|
body: Bytes::new(),
|
||||||
|
|||||||
@ -48,7 +48,7 @@ impl FromS3Response for DeleteBucketPolicyResponse {
|
|||||||
headers: mem::take(resp.headers_mut()),
|
headers: mem::take(resp.headers_mut()),
|
||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucketPolicy => Ok(Self {
|
Err(Error::S3Error(e)) if matches!(e.code, ErrorCode::NoSuchBucketPolicy) => Ok(Self {
|
||||||
request,
|
request,
|
||||||
headers: e.headers,
|
headers: e.headers,
|
||||||
body: Bytes::new(),
|
body: Bytes::new(),
|
||||||
|
|||||||
@ -49,7 +49,7 @@ impl FromS3Response for DeleteBucketReplicationResponse {
|
|||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e))
|
Err(Error::S3Error(e))
|
||||||
if e.code == ErrorCode::ReplicationConfigurationNotFoundError =>
|
if matches!(e.code, ErrorCode::ReplicationConfigurationNotFoundError) =>
|
||||||
{
|
{
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
request,
|
request,
|
||||||
|
|||||||
@ -85,7 +85,10 @@ impl FromS3Response for GetBucketEncryptionResponse {
|
|||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e))
|
Err(Error::S3Error(e))
|
||||||
if e.code == ErrorCode::ServerSideEncryptionConfigurationNotFoundError =>
|
if matches!(
|
||||||
|
e.code,
|
||||||
|
ErrorCode::ServerSideEncryptionConfigurationNotFoundError
|
||||||
|
) =>
|
||||||
{
|
{
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
request,
|
request,
|
||||||
|
|||||||
@ -48,7 +48,7 @@ impl GetBucketPolicyResponse {
|
|||||||
/// for accessing the bucket and its contents.
|
/// for accessing the bucket and its contents.
|
||||||
pub fn config(&self) -> Result<&str, Error> {
|
pub fn config(&self) -> Result<&str, Error> {
|
||||||
std::str::from_utf8(&self.body).map_err(|e| {
|
std::str::from_utf8(&self.body).map_err(|e| {
|
||||||
Error::Utf8Error(format!("Failed to parse bucket policy as UTF-8: {}", e).into())
|
Error::Utf8Error(format!("Failed to parse bucket policy as UTF-8: {e}").into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ impl FromS3Response for GetBucketPolicyResponse {
|
|||||||
headers: mem::take(resp.headers_mut()),
|
headers: mem::take(resp.headers_mut()),
|
||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucketPolicy => Ok(Self {
|
Err(Error::S3Error(e)) if matches!(e.code, ErrorCode::NoSuchBucketPolicy) => Ok(Self {
|
||||||
request,
|
request,
|
||||||
headers: e.headers,
|
headers: e.headers,
|
||||||
body: Bytes::from_static("{}".as_ref()),
|
body: Bytes::from_static("{}".as_ref()),
|
||||||
|
|||||||
@ -54,7 +54,7 @@ impl FromS3Response for GetBucketTaggingResponse {
|
|||||||
headers: mem::take(resp.headers_mut()),
|
headers: mem::take(resp.headers_mut()),
|
||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchTagSet => Ok(Self {
|
Err(Error::S3Error(e)) if matches!(e.code, ErrorCode::NoSuchTagSet) => Ok(Self {
|
||||||
request,
|
request,
|
||||||
headers: e.headers,
|
headers: e.headers,
|
||||||
body: Bytes::new(),
|
body: Bytes::new(),
|
||||||
|
|||||||
@ -40,7 +40,7 @@ impl GetObjectPromptResponse {
|
|||||||
/// This method retrieves the content of the object as a UTF-8 encoded string.
|
/// This method retrieves the content of the object as a UTF-8 encoded string.
|
||||||
pub fn prompt_response(&self) -> Result<&str, Error> {
|
pub fn prompt_response(&self) -> Result<&str, Error> {
|
||||||
std::str::from_utf8(&self.body).map_err(|e| {
|
std::str::from_utf8(&self.body).map_err(|e| {
|
||||||
Error::Utf8Error(format!("Failed to parse prompt_response as UTF-8: {}", e).into())
|
Error::Utf8Error(format!("Failed to parse prompt_response as UTF-8: {e}").into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,9 @@ impl FromS3Response for GetObjectRetentionResponse {
|
|||||||
headers: mem::take(resp.headers_mut()),
|
headers: mem::take(resp.headers_mut()),
|
||||||
body: resp.bytes().await?,
|
body: resp.bytes().await?,
|
||||||
}),
|
}),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchObjectLockConfiguration => {
|
Err(Error::S3Error(e))
|
||||||
|
if matches!(e.code, ErrorCode::NoSuchObjectLockConfiguration) =>
|
||||||
|
{
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
request,
|
request,
|
||||||
headers: e.headers,
|
headers: e.headers,
|
||||||
|
|||||||
@ -10,18 +10,12 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Response types for ListObjects APIs
|
|
||||||
|
|
||||||
use crate::impl_has_s3fields;
|
use crate::impl_has_s3fields;
|
||||||
|
use crate::s3::error::Error;
|
||||||
use crate::s3::response::a_response_traits::HasS3Fields;
|
use crate::s3::response::a_response_traits::HasS3Fields;
|
||||||
use crate::s3::{
|
use crate::s3::types::{FromS3Response, ListEntry, S3Request};
|
||||||
error::Error,
|
use crate::s3::utils::xml::{Element, MergeXmlElements};
|
||||||
types::{FromS3Response, ListEntry, S3Request},
|
use crate::s3::utils::{from_iso8601utc, parse_tags, urldecode};
|
||||||
utils::{
|
|
||||||
from_iso8601utc, parse_tags, urldecode,
|
|
||||||
xml::{Element, MergeXmlElements},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::{Buf, Bytes};
|
use bytes::{Buf, Bytes};
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
|||||||
@ -80,7 +80,7 @@ impl SegmentedBytes {
|
|||||||
impl fmt::Display for SegmentedBytes {
|
impl fmt::Display for SegmentedBytes {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match std::str::from_utf8(self.to_bytes().as_ref()) {
|
match std::str::from_utf8(self.to_bytes().as_ref()) {
|
||||||
Ok(s) => write!(f, "{}", s),
|
Ok(s) => write!(f, "{s}"),
|
||||||
Err(_) => Ok(()), // or: write!(f, "<invalid utf8>")
|
Err(_) => Ok(()), // or: write!(f, "<invalid utf8>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -255,14 +255,14 @@ pub fn check_bucket_name(bucket_name: impl AsRef<str>, strict: bool) -> Result<(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
if bucket_name_len < 3 {
|
if bucket_name_len < 3 {
|
||||||
return Err(Error::InvalidBucketName(
|
return Err(Error::InvalidBucketName(format!(
|
||||||
"bucket name cannot be less than 3 characters".into(),
|
"bucket name ('{bucket_name}') cannot be less than 3 characters"
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
if bucket_name_len > 63 {
|
if bucket_name_len > 63 {
|
||||||
return Err(Error::InvalidBucketName(
|
return Err(Error::InvalidBucketName(format!(
|
||||||
"Bucket name cannot be greater than 63 characters".into(),
|
"Bucket name ('{bucket_name}') cannot be greater than 63 characters"
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -274,8 +274,8 @@ pub fn check_bucket_name(bucket_name: impl AsRef<str>, strict: bool) -> Result<(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if IPV4_REGEX.is_match(bucket_name) {
|
if IPV4_REGEX.is_match(bucket_name) {
|
||||||
return Err(Error::InvalidBucketName(String::from(
|
return Err(Error::InvalidBucketName(format!(
|
||||||
"bucket name cannot be an IP address",
|
"bucket name ('{bucket_name}') cannot be an IP address"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,12 +288,14 @@ pub fn check_bucket_name(bucket_name: impl AsRef<str>, strict: bool) -> Result<(
|
|||||||
if strict {
|
if strict {
|
||||||
if !VALID_BUCKET_NAME_STRICT_REGEX.is_match(bucket_name) {
|
if !VALID_BUCKET_NAME_STRICT_REGEX.is_match(bucket_name) {
|
||||||
return Err(Error::InvalidBucketName(format!(
|
return Err(Error::InvalidBucketName(format!(
|
||||||
"bucket name ('{bucket_name}') does not follow S3 standards strictly",
|
"bucket name ('{bucket_name}') does not follow S3 standards strictly, according to {}",
|
||||||
|
*VALID_BUCKET_NAME_STRICT_REGEX
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
} else if !VALID_BUCKET_NAME_REGEX.is_match(bucket_name) {
|
} else if !VALID_BUCKET_NAME_REGEX.is_match(bucket_name) {
|
||||||
return Err(Error::InvalidBucketName(format!(
|
return Err(Error::InvalidBucketName(format!(
|
||||||
"bucket name ('{bucket_name}') does not follow S3 standards"
|
"bucket name ('{bucket_name}') does not follow S3 standards, according to {}",
|
||||||
|
*VALID_BUCKET_NAME_REGEX
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,13 +303,20 @@ pub fn check_bucket_name(bucket_name: impl AsRef<str>, strict: bool) -> Result<(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_object_name(object_name: impl AsRef<str>) -> Result<(), Error> {
|
pub fn check_object_name(object_name: impl AsRef<str>) -> Result<(), Error> {
|
||||||
if object_name.as_ref().is_empty() {
|
let object_name: &str = object_name.as_ref();
|
||||||
Err(Error::InvalidObjectName(
|
let object_name_n_bytes = object_name.len();
|
||||||
|
if object_name_n_bytes == 0 {
|
||||||
|
return Err(Error::InvalidObjectName(
|
||||||
"object name cannot be empty".into(),
|
"object name cannot be empty".into(),
|
||||||
))
|
));
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
if object_name_n_bytes > 1024 {
|
||||||
|
return Err(Error::InvalidObjectName(format!(
|
||||||
|
"Object name ('{object_name}') cannot be greater than 1024 bytes"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets text value of given XML element for given tag.
|
/// Gets text value of given XML element for given tag.
|
||||||
|
|||||||
@ -15,11 +15,13 @@
|
|||||||
|
|
||||||
use minio::s3::client::DEFAULT_REGION;
|
use minio::s3::client::DEFAULT_REGION;
|
||||||
use minio::s3::error::{Error, ErrorCode};
|
use minio::s3::error::{Error, ErrorCode};
|
||||||
use minio::s3::response::a_response_traits::{HasBucket, HasRegion};
|
use minio::s3::response::a_response_traits::{HasBucket, HasObject, HasRegion};
|
||||||
use minio::s3::response::{BucketExistsResponse, CreateBucketResponse, DeleteBucketResponse};
|
use minio::s3::response::{
|
||||||
|
BucketExistsResponse, CreateBucketResponse, DeleteBucketResponse, PutObjectContentResponse,
|
||||||
|
};
|
||||||
use minio::s3::types::S3Api;
|
use minio::s3::types::S3Api;
|
||||||
use minio_common::test_context::TestContext;
|
use minio_common::test_context::TestContext;
|
||||||
use minio_common::utils::rand_bucket_name;
|
use minio_common::utils::{rand_bucket_name, rand_object_name};
|
||||||
|
|
||||||
#[minio_macros::test(no_bucket)]
|
#[minio_macros::test(no_bucket)]
|
||||||
async fn bucket_create(ctx: TestContext) {
|
async fn bucket_create(ctx: TestContext) {
|
||||||
@ -41,7 +43,7 @@ async fn bucket_create(ctx: TestContext) {
|
|||||||
ctx.client.create_bucket(&bucket_name).send().await;
|
ctx.client.create_bucket(&bucket_name).send().await;
|
||||||
match resp {
|
match resp {
|
||||||
Ok(_) => panic!("Bucket already exists, but was created again"),
|
Ok(_) => panic!("Bucket already exists, but was created again"),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::BucketAlreadyOwnedByYou => {
|
Err(Error::S3Error(e)) if matches!(e.code, ErrorCode::BucketAlreadyOwnedByYou) => {
|
||||||
// this is expected, as the bucket already exists
|
// this is expected, as the bucket already exists
|
||||||
}
|
}
|
||||||
Err(e) => panic!("Unexpected error: {:?}", e),
|
Err(e) => panic!("Unexpected error: {:?}", e),
|
||||||
@ -57,7 +59,7 @@ async fn bucket_delete(ctx: TestContext) {
|
|||||||
ctx.client.delete_bucket(&bucket_name).send().await;
|
ctx.client.delete_bucket(&bucket_name).send().await;
|
||||||
match resp {
|
match resp {
|
||||||
Ok(_) => panic!("Bucket does not exist, but was removed"),
|
Ok(_) => panic!("Bucket does not exist, but was removed"),
|
||||||
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucket => {
|
Err(Error::S3Error(e)) if matches!(e.code, ErrorCode::NoSuchBucket) => {
|
||||||
// this is expected, as the bucket does not exist
|
// this is expected, as the bucket does not exist
|
||||||
}
|
}
|
||||||
Err(e) => panic!("Unexpected error: {:?}", e),
|
Err(e) => panic!("Unexpected error: {:?}", e),
|
||||||
@ -85,3 +87,40 @@ async fn bucket_delete(ctx: TestContext) {
|
|||||||
assert_eq!(resp.bucket(), bucket_name);
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
assert_eq!(resp.region(), "");
|
assert_eq!(resp.region(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[minio_macros::test(no_bucket)]
|
||||||
|
async fn bucket_delete_and_purge_1(ctx: TestContext) {
|
||||||
|
let bucket_name = rand_bucket_name();
|
||||||
|
|
||||||
|
// create a new bucket
|
||||||
|
let resp: CreateBucketResponse = ctx.client.create_bucket(&bucket_name).send().await.unwrap();
|
||||||
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
|
assert_eq!(resp.region(), DEFAULT_REGION);
|
||||||
|
|
||||||
|
// add some objects to the bucket
|
||||||
|
for _ in 0..5 {
|
||||||
|
let object_name = rand_object_name();
|
||||||
|
let resp: PutObjectContentResponse = ctx
|
||||||
|
.client
|
||||||
|
.put_object_content(&bucket_name, &object_name, "Hello, World!")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
|
assert_eq!(resp.object(), object_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to remove the bucket without purging, this should fail because the bucket is not empty
|
||||||
|
let resp: Result<DeleteBucketResponse, Error> =
|
||||||
|
ctx.client.delete_bucket(&bucket_name).send().await;
|
||||||
|
|
||||||
|
assert!(resp.is_err());
|
||||||
|
|
||||||
|
// try to remove the bucket with purging, this should succeed
|
||||||
|
let resp: DeleteBucketResponse = ctx
|
||||||
|
.client
|
||||||
|
.delete_and_purge_bucket(&bucket_name)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
|
}
|
||||||
|
|||||||
@ -38,12 +38,7 @@ async fn list_buckets(ctx: TestContext) {
|
|||||||
for bucket in resp.buckets().unwrap().iter() {
|
for bucket in resp.buckets().unwrap().iter() {
|
||||||
if names.contains(&bucket.name) {
|
if names.contains(&bucket.name) {
|
||||||
count += 1;
|
count += 1;
|
||||||
} // else if bucket.name.len() == 8 {
|
}
|
||||||
// match ctx.client.delete_and_purge_bucket(&bucket.name).await {
|
|
||||||
// Ok(_) => println!("Deleted bucket: {}", bucket.name),
|
|
||||||
// Err(e) => println!("Failed to delete bucket {}: {}", bucket.name, e)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
assert_eq!(guards.len(), N_BUCKETS);
|
assert_eq!(guards.len(), N_BUCKETS);
|
||||||
assert_eq!(count, N_BUCKETS);
|
assert_eq!(count, N_BUCKETS);
|
||||||
|
|||||||
@ -41,10 +41,10 @@ async fn list_objects(
|
|||||||
|
|
||||||
let is_express = ctx.client.is_minio_express().await;
|
let is_express = ctx.client.is_minio_express().await;
|
||||||
if is_express && !express {
|
if is_express && !express {
|
||||||
println!("Skipping test because it is running in MinIO Express mode");
|
eprintln!("Skipping test because it is running in MinIO Express mode");
|
||||||
return;
|
return;
|
||||||
} else if !is_express && express {
|
} else if !is_express && express {
|
||||||
println!("Skipping test because it is NOT running in MinIO Express mode");
|
eprintln!("Skipping test because it is NOT running in MinIO Express mode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,29 +97,29 @@ async fn list_objects(
|
|||||||
assert_eq!(names_set_after, names_set_before);
|
assert_eq!(names_set_after, names_set_before);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[minio_macros::test]
|
#[minio_macros::test(skip_if_express)]
|
||||||
async fn list_objects_v1_no_versions(ctx: TestContext, bucket_name: String) {
|
async fn list_objects_v1_no_versions(ctx: TestContext, bucket_name: String) {
|
||||||
list_objects(true, false, false, 5, 5, ctx, bucket_name).await;
|
list_objects(true, false, false, 5, 5, ctx, bucket_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[minio_macros::test]
|
#[minio_macros::test(skip_if_express)]
|
||||||
async fn list_objects_v1_with_versions(ctx: TestContext, bucket_name: String) {
|
async fn list_objects_v1_with_versions(ctx: TestContext, bucket_name: String) {
|
||||||
list_objects(true, true, false, 5, 5, ctx, bucket_name).await;
|
list_objects(true, true, false, 5, 5, ctx, bucket_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[minio_macros::test]
|
#[minio_macros::test(skip_if_express)]
|
||||||
async fn list_objects_v2_no_versions(ctx: TestContext, bucket_name: String) {
|
async fn list_objects_v2_no_versions(ctx: TestContext, bucket_name: String) {
|
||||||
list_objects(false, false, false, 5, 5, ctx, bucket_name).await;
|
list_objects(false, false, false, 5, 5, ctx, bucket_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[minio_macros::test]
|
#[minio_macros::test(skip_if_express)]
|
||||||
async fn list_objects_v2_with_versions(ctx: TestContext, bucket_name: String) {
|
async fn list_objects_v2_with_versions(ctx: TestContext, bucket_name: String) {
|
||||||
list_objects(false, true, false, 5, 5, ctx, bucket_name).await;
|
list_objects(false, true, false, 5, 5, ctx, bucket_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test for S3-Express: List objects with S3-Express are only supported with V2 API, without
|
/// Test for S3-Express: List objects with S3-Express are only supported with V2 API, without
|
||||||
/// versions, and yield unsorted results.
|
/// versions, and yield results that need not be sorted.
|
||||||
#[minio_macros::test]
|
#[minio_macros::test(skip_if_not_express)]
|
||||||
async fn list_objects_express(ctx: TestContext, bucket_name: String) {
|
async fn list_objects_express(ctx: TestContext, bucket_name: String) {
|
||||||
list_objects(false, false, true, 5, 5, ctx, bucket_name).await;
|
list_objects(false, false, true, 5, 5, ctx, bucket_name).await;
|
||||||
}
|
}
|
||||||
|
|||||||
133
tests/test_object_delete.rs
Normal file
133
tests/test_object_delete.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||||
|
// Copyright 2025 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use async_std::stream::StreamExt;
|
||||||
|
use minio::s3::builders::ObjectToDelete;
|
||||||
|
use minio::s3::response::a_response_traits::{HasBucket, HasObject};
|
||||||
|
use minio::s3::response::{
|
||||||
|
DeleteObjectResponse, DeleteObjectsResponse, DeleteResult, PutObjectContentResponse,
|
||||||
|
};
|
||||||
|
use minio::s3::types::{S3Api, ToStream};
|
||||||
|
use minio_common::test_context::TestContext;
|
||||||
|
use minio_common::utils::rand_object_name_utf8;
|
||||||
|
|
||||||
|
async fn create_object(
|
||||||
|
ctx: &TestContext,
|
||||||
|
bucket_name: &str,
|
||||||
|
object_name: &str,
|
||||||
|
) -> PutObjectContentResponse {
|
||||||
|
let resp: PutObjectContentResponse = ctx
|
||||||
|
.client
|
||||||
|
.put_object_content(bucket_name, object_name, "hello world")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
|
assert_eq!(resp.object(), object_name);
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[minio_macros::test]
|
||||||
|
async fn delete_object(ctx: TestContext, bucket_name: String) {
|
||||||
|
let object_name = rand_object_name_utf8(20);
|
||||||
|
let _resp = create_object(&ctx, &bucket_name, &object_name).await;
|
||||||
|
|
||||||
|
let resp: DeleteObjectResponse = ctx
|
||||||
|
.client
|
||||||
|
.delete_object(&bucket_name, &object_name)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[minio_macros::test]
|
||||||
|
async fn delete_object_with_whitespace(ctx: TestContext, bucket_name: String) {
|
||||||
|
let object_name = format!(" {}", rand_object_name_utf8(20));
|
||||||
|
let _resp = create_object(&ctx, &bucket_name, &object_name).await;
|
||||||
|
|
||||||
|
let resp: DeleteObjectResponse = ctx
|
||||||
|
.client
|
||||||
|
.delete_object(&bucket_name, &object_name)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(resp.bucket(), bucket_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[minio_macros::test]
|
||||||
|
async fn delete_objects(ctx: TestContext, bucket_name: String) {
|
||||||
|
const OBJECT_COUNT: usize = 3;
|
||||||
|
let mut names: Vec<String> = Vec::new();
|
||||||
|
for _ in 1..=OBJECT_COUNT {
|
||||||
|
let object_name = rand_object_name_utf8(20);
|
||||||
|
let _resp = create_object(&ctx, &bucket_name, &object_name).await;
|
||||||
|
names.push(object_name);
|
||||||
|
}
|
||||||
|
let del_items: Vec<ObjectToDelete> = names
|
||||||
|
.iter()
|
||||||
|
.map(|v| ObjectToDelete::from(v.as_str()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let resp: DeleteObjectsResponse = ctx
|
||||||
|
.client
|
||||||
|
.delete_objects::<&String, ObjectToDelete>(&bucket_name, del_items)
|
||||||
|
.verbose_mode(true) // Enable verbose mode to get detailed response
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let deleted_names: Vec<DeleteResult> = resp.result().unwrap();
|
||||||
|
assert_eq!(deleted_names.len(), OBJECT_COUNT);
|
||||||
|
for obj in deleted_names.iter() {
|
||||||
|
assert!(obj.is_deleted());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[minio_macros::test]
|
||||||
|
async fn delete_objects_streaming(ctx: TestContext, bucket_name: String) {
|
||||||
|
const OBJECT_COUNT: usize = 3;
|
||||||
|
let mut names: Vec<String> = Vec::new();
|
||||||
|
for _ in 1..=OBJECT_COUNT {
|
||||||
|
let object_name = rand_object_name_utf8(20);
|
||||||
|
let _resp = create_object(&ctx, &bucket_name, &object_name).await;
|
||||||
|
names.push(object_name);
|
||||||
|
}
|
||||||
|
let del_items: Vec<ObjectToDelete> = names
|
||||||
|
.iter()
|
||||||
|
.map(|v| ObjectToDelete::from(v.as_str()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut resp = ctx
|
||||||
|
.client
|
||||||
|
.delete_objects_streaming(&bucket_name, del_items.into_iter())
|
||||||
|
.verbose_mode(true)
|
||||||
|
.to_stream()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut del_count = 0;
|
||||||
|
while let Some(item) = resp.next().await {
|
||||||
|
let res = item.unwrap();
|
||||||
|
let del_result = res.result().unwrap();
|
||||||
|
del_count += del_result.len();
|
||||||
|
|
||||||
|
for obj in del_result.into_iter() {
|
||||||
|
assert!(obj.is_deleted());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(del_count, 3);
|
||||||
|
}
|
||||||
@ -15,9 +15,8 @@
|
|||||||
|
|
||||||
use http::header;
|
use http::header;
|
||||||
use minio::s3::builders::{MIN_PART_SIZE, ObjectContent};
|
use minio::s3::builders::{MIN_PART_SIZE, ObjectContent};
|
||||||
use minio::s3::error::{Error, ErrorCode};
|
|
||||||
use minio::s3::response::a_response_traits::{
|
use minio::s3::response::a_response_traits::{
|
||||||
HasBucket, HasEtagFromHeaders, HasIsDeleteMarker, HasObject, HasS3Fields, HasVersion,
|
HasBucket, HasEtagFromHeaders, HasIsDeleteMarker, HasObject, HasS3Fields,
|
||||||
};
|
};
|
||||||
use minio::s3::response::{DeleteObjectResponse, PutObjectContentResponse, StatObjectResponse};
|
use minio::s3::response::{DeleteObjectResponse, PutObjectContentResponse, StatObjectResponse};
|
||||||
use minio::s3::types::S3Api;
|
use minio::s3::types::S3Api;
|
||||||
@ -26,16 +25,13 @@ use minio_common::test_context::TestContext;
|
|||||||
use minio_common::utils::rand_object_name;
|
use minio_common::utils::rand_object_name;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
#[minio_macros::test]
|
async fn test_put_object(ctx: &TestContext, bucket_name: &str, object_name: &str) {
|
||||||
async fn put_object(ctx: TestContext, bucket_name: String) {
|
|
||||||
let object_name: String = rand_object_name();
|
|
||||||
|
|
||||||
let size = 16_u64;
|
let size = 16_u64;
|
||||||
let resp: PutObjectContentResponse = ctx
|
let resp: PutObjectContentResponse = ctx
|
||||||
.client
|
.client
|
||||||
.put_object_content(
|
.put_object_content(
|
||||||
&bucket_name,
|
bucket_name,
|
||||||
&object_name,
|
object_name,
|
||||||
ObjectContent::new_from_stream(RandSrc::new(size), Some(size)),
|
ObjectContent::new_from_stream(RandSrc::new(size), Some(size)),
|
||||||
)
|
)
|
||||||
.send()
|
.send()
|
||||||
@ -48,35 +44,26 @@ async fn put_object(ctx: TestContext, bucket_name: String) {
|
|||||||
|
|
||||||
let resp: StatObjectResponse = ctx
|
let resp: StatObjectResponse = ctx
|
||||||
.client
|
.client
|
||||||
.stat_object(&bucket_name, &object_name)
|
.stat_object(bucket_name, object_name)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.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().unwrap(), size);
|
assert_eq!(resp.size().unwrap(), size);
|
||||||
|
}
|
||||||
|
|
||||||
let resp: DeleteObjectResponse = ctx
|
/// Test putting an object into a bucket and verifying its existence.
|
||||||
.client
|
#[minio_macros::test]
|
||||||
.delete_object(&bucket_name, &object_name)
|
async fn put_object_1(ctx: TestContext, bucket_name: String) {
|
||||||
.send()
|
test_put_object(&ctx, &bucket_name, &rand_object_name()).await;
|
||||||
.await
|
}
|
||||||
.unwrap();
|
|
||||||
assert!(resp.version_id().is_none());
|
|
||||||
|
|
||||||
// Validate delete succeeded.
|
/// Test putting an object with a name that contains special characters.
|
||||||
let resp: Result<StatObjectResponse, Error> = ctx
|
#[minio_macros::test]
|
||||||
.client
|
async fn put_object_2(ctx: TestContext, bucket_name: String) {
|
||||||
.stat_object(&bucket_name, &object_name)
|
test_put_object(&ctx, &bucket_name, "name with+spaces").await;
|
||||||
.send()
|
test_put_object(&ctx, &bucket_name, "name%20with%2Bspaces").await;
|
||||||
.await;
|
|
||||||
|
|
||||||
match resp.err().unwrap() {
|
|
||||||
Error::S3Error(er) => {
|
|
||||||
assert_eq!(er.code, ErrorCode::NoSuchKey)
|
|
||||||
}
|
|
||||||
e => panic!("Unexpected error {:?}", e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[minio_macros::test]
|
#[minio_macros::test]
|
||||||
@ -108,14 +95,6 @@ async fn put_object_multipart(ctx: TestContext, bucket_name: String) {
|
|||||||
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().unwrap(), size);
|
assert_eq!(resp.size().unwrap(), size);
|
||||||
|
|
||||||
let resp: DeleteObjectResponse = ctx
|
|
||||||
.client
|
|
||||||
.delete_object(&bucket_name, &object_name)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.version_id(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[minio_macros::test]
|
#[minio_macros::test]
|
||||||
@ -192,14 +171,6 @@ async fn put_object_content_2(ctx: TestContext, bucket_name: String) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(resp.size().unwrap(), *size);
|
assert_eq!(resp.size().unwrap(), *size);
|
||||||
assert_eq!(resp.etag().unwrap(), etag);
|
assert_eq!(resp.etag().unwrap(), etag);
|
||||||
|
|
||||||
let resp: DeleteObjectResponse = ctx
|
|
||||||
.client
|
|
||||||
.delete_object(&bucket_name, &object_name)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.version_id(), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,11 +218,6 @@ async fn put_object_content_3(ctx: TestContext, bucket_name: String) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(resp.size().unwrap(), sizes[idx]);
|
assert_eq!(resp.size().unwrap(), sizes[idx]);
|
||||||
assert_eq!(resp.etag().unwrap(), etag);
|
assert_eq!(resp.etag().unwrap(), etag);
|
||||||
client
|
|
||||||
.delete_object(&test_bucket, &object_name)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
|
||||||
// Copyright 2025 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
use async_std::stream::StreamExt;
|
|
||||||
use minio::s3::builders::ObjectToDelete;
|
|
||||||
use minio::s3::response::PutObjectContentResponse;
|
|
||||||
use minio::s3::response::a_response_traits::{HasBucket, HasObject};
|
|
||||||
use minio::s3::types::ToStream;
|
|
||||||
use minio_common::test_context::TestContext;
|
|
||||||
use minio_common::utils::rand_object_name;
|
|
||||||
|
|
||||||
#[minio_macros::test]
|
|
||||||
async fn remove_objects(ctx: TestContext, bucket_name: String) {
|
|
||||||
let mut names: Vec<String> = Vec::new();
|
|
||||||
for _ in 1..=3 {
|
|
||||||
let object_name = rand_object_name();
|
|
||||||
let resp: PutObjectContentResponse = ctx
|
|
||||||
.client
|
|
||||||
.put_object_content(&bucket_name, &object_name, "")
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.bucket(), bucket_name);
|
|
||||||
assert_eq!(resp.object(), object_name);
|
|
||||||
names.push(object_name);
|
|
||||||
}
|
|
||||||
let del_items: Vec<ObjectToDelete> = names
|
|
||||||
.iter()
|
|
||||||
.map(|v| ObjectToDelete::from(v.as_str()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut resp = ctx
|
|
||||||
.client
|
|
||||||
.delete_objects_streaming(&bucket_name, del_items.into_iter())
|
|
||||||
.verbose_mode(true)
|
|
||||||
.to_stream()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut del_count = 0;
|
|
||||||
while let Some(item) = resp.next().await {
|
|
||||||
let res = item.unwrap();
|
|
||||||
let del_result = res.result().unwrap();
|
|
||||||
del_count += del_result.len();
|
|
||||||
|
|
||||||
for obj in del_result.into_iter() {
|
|
||||||
assert!(obj.is_deleted());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(del_count, 3);
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user