Added object_prompt support (#109)

This commit is contained in:
Henk-Jan Lebbink 2025-01-23 18:56:29 +01:00 committed by GitHub
parent 8facff7bad
commit 0438f044ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 443 additions and 48 deletions

View File

@ -36,7 +36,7 @@ futures-util = "0.3.31"
hex = "0.4.3" hex = "0.4.3"
hmac = "0.12.1" hmac = "0.12.1"
home = "0.5.9" home = "0.5.9"
http = "1.1.0" http = "1.2.0"
hyper = { version = "1.5.1", features = ["full"] } hyper = { version = "1.5.1", features = ["full"] }
lazy_static = "1.5.0" lazy_static = "1.5.0"
log = "0.4.22" log = "0.4.22"
@ -61,4 +61,7 @@ clap = { version = "4.5.23", features = ["derive"] }
quickcheck = "1.0.3" quickcheck = "1.0.3"
[[example]] [[example]]
name = "file-uploader" name = "file_uploader"
[[example]]
name = "object_prompt"

View File

@ -4,10 +4,20 @@ MinIO Rust SDK is Simple Storage Service (aka S3) client to perform bucket and o
For a complete list of APIs and examples, please take a look at the [MinIO Rust Client API Reference](https://minio-rs.min.io/) For a complete list of APIs and examples, please take a look at the [MinIO Rust Client API Reference](https://minio-rs.min.io/)
## Example:: file-uploader.rs ## Examples
[Upload a file to MinIO](examples/file-uploader.rs) Run the examples from the command line with:
`cargo run --example <example_name>`
### file_uploader.rs
* [Upload a file to MinIO](examples/file_uploader)
* [Upload a file to MinIO with CLI](examples/put_object)
### object_prompt.rs
* [Prompt a file on MinIO](examples/object_prompt)
## License ## License
This SDK is distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), see [LICENSE](https://github.com/minio/minio-rs/blob/master/LICENSE) for more information. This SDK is distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), see [LICENSE](https://github.com/minio/minio-rs/blob/master/LICENSE) for more information.

BIN
examples/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -1,4 +1,18 @@
use log::info; // MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 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 minio::s3::args::{BucketExistsArgs, MakeBucketArgs}; use minio::s3::args::{BucketExistsArgs, MakeBucketArgs};
use minio::s3::builders::ObjectContent; use minio::s3::builders::ObjectContent;
use minio::s3::client::ClientBuilder; use minio::s3::client::ClientBuilder;
@ -11,8 +25,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher
let base_url = "https://play.min.io".parse::<BaseUrl>()?; let base_url = "https://play.min.io".parse::<BaseUrl>()?;
log::info!("Trying to connect to MinIO at: `{:?}`", base_url);
info!("Trying to connect to MinIO at: `{:?}`", base_url);
let static_provider = StaticProvider::new( let static_provider = StaticProvider::new(
"Q3AM3UQ867SPQQA43P2F", "Q3AM3UQ867SPQQA43P2F",
@ -24,7 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.provider(Some(Box::new(static_provider))) .provider(Some(Box::new(static_provider)))
.build()?; .build()?;
let bucket_name: &str = "asiatrip"; let bucket_name: &str = "file-upload-rust-bucket";
// Check 'bucket_name' bucket exist or not. // Check 'bucket_name' bucket exist or not.
let exists: bool = client let exists: bool = client
@ -41,12 +54,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
} }
// File we are going to upload to the bucket // File we are going to upload to the bucket
let filename: &Path = Path::new("/tmp/asiaphotos.zip"); let filename: &Path = Path::new("./examples/cat.png");
// Name of the object that will be stored in the bucket // Name of the object that will be stored in the bucket
let object_name: &str = "asiaphotos-2015.zip"; let object_name: &str = "cat.png";
info!("filename {}", &filename.to_str().unwrap()); if filename.exists() {
log::info!("File '{}' exists.", &filename.to_str().unwrap());
} else {
log::error!("File '{}' does not exist.", &filename.to_str().unwrap());
return Ok(());
}
let content = ObjectContent::from(filename); let content = ObjectContent::from(filename);
client client
@ -54,8 +72,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.send() .send()
.await?; .await?;
info!( log::info!(
"file `{}` is successfully uploaded as object `{object_name}` to bucket `{bucket_name}`.", "file '{}' is successfully uploaded as object '{object_name}' to bucket '{bucket_name}'.",
filename.display() filename.display()
); );
Ok(()) Ok(())

90
examples/object_prompt.rs Normal file
View File

@ -0,0 +1,90 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 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 minio::s3::args::{BucketExistsArgs, MakeBucketArgs};
use minio::s3::builders::{ObjectContent, ObjectPrompt};
use minio::s3::client::ClientBuilder;
use minio::s3::creds::StaticProvider;
use minio::s3::http::BaseUrl;
use minio::s3::types::S3Api;
use std::path::Path;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher
let base_url = "https://play.min.io".parse::<BaseUrl>()?;
log::info!("Trying to connect to MinIO at: `{:?}`", base_url);
let static_provider = StaticProvider::new(
"Q3AM3UQ867SPQQA43P2F",
"zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
None,
);
let client = ClientBuilder::new(base_url.clone())
.provider(Some(Box::new(static_provider)))
.ignore_cert_check(Some(true))
.build()?;
let bucket_name: &str = "object-prompt-rust-bucket";
// Check 'bucket_name' bucket exist or not.
let exists: bool = client
.bucket_exists(&BucketExistsArgs::new(bucket_name).unwrap())
.await
.unwrap();
// Make 'bucket_name' bucket if not exist.
if !exists {
client
.make_bucket(&MakeBucketArgs::new(bucket_name).unwrap())
.await
.unwrap();
}
// File we are going to upload to the bucket
let filename: &Path = Path::new("./examples/cat.png");
// Name of the object that will be stored in the bucket
let object_name: &str = "cat.png";
if filename.exists() {
log::info!("File '{}' exists.", &filename.to_str().unwrap());
} else {
log::error!("File '{}' does not exist.", &filename.to_str().unwrap());
return Ok(());
}
let content = ObjectContent::from(filename);
client
.put_object_content(bucket_name, object_name, content)
.send()
.await?;
log::info!(
"File '{}' is successfully uploaded as object '{object_name}' to bucket '{bucket_name}'.",
filename.display()
);
let op = ObjectPrompt::new(bucket_name, object_name, "what is it about?")
//.lambda_arn("arn:minio:s3-object-lambda::_:webhook") // this is the default value
.client(&client);
let res = op.send().await?;
log::info!("Object prompt result: '{}'", res.prompt_response);
Ok(())
}

View File

@ -1,3 +1,6 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
@ -17,6 +20,7 @@ mod get_object;
mod list_objects; mod list_objects;
mod listen_bucket_notification; mod listen_bucket_notification;
mod object_content; mod object_content;
mod object_prompt;
mod put_object; mod put_object;
mod remove_objects; mod remove_objects;
@ -25,5 +29,6 @@ pub use get_object::*;
pub use list_objects::*; pub use list_objects::*;
pub use listen_bucket_notification::*; pub use listen_bucket_notification::*;
pub use object_content::*; pub use object_content::*;
pub use object_prompt::*;
pub use put_object::*; pub use put_object::*;
pub use remove_objects::*; pub use remove_objects::*;

View File

@ -178,8 +178,7 @@ impl ToS3Request for GetObject {
"object name cannot be empty", "object name cannot be empty",
))); )));
} }
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let client = self.client.clone().ok_or(Error::NoClientProvided)?;
if self.ssec.is_some() && !client.is_secure() { if self.ssec.is_some() && !client.is_secure() {
return Err(Error::SseTlsRequired(None)); return Err(Error::SseTlsRequired(None));
@ -199,15 +198,12 @@ impl ToS3Request for GetObject {
query_params.insert(String::from("versionId"), v.to_string()); query_params.insert(String::from("versionId"), v.to_string());
} }
let req = S3Request::new( let req = S3Request::new(client, Method::GET)
self.client.as_ref().ok_or(Error::NoClientProvided)?, .region(self.region.as_deref())
Method::GET, .bucket(Some(&self.bucket))
) .object(Some(&self.object))
.region(self.region.as_deref()) .query_params(query_params)
.bucket(Some(&self.bucket)) .headers(headers);
.object(Some(&self.object))
.query_params(query_params)
.headers(headers);
Ok(req) Ok(req)
} }

