use url::{ParseError, Url};
use crate::actions::{
AbortMultipartUpload, CompleteMultipartUpload, CreateBucket, CreateMultipartUpload,
DeleteBucket, DeleteObject, GetObject, ListObjectsV2, PutObject, UploadPart,
};
use crate::signing::util::percent_encode_path;
use crate::Credentials;
#[derive(Debug, Clone)]
pub struct Bucket {
base_url: Url,
name: String,
region: String,
}
impl Bucket {
pub fn new(endpoint: Url, path_style: bool, name: String, region: String) -> Option<Self> {
let _ = endpoint.host_str()?;
match endpoint.scheme() {
"http" | "https" => {}
_ => return None,
};
let base_url = base_url(endpoint, &name, path_style);
Some(Self {
base_url,
name,
region,
})
}
pub fn base_url(&self) -> &Url {
&self.base_url
}
pub fn name(&self) -> &str {
&self.name
}
pub fn region(&self) -> &str {
&self.region
}
pub fn object_url(&self, object: &str) -> Result<Url, ParseError> {
let object = percent_encode_path(object);
self.base_url.join(&object)
}
}
fn base_url(mut endpoint: Url, name: &str, path_style: bool) -> Url {
if path_style {
let path = format!("{}/", name);
endpoint.join(&path).unwrap()
} else {
let host = format!("{}.{}", name, endpoint.host_str().unwrap());
endpoint.set_host(Some(&host)).unwrap();
endpoint
}
}
impl Bucket {
pub fn create_bucket<'a>(&'a self, credentials: &'a Credentials) -> CreateBucket<'a> {
CreateBucket::new(self, Some(credentials))
}
pub fn delete_bucket<'a>(&'a self, credentials: &'a Credentials) -> DeleteBucket<'a> {
DeleteBucket::new(self, credentials)
}
}
impl Bucket {
pub fn get_object<'a>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
) -> GetObject<'a> {
GetObject::new(self, credentials, object)
}
pub fn list_objects_v2<'a>(
&'a self,
credentials: Option<&'a Credentials>,
) -> ListObjectsV2<'a> {
ListObjectsV2::new(self, credentials)
}
pub fn put_object<'a>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
) -> PutObject<'a> {
PutObject::new(self, credentials, object)
}
pub fn delete_object<'a>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
) -> DeleteObject<'a> {
DeleteObject::new(self, credentials, object)
}
}
impl Bucket {
pub fn create_multipart_upload<'a>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
) -> CreateMultipartUpload<'a> {
CreateMultipartUpload::new(self, credentials, object)
}
pub fn upload_part<'a>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
part_number: u16,
upload_id: &'a str,
) -> UploadPart<'a> {
UploadPart::new(self, credentials, object, part_number, upload_id)
}
pub fn complete_multipart_upload<'a, I>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
upload_id: &'a str,
etags: I,
) -> CompleteMultipartUpload<'a, I> {
CompleteMultipartUpload::new(self, credentials, object, upload_id, etags)
}
pub fn abort_multipart_upload<'a>(
&'a self,
credentials: Option<&'a Credentials>,
object: &'a str,
upload_id: &'a str,
) -> AbortMultipartUpload<'a> {
AbortMultipartUpload::new(self, credentials, object, upload_id)
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn new_pathstyle() {
let endpoint: Url = "https://s3-eu-west-1.amazonaws.com".parse().unwrap();
let base_url: Url = "https://s3-eu-west-1.amazonaws.com/rusty-s3/"
.parse()
.unwrap();
let name = "rusty-s3";
let region = "eu-west-1";
let bucket = Bucket::new(endpoint, true, name.into(), region.into()).unwrap();
assert_eq!(bucket.base_url(), &base_url);
assert_eq!(bucket.name(), name);
assert_eq!(bucket.region(), region);
}
#[test]
fn new_domainstyle() {
let endpoint: Url = "https://s3-eu-west-1.amazonaws.com".parse().unwrap();
let base_url: Url = "https://rusty-s3.s3-eu-west-1.amazonaws.com"
.parse()
.unwrap();
let name = "rusty-s3";
let region = "eu-west-1";
let bucket = Bucket::new(endpoint, false, name.into(), region.into()).unwrap();
assert_eq!(bucket.base_url(), &base_url);
assert_eq!(bucket.name(), name);
assert_eq!(bucket.region(), region);
}
#[test]
fn new_bad_scheme() {
let endpoint = "file:///home/something".parse().unwrap();
let name = "rusty-s3";
let region = "eu-west-1";
assert!(Bucket::new(endpoint, true, name.into(), region.into()).is_none());
}
#[test]
fn object_url_pathstyle() {
let endpoint: Url = "https://s3-eu-west-1.amazonaws.com".parse().unwrap();
let name = "rusty-s3";
let region = "eu-west-1";
let bucket = Bucket::new(endpoint, true, name.into(), region.into()).unwrap();
let path_style = bucket.object_url("something/cat.jpg").unwrap();
assert_eq!(
"https://s3-eu-west-1.amazonaws.com/rusty-s3/something/cat.jpg",
path_style.as_str()
);
}
#[test]
fn object_url_domainstyle() {
let endpoint: Url = "https://s3-eu-west-1.amazonaws.com".parse().unwrap();
let name = "rusty-s3";
let region = "eu-west-1";
let bucket = Bucket::new(endpoint, false, name.into(), region.into()).unwrap();
let domain_style = bucket.object_url("something/cat.jpg").unwrap();
assert_eq!(
"https://rusty-s3.s3-eu-west-1.amazonaws.com/something/cat.jpg",
domain_style.as_str()
);
}
#[test]
fn all_actions() {
let endpoint: Url = "https://s3-eu-west-1.amazonaws.com".parse().unwrap();
let name = "rusty-s3";
let region = "eu-west-1";
let bucket = Bucket::new(endpoint, true, name.into(), region.into()).unwrap();
let credentials = Credentials::new(
"AKIAIOSFODNN7EXAMPLE".into(),
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".into(),
);
let _ = bucket.create_bucket(&credentials);
let _ = bucket.get_object(Some(&credentials), "duck.jpg");
let _ = bucket.list_objects_v2(Some(&credentials));
let _ = bucket.put_object(Some(&credentials), "duck.jpg");
let _ = bucket.delete_object(Some(&credentials), "duck.jpg");
let _ = bucket.create_multipart_upload(Some(&credentials), "duck.jpg");
let _ = bucket.upload_part(Some(&credentials), "duck.jpg", 1, "abcd");
let _ = bucket.complete_multipart_upload(
Some(&credentials),
"duck.jpg",
"abcd",
["1234"].iter().copied(),
);
let _ = bucket.abort_multipart_upload(Some(&credentials), "duck.jpg", "abcd");
}
}