View File

@ -0,0 +1,156 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 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 crate::s3::builders::SegmentedBytes;
use crate::s3::sse::{Sse, SseCustomerKey};
use crate::s3::utils::{check_bucket_name, merge, Multimap};
use crate::s3::{
client::Client,
error::Error,
response::ObjectPromptResponse,
types::{S3Api, S3Request, ToS3Request},
};
use bytes::Bytes;
use http::Method;
use serde_json::json;
#[derive(Debug, Clone, Default)]
pub struct ObjectPrompt {
client: Option<Client>,
bucket: String,
object: String,
prompt: String,
lambda_arn: Option<String>,
version_id: Option<String>,
region: Option<String>,
ssec: Option<SseCustomerKey>,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
}
// builder interface
impl ObjectPrompt {
pub fn new(bucket: &str, object: &str, prompt: &str) -> Self {
ObjectPrompt {
client: None,
bucket: bucket.to_string(),
object: object.to_string(),
prompt: prompt.to_string(),
..Default::default()
}
}
pub fn client(mut self, client: &Client) -> Self {
self.client = Some(client.clone());
self
}
pub fn lambda_arn(mut self, lambda_arn: &str) -> Self {
self.lambda_arn = Some(lambda_arn.to_string());
self
}
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id;
self
}
pub fn region(mut self, region: Option<String>) -> Self {
self.region = region;
self
}
pub fn ssec(mut self, ssec: Option<SseCustomerKey>) -> Self {
self.ssec = ssec;
self
}
}
// internal helpers
impl ObjectPrompt {
fn get_headers(&self) -> Multimap {
let mut headers = Multimap::new();
if let Some(v) = &self.ssec {
merge(&mut headers, &v.headers());
}
headers
}
}
impl ToS3Request for ObjectPrompt {
fn to_s3request(&self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
if self.object.is_empty() {
return Err(Error::InvalidObjectName(String::from(
"object name cannot be empty",
)));
}
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
if self.ssec.is_some() && !client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
let mut headers = Multimap::new();
if let Some(v) = &self.extra_headers {
merge(&mut headers, v);
}
merge(&mut headers, &self.get_headers());
let mut query_params = Multimap::new();
if let Some(v) = &self.extra_query_params {
merge(&mut query_params, v);
}
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
query_params.insert(
String::from("lambdaArn"),
self.lambda_arn
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
);
let prompt_body = json!({ "prompt": self.prompt });
let body: SegmentedBytes = SegmentedBytes::from(Bytes::from(prompt_body.to_string()));
let req = S3Request::new(client, Method::POST)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.object(Some(&self.object))
.query_params(query_params)
.headers(headers)
.body(Some(body));
Ok(req)
}
}
impl S3Api for ObjectPrompt {
type S3Response = ObjectPromptResponse;
}

View File

@ -682,9 +682,9 @@ fn object_write_args_headers(
Ok(map) Ok(map)
} }
// PutObjectContent takes a `ObjectContent` stream and uploads it to MinIO/S3. /// PutObjectContent takes a `ObjectContent` stream and uploads it to MinIO/S3.
// ///
// It is a higher level API and handles multipart uploads transparently. /// It is a higher level API and handles multipart uploads transparently.
pub struct PutObjectContent { pub struct PutObjectContent {
client: Option<Client>, client: Option<Client>,
@ -1048,8 +1048,8 @@ pub const MAX_PART_SIZE: u64 = 1024 * MIN_PART_SIZE; // 5 GiB
pub const MAX_OBJECT_SIZE: u64 = 1024 * MAX_PART_SIZE; // 5 TiB pub const MAX_OBJECT_SIZE: u64 = 1024 * MAX_PART_SIZE; // 5 TiB
pub const MAX_MULTIPART_COUNT: u16 = 10_000; pub const MAX_MULTIPART_COUNT: u16 = 10_000;
// Returns the size of each part to upload and the total number of parts. The /// Returns the size of each part to upload and the total number of parts. The
// number of parts is `None` when the object size is unknown. /// number of parts is `None` when the object size is unknown.
fn calc_part_info(object_size: Size, part_size: Size) -> Result<(u64, Option<u16>), Error> { fn calc_part_info(object_size: Size, part_size: Size) -> Result<(u64, Option<u16>), Error> {
// Validate arguments against limits. // Validate arguments against limits.
if let Size::Known(v) = part_size { if let Size::Known(v) = part_size {

View File

@ -126,10 +126,6 @@ impl RemoveObject {
} }
} }
impl S3Api for RemoveObject {
type S3Response = RemoveObjectResponse;
}
impl ToS3Request for RemoveObject { impl ToS3Request for RemoveObject {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(&self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
@ -160,6 +156,10 @@ impl ToS3Request for RemoveObject {
} }
} }
impl S3Api for RemoveObject {
type S3Response = RemoveObjectResponse;
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RemoveObjectsApi { pub struct RemoveObjectsApi {
client: Option<ClientCore>, client: Option<ClientCore>,
@ -302,7 +302,10 @@ impl From<ObjectToDelete> for DeleteObjects {
} }
} }
impl<I: Iterator<Item = ObjectToDelete> + Send + Sync + 'static> From<I> for DeleteObjects { impl<I> From<I> for DeleteObjects
where
I: Iterator<Item = ObjectToDelete> + Send + Sync + 'static,
{
fn from(keys: I) -> Self { fn from(keys: I) -> Self {
DeleteObjects::from_stream(stream_iter(keys)) DeleteObjects::from_stream(stream_iter(keys))
} }

View File

@ -50,6 +50,7 @@ use xmltree::Element;
mod get_object; mod get_object;
mod list_objects; mod list_objects;
mod listen_bucket_notification; mod listen_bucket_notification;
mod object_prompt;
mod put_object; mod put_object;
mod remove_objects; mod remove_objects;

View File

@ -0,0 +1,27 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 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.
//! S3 APIs for downloading objects.
use crate::s3::builders::ObjectPrompt;
use super::Client;
impl Client {
/// Create a ObjectPrompt request builder. Prompt an object using natural language.
pub fn object_prompt(&self, bucket: &str, object: &str, prompt: &str) -> ObjectPrompt {
ObjectPrompt::new(bucket, object, prompt).client(self)
}
}

View File

@ -35,6 +35,7 @@ mod buckets;
mod get_object; mod get_object;
pub(crate) mod list_objects; pub(crate) mod list_objects;
mod listen_bucket_notification; mod listen_bucket_notification;
mod object_prompt;
mod put_object; mod put_object;
mod remove_objects; mod remove_objects;
@ -42,6 +43,7 @@ pub use buckets::{GetBucketVersioningResponse, ListBucketsResponse};
pub use get_object::GetObjectResponse; pub use get_object::GetObjectResponse;
pub use list_objects::ListObjectsResponse; pub use list_objects::ListObjectsResponse;
pub use listen_bucket_notification::ListenBucketNotificationResponse; pub use listen_bucket_notification::ListenBucketNotificationResponse;
pub use object_prompt::ObjectPromptResponse;
pub use put_object::{ pub use put_object::{
AbortMultipartUploadResponse2, CompleteMultipartUploadResponse2, AbortMultipartUploadResponse2, CompleteMultipartUploadResponse2,
CreateMultipartUploadResponse2, PutObjectContentResponse, PutObjectResponse, CreateMultipartUploadResponse2, PutObjectContentResponse, PutObjectResponse,
@ -381,7 +383,7 @@ impl SelectObjectContentResponse {
offset += 1; offset += 1;
let b1 = self.data[offset] as u16; let b1 = self.data[offset] as u16;
offset += 1; offset += 1;
length = (b0 << 8 | b1) as usize; length = ((b0 << 8) | b1) as usize;
let value = String::from_utf8(self.data[offset..offset + length].to_vec())?; let value = String::from_utf8(self.data[offset..offset + length].to_vec())?;
offset += length; offset += length;

View File

@ -0,0 +1,61 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 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 crate::s3::error::Error;
use crate::s3::types::{FromS3Response, S3Request};
use async_trait::async_trait;
pub struct ObjectPromptResponse {
pub headers: http::HeaderMap,
pub region: String,
pub bucket_name: String,
pub object_name: String,
pub prompt_response: String,
}
#[async_trait]
impl FromS3Response for ObjectPromptResponse {
async fn from_s3response<'a>(
req: S3Request<'a>,
response: reqwest::Response,
) -> Result<Self, Error> {
let headers = response.headers().clone();
let body = response.bytes().await?;
let prompt_response: String = String::from_utf8(body.to_vec())?;
let region: String = req.region.unwrap_or("").to_string(); // Keep this since it defaults to an empty string
let bucket_name: String = req
.bucket
.ok_or_else(|| {
Error::InvalidBucketName(String::from("Missing bucket name in request"))
})?
.to_string();
let object_name: String = req
.object
.ok_or_else(|| {
Error::InvalidObjectName(String::from("Missing object name in request"))
})?
.to_string();
Ok(ObjectPromptResponse {
headers,
region,
bucket_name,
object_name,
prompt_response,
})
}
}

View File

@ -74,16 +74,34 @@ impl FromS3Response for CreateMultipartUploadResponse2 {
req: S3Request<'a>, req: S3Request<'a>,
response: reqwest::Response, response: reqwest::Response,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let header_map = response.headers().clone(); let headers = response.headers().clone();
let body = response.bytes().await?; let body = response.bytes().await?;
let root = Element::parse(body.reader())?; let root = Element::parse(body.reader())?;
let region: String = req.region.unwrap_or("").to_string(); // Keep this since it defaults to an empty string
let bucket_name: String = req
.bucket
.ok_or_else(|| {
Error::InvalidBucketName(String::from("Missing bucket name in request"))
})?
.to_string();
let object_name: String = req
.object
.ok_or_else(|| {
Error::InvalidObjectName(String::from("Missing object name in request"))
})?
.to_string();
let upload_id: String = get_text(&root, "UploadId")?;
Ok(CreateMultipartUploadResponse2 { Ok(CreateMultipartUploadResponse2 {
headers: header_map.clone(), headers,
region: req.region.unwrap_or("").to_string(), region,
bucket_name: req.bucket.unwrap().to_string(), bucket_name,
object_name: req.object.unwrap().to_string(), object_name,
upload_id: get_text(&root, "UploadId")?, upload_id,
}) })
} }
} }

View File

@ -335,7 +335,9 @@ pub fn match_region(value: &str) -> bool {
/// Validates given bucket name /// Validates given bucket name
pub fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<(), Error> { pub fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<(), Error> {
if bucket_name.trim().is_empty() { let bucket_name: &str = bucket_name.trim();
if bucket_name.is_empty() {
return Err(Error::InvalidBucketName(String::from( return Err(Error::InvalidBucketName(String::from(
"bucket name cannot be empty", "bucket name cannot be empty",
))); )));
@ -368,20 +370,23 @@ pub fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<(), Error> {
} }
if bucket_name.contains("..") || bucket_name.contains(".-") || bucket_name.contains("-.") { if bucket_name.contains("..") || bucket_name.contains(".-") || bucket_name.contains("-.") {
return Err(Error::InvalidBucketName(String::from( return Err(Error::InvalidBucketName(format!(
"bucket name contains invalid successive characters '..', '.-' or '-.'", "bucket name ('{}') contains invalid successive characters '..', '.-' or '-.'",
bucket_name
))); )));
} }
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(String::from( return Err(Error::InvalidBucketName(format!(
"bucket name does not follow S3 standards strictly", "bucket name ('{}') does not follow S3 standards strictly",
bucket_name
))); )));
} }
} else if !VALID_BUCKET_NAME_REGEX.is_match(bucket_name) { } else if !VALID_BUCKET_NAME_REGEX.is_match(bucket_name) {
return Err(Error::InvalidBucketName(String::from( return Err(Error::InvalidBucketName(format!(
"bucket name does not follow S3 standards", "bucket name ('{}') does not follow S3 standards",
bucket_name
))); )));
} }