diff --git a/src/api/api_server.rs b/src/api/api_server.rs index c4606226..b34030fd 100644 --- a/src/api/api_server.rs +++ b/src/api/api_server.rs @@ -107,25 +107,25 @@ async fn handler_inner(garage: Arc, req: Request) -> Result return handle_request_without_bucket(garage, req, api_key, endpoint).await, Some(bucket) => bucket.to_string(), }; + // Special code path for CreateBucket API endpoint + if let Endpoint::CreateBucket {} = endpoint { + return handle_create_bucket(&garage, req, content_sha256, api_key, bucket_name).await; + } + let bucket_id = resolve_bucket(&garage, &bucket_name, &api_key).await?; let allowed = match endpoint.authorization_type() { - Authorization::Read(_) => api_key.allow_read(&bucket_id), - Authorization::Write(_) => api_key.allow_write(&bucket_id), - Authorization::Owner(_) => api_key.allow_owner(&bucket_id), + Authorization::Read => api_key.allow_read(&bucket_id), + Authorization::Write => api_key.allow_write(&bucket_id), + Authorization::Owner => api_key.allow_owner(&bucket_id), _ => unreachable!(), }; @@ -142,7 +142,6 @@ async fn handler_inner(garage: Arc, req: Request) -> Result { handle_put_part( garage, @@ -155,14 +154,11 @@ async fn handler_inner(garage: Arc, req: Request) -> Result { - handle_copy(garage, &api_key, &req, bucket_id, &key).await - } + Endpoint::CopyObject { key } => handle_copy(garage, &api_key, &req, bucket_id, &key).await, Endpoint::UploadPartCopy { key, part_number, upload_id, - .. } => { handle_upload_part_copy( garage, @@ -175,25 +171,21 @@ async fn handler_inner(garage: Arc, req: Request) -> Result { + Endpoint::PutObject { key } => { handle_put(garage, req, bucket_id, &key, &api_key, content_sha256).await } - Endpoint::AbortMultipartUpload { key, upload_id, .. } => { + Endpoint::AbortMultipartUpload { key, upload_id } => { handle_abort_multipart_upload(garage, bucket_id, &key, &upload_id).await } Endpoint::DeleteObject { key, .. } => handle_delete(garage, bucket_id, &key).await, - Endpoint::CreateMultipartUpload { bucket, key } => { - handle_create_multipart_upload(garage, &req, &bucket, bucket_id, &key).await + Endpoint::CreateMultipartUpload { key } => { + handle_create_multipart_upload(garage, &req, &bucket_name, bucket_id, &key).await } - Endpoint::CompleteMultipartUpload { - bucket, - key, - upload_id, - } => { + Endpoint::CompleteMultipartUpload { key, upload_id } => { handle_complete_multipart_upload( garage, req, - &bucket, + &bucket_name, bucket_id, &key, &upload_id, @@ -201,19 +193,18 @@ async fn handler_inner(garage: Arc, req: Request) -> Result unreachable!(), - Endpoint::HeadBucket { .. } => { + Endpoint::CreateBucket {} => unreachable!(), + Endpoint::HeadBucket {} => { let empty_body: Body = Body::from(vec![]); let response = Response::builder().body(empty_body).unwrap(); Ok(response) } - Endpoint::DeleteBucket { .. } => { + Endpoint::DeleteBucket {} => { handle_delete_bucket(&garage, bucket_id, bucket_name, api_key).await } - Endpoint::GetBucketLocation { .. } => handle_get_bucket_location(garage), - Endpoint::GetBucketVersioning { .. } => handle_get_bucket_versioning(), + Endpoint::GetBucketLocation {} => handle_get_bucket_location(garage), + Endpoint::GetBucketVersioning {} => handle_get_bucket_versioning(), Endpoint::ListObjects { - bucket, delimiter, encoding_type, marker, @@ -224,7 +215,7 @@ async fn handler_inner(garage: Arc, req: Request) -> Result, req: Request) -> Result, req: Request) -> Result, req: Request) -> Result, req: Request) -> Result, req: Request) -> Result { + Endpoint::DeleteObjects {} => { handle_delete_objects(garage, bucket_id, req, content_sha256).await } - Endpoint::GetBucketWebsite { .. } => handle_get_website(garage, bucket_id).await, - Endpoint::PutBucketWebsite { .. } => { + Endpoint::GetBucketWebsite {} => handle_get_website(garage, bucket_id).await, + Endpoint::PutBucketWebsite {} => { handle_put_website(garage, bucket_id, req, content_sha256).await } - Endpoint::DeleteBucketWebsite { .. } => handle_delete_website(garage, bucket_id).await, + Endpoint::DeleteBucketWebsite {} => handle_delete_website(garage, bucket_id).await, endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())), } } diff --git a/src/api/s3_router.rs b/src/api/s3_router.rs index a8ac0086..9add5e4a 100644 --- a/src/api/s3_router.rs +++ b/src/api/s3_router.rs @@ -8,6 +8,17 @@ use hyper::{HeaderMap, Method, Request}; /// This macro is used to generate very repetitive match {} blocks in this module /// It is _not_ made to be used anywhere else macro_rules! s3_match { + (@match $enum:expr , [ $($endpoint:ident,)* ]) => {{ + // usage: s3_match {@match my_enum, [ VariantWithField1, VariantWithField2 ..] } + // returns true if the variant was one of the listed variants, false otherwise. + use Endpoint::*; + match $enum { + $( + $endpoint { .. } => true, + )* + _ => false + } + }}; (@extract $enum:expr , $param:ident, [ $($endpoint:ident,)* ]) => {{ // usage: s3_match {@extract my_enum, field_name, [ VariantWithField1, VariantWithField2 ..] } // returns Some(field_value), or None if the variant was not one of the listed variants. @@ -19,10 +30,10 @@ macro_rules! s3_match { _ => None } }}; - (@gen_parser ($keyword:expr, $key:expr, $bucket:expr, $query:expr, $header:expr), + (@gen_parser ($keyword:expr, $key:expr, $query:expr, $header:expr), key: [$($kw_k:ident $(if $required_k:ident)? $(header $header_k:expr)? => $api_k:ident $(($($conv_k:ident :: $param_k:ident),*))?,)*], no_key: [$($kw_nk:ident $(if $required_nk:ident)? $(if_header $header_nk:expr)? => $api_nk:ident $(($($conv_nk:ident :: $param_nk:ident),*))?,)*]) => {{ - // usage: s3_match {@gen_parser (keyword, key, bucket, query, header), + // usage: s3_match {@gen_parser (keyword, key, query, header), // key: [ // SOME_KEYWORD => VariantWithKey, // ... @@ -38,7 +49,6 @@ macro_rules! s3_match { match ($keyword, !$key.is_empty()){ $( ($kw_k, true) if true $(&& $query.$required_k.is_some())? $(&& $header.contains_key($header_k))? => Ok($api_k { - bucket: $bucket, key: $key, $($( $param_k: s3_match!(@@parse_param $query, $conv_k, $param_k), @@ -47,7 +57,6 @@ macro_rules! s3_match { )* $( ($kw_nk, false) $(if $query.$required_nk.is_some())? $(if $header.contains($header_nk))? => Ok($api_nk { - bucket: $bucket, $($( $param_nk: s3_match!(@@parse_param $query, $conv_nk, $param_nk), )*)? @@ -87,7 +96,6 @@ macro_rules! s3_match { $( $(#[$outer:meta])* $variant:ident $({ - bucket: String, $($name:ident: $ty:ty,)* })?, )* @@ -97,7 +105,6 @@ macro_rules! s3_match { $( $(#[$outer])* $variant $({ - bucket: String, $($name: $ty, )* })?, )* @@ -108,15 +115,6 @@ macro_rules! s3_match { $(Endpoint::$variant $({ $($name: _,)* .. })? => stringify!($variant),)* } } - - /// Get the bucket the request target. Returns None for requests not related to a bucket. - pub fn get_bucket(&self) -> Option<&str> { - match self { - $( - Endpoint::$variant $({ bucket, $($name: _,)* .. })? => s3_match!{@if ($(bucket $($name)*)?) then (Some(bucket)) else (None)}, - )* - } - } } }; (@if ($($cond:tt)+) then ($($then:tt)*) else ($($else:tt)*)) => { @@ -138,215 +136,158 @@ s3_match! {@func #[derive(Debug, Clone, PartialEq, Eq)] pub enum Endpoint { AbortMultipartUpload { - bucket: String, key: String, upload_id: String, }, CompleteMultipartUpload { - bucket: String, key: String, upload_id: String, }, CopyObject { - bucket: String, key: String, }, CreateBucket { - bucket: String, }, CreateMultipartUpload { - bucket: String, key: String, }, DeleteBucket { - bucket: String, }, DeleteBucketAnalyticsConfiguration { - bucket: String, id: String, }, DeleteBucketCors { - bucket: String, }, DeleteBucketEncryption { - bucket: String, }, DeleteBucketIntelligentTieringConfiguration { - bucket: String, id: String, }, DeleteBucketInventoryConfiguration { - bucket: String, id: String, }, DeleteBucketLifecycle { - bucket: String, }, DeleteBucketMetricsConfiguration { - bucket: String, id: String, }, DeleteBucketOwnershipControls { - bucket: String, }, DeleteBucketPolicy { - bucket: String, }, DeleteBucketReplication { - bucket: String, }, DeleteBucketTagging { - bucket: String, }, DeleteBucketWebsite { - bucket: String, }, DeleteObject { - bucket: String, key: String, version_id: Option, }, DeleteObjects { - bucket: String, }, DeleteObjectTagging { - bucket: String, key: String, version_id: Option, }, DeletePublicAccessBlock { - bucket: String, }, GetBucketAccelerateConfiguration { - bucket: String, }, GetBucketAcl { - bucket: String, }, GetBucketAnalyticsConfiguration { - bucket: String, id: String, }, GetBucketCors { - bucket: String, }, GetBucketEncryption { - bucket: String, }, GetBucketIntelligentTieringConfiguration { - bucket: String, id: String, }, GetBucketInventoryConfiguration { - bucket: String, id: String, }, GetBucketLifecycleConfiguration { - bucket: String, }, GetBucketLocation { - bucket: String, }, GetBucketLogging { - bucket: String, }, GetBucketMetricsConfiguration { - bucket: String, id: String, }, GetBucketNotificationConfiguration { - bucket: String, }, GetBucketOwnershipControls { - bucket: String, }, GetBucketPolicy { - bucket: String, }, GetBucketPolicyStatus { - bucket: String, }, GetBucketReplication { - bucket: String, }, GetBucketRequestPayment { - bucket: String, }, GetBucketTagging { - bucket: String, }, GetBucketVersioning { - bucket: String, }, GetBucketWebsite { - bucket: String, }, /// There are actually many more query parameters, used to add headers to the answer. They were /// not added here as they are best handled in a dedicated route. GetObject { - bucket: String, key: String, part_number: Option, version_id: Option, }, GetObjectAcl { - bucket: String, key: String, version_id: Option, }, GetObjectLegalHold { - bucket: String, key: String, version_id: Option, }, GetObjectLockConfiguration { - bucket: String, }, GetObjectRetention { - bucket: String, key: String, version_id: Option, }, GetObjectTagging { - bucket: String, key: String, version_id: Option, }, GetObjectTorrent { - bucket: String, key: String, }, GetPublicAccessBlock { - bucket: String, }, HeadBucket { - bucket: String, }, HeadObject { - bucket: String, key: String, part_number: Option, version_id: Option, }, ListBucketAnalyticsConfigurations { - bucket: String, continuation_token: Option, }, ListBucketIntelligentTieringConfigurations { - bucket: String, continuation_token: Option, }, ListBucketInventoryConfigurations { - bucket: String, continuation_token: Option, }, ListBucketMetricsConfigurations { - bucket: String, continuation_token: Option, }, ListBuckets, ListMultipartUploads { - bucket: String, delimiter: Option, encoding_type: Option, key_marker: Option, @@ -355,7 +296,6 @@ pub enum Endpoint { upload_id_marker: Option, }, ListObjects { - bucket: String, delimiter: Option, encoding_type: Option, marker: Option, @@ -363,7 +303,6 @@ pub enum Endpoint { prefix: Option, }, ListObjectsV2 { - bucket: String, // This value should always be 2. It is not checked when constructing the struct list_type: String, continuation_token: Option, @@ -375,7 +314,6 @@ pub enum Endpoint { start_after: Option, }, ListObjectVersions { - bucket: String, delimiter: Option, encoding_type: Option, key_marker: Option, @@ -384,119 +322,89 @@ pub enum Endpoint { version_id_marker: Option, }, ListParts { - bucket: String, key: String, max_parts: Option, part_number_marker: Option, upload_id: String, }, PutBucketAccelerateConfiguration { - bucket: String, }, PutBucketAcl { - bucket: String, }, PutBucketAnalyticsConfiguration { - bucket: String, id: String, }, PutBucketCors { - bucket: String, }, PutBucketEncryption { - bucket: String, }, PutBucketIntelligentTieringConfiguration { - bucket: String, id: String, }, PutBucketInventoryConfiguration { - bucket: String, id: String, }, PutBucketLifecycleConfiguration { - bucket: String, }, PutBucketLogging { - bucket: String, }, PutBucketMetricsConfiguration { - bucket: String, id: String, }, PutBucketNotificationConfiguration { - bucket: String, }, PutBucketOwnershipControls { - bucket: String, }, PutBucketPolicy { - bucket: String, }, PutBucketReplication { - bucket: String, }, PutBucketRequestPayment { - bucket: String, }, PutBucketTagging { - bucket: String, }, PutBucketVersioning { - bucket: String, }, PutBucketWebsite { - bucket: String, }, PutObject { - bucket: String, key: String, }, PutObjectAcl { - bucket: String, key: String, version_id: Option, }, PutObjectLegalHold { - bucket: String, key: String, version_id: Option, }, PutObjectLockConfiguration { - bucket: String, }, PutObjectRetention { - bucket: String, key: String, version_id: Option, }, PutObjectTagging { - bucket: String, key: String, version_id: Option, }, PutPublicAccessBlock { - bucket: String, }, RestoreObject { - bucket: String, key: String, version_id: Option, }, SelectObjectContent { - bucket: String, key: String, // This value should always be 2. It is not checked when constructing the struct select_type: String, }, UploadPart { - bucket: String, key: String, part_number: u64, upload_id: String, }, UploadPartCopy { - bucket: String, key: String, part_number: u64, upload_id: String, @@ -506,12 +414,16 @@ pub enum Endpoint { impl Endpoint { /// Determine which S3 endpoint a request is for using the request, and a bucket which was /// possibly extracted from the Host header. - pub fn from_request(req: &Request, bucket: Option) -> Result { + /// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets + pub fn from_request( + req: &Request, + bucket: Option, + ) -> Result<(Self, Option), Error> { let uri = req.uri(); let path = uri.path().trim_start_matches('/'); let query = uri.query(); if bucket.is_none() && path.is_empty() { - return Ok(Self::ListBuckets); + return Ok((Self::ListBuckets, None)); } let (bucket, key) = if let Some(bucket) = bucket { @@ -529,29 +441,25 @@ impl Endpoint { let mut query = QueryParameters::from_query(query.unwrap_or_default())?; let res = match *req.method() { - Method::GET => Self::from_get(bucket, key, &mut query)?, - Method::HEAD => Self::from_head(bucket, key, &mut query)?, - Method::POST => Self::from_post(bucket, key, &mut query)?, - Method::PUT => Self::from_put(bucket, key, &mut query, req.headers())?, - Method::DELETE => Self::from_delete(bucket, key, &mut query)?, + Method::GET => Self::from_get(key, &mut query)?, + Method::HEAD => Self::from_head(key, &mut query)?, + Method::POST => Self::from_post(key, &mut query)?, + Method::PUT => Self::from_put(key, &mut query, req.headers())?, + Method::DELETE => Self::from_delete(key, &mut query)?, _ => return Err(Error::BadRequest("Unknown method".to_owned())), }; if let Some(message) = query.nonempty_message() { debug!("Unused query parameter: {}", message) } - Ok(res) + Ok((res, Some(bucket))) } /// Determine which endpoint a request is for, knowing it is a GET. - fn from_get( - bucket: String, - key: String, - query: &mut QueryParameters<'_>, - ) -> Result { + fn from_get(key: String, query: &mut QueryParameters<'_>) -> Result { s3_match! { @gen_parser - (query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None), + (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ EMPTY if upload_id => ListParts (query::upload_id, opt_parse::max_parts, opt_parse::part_number_marker), EMPTY => GetObject (query_opt::version_id, opt_parse::part_number), @@ -605,14 +513,10 @@ impl Endpoint { } /// Determine which endpoint a request is for, knowing it is a HEAD. - fn from_head( - bucket: String, - key: String, - query: &mut QueryParameters<'_>, - ) -> Result { + fn from_head(key: String, query: &mut QueryParameters<'_>) -> Result { s3_match! { @gen_parser - (query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None), + (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ EMPTY => HeadObject(opt_parse::part_number, query_opt::version_id), ], @@ -623,14 +527,10 @@ impl Endpoint { } /// Determine which endpoint a request is for, knowing it is a POST. - fn from_post( - bucket: String, - key: String, - query: &mut QueryParameters<'_>, - ) -> Result { + fn from_post(key: String, query: &mut QueryParameters<'_>) -> Result { s3_match! { @gen_parser - (query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None), + (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ EMPTY if upload_id => CompleteMultipartUpload (query::upload_id), RESTORE => RestoreObject (query_opt::version_id), @@ -645,14 +545,13 @@ impl Endpoint { /// Determine which endpoint a request is for, knowing it is a PUT. fn from_put( - bucket: String, key: String, query: &mut QueryParameters<'_>, headers: &HeaderMap, ) -> Result { s3_match! { @gen_parser - (query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, headers), + (query.keyword.take().unwrap_or_default().as_ref(), key, query, headers), key: [ EMPTY if part_number header "x-amz-copy-source" => UploadPartCopy (parse::part_number, query::upload_id), EMPTY header "x-amz-copy-source" => CopyObject, @@ -691,14 +590,10 @@ impl Endpoint { } /// Determine which endpoint a request is for, knowing it is a DELETE. - fn from_delete( - bucket: String, - key: String, - query: &mut QueryParameters<'_>, - ) -> Result { + fn from_delete(key: String, query: &mut QueryParameters<'_>) -> Result { s3_match! { @gen_parser - (query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None), + (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ EMPTY if upload_id => AbortMultipartUpload (query::upload_id), EMPTY => DeleteObject (query_opt::version_id), @@ -759,16 +654,13 @@ impl Endpoint { } /// Get the kind of authorization which is required to perform the operation. - pub fn authorization_type(&self) -> Authorization<'_> { - let bucket = if let Some(bucket) = self.get_bucket() { - bucket - } else { + pub fn authorization_type(&self) -> Authorization { + if let Endpoint::ListBuckets = self { return Authorization::None; }; let readonly = s3_match! { - @extract + @match self, - bucket, [ GetBucketAccelerateConfiguration, GetBucketAcl, @@ -810,41 +702,38 @@ impl Endpoint { ListParts, SelectObjectContent, ] - } - .is_some(); + }; let owner = s3_match! { - @extract + @match self, - bucket, [ DeleteBucket, GetBucketWebsite, PutBucketWebsite, DeleteBucketWebsite, ] - } - .is_some(); + }; if readonly { - Authorization::Read(bucket) + Authorization::Read } else if owner { - Authorization::Owner(bucket) + Authorization::Owner } else { - Authorization::Write(bucket) + Authorization::Write } } } /// What kind of authorization is required to perform a given action #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Authorization<'a> { +pub enum Authorization { /// No authorization is required None, - /// Having Read permission on bucket .0 is required - Read(&'a str), - /// Having Write permission on bucket .0 is required - Write(&'a str), - /// Having Owner permission on bucket .0 is required - Owner(&'a str), + /// Having Read permission on bucket + Read, + /// Having Write permission on bucket + Write, + /// Having Owner permission on bucket + Owner, } /// This macro is used to generate part of the code in this module. It must be called only one, and @@ -985,7 +874,7 @@ mod tests { uri: &str, bucket: Option, header: Option<(&str, &str)>, - ) -> Endpoint { + ) -> (Endpoint, Option) { let mut req = Request::builder().method(method).uri(uri); if let Some((k, v)) = header { req = req.header(k, v) @@ -1000,13 +889,13 @@ mod tests { $( assert!( matches!( - parse(test_cases!{@actual_method $method}, $uri, Some("my_bucket".to_owned()), None), + parse(test_cases!{@actual_method $method}, $uri, Some("my_bucket".to_owned()), None).0, Endpoint::$variant { .. } ) ); assert!( matches!( - parse(test_cases!{@actual_method $method}, concat!("/my_bucket", $uri), None, None), + parse(test_cases!{@actual_method $method}, concat!("/my_bucket", $uri), None, None).0, Endpoint::$variant { .. } ) ); @@ -1025,78 +914,82 @@ mod tests { (@actual_method OWNER_DELETE) => {{ "DELETE" }}; (@auth HEAD $uri:expr) => {{ - assert_eq!(parse("HEAD", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Read("my_bucket")) + assert_eq!(parse("HEAD", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Read) }}; (@auth GET $uri:expr) => {{ - assert_eq!(parse("GET", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Read("my_bucket")) + assert_eq!(parse("GET", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Read) }}; (@auth OWNER_GET $uri:expr) => {{ - assert_eq!(parse("GET", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Owner("my_bucket")) + assert_eq!(parse("GET", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Owner) }}; (@auth PUT $uri:expr) => {{ - assert_eq!(parse("PUT", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Write("my_bucket")) + assert_eq!(parse("PUT", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Write) }}; (@auth OWNER_PUT $uri:expr) => {{ - assert_eq!(parse("PUT", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Owner("my_bucket")) + assert_eq!(parse("PUT", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Owner) }}; (@auth POST $uri:expr) => {{ - assert_eq!(parse("POST", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Write("my_bucket")) + assert_eq!(parse("POST", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Write) }}; (@auth DELETE $uri:expr) => {{ - assert_eq!(parse("DELETE", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Write("my_bucket")) + assert_eq!(parse("DELETE", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Write) }}; (@auth OWNER_DELETE $uri:expr) => {{ - assert_eq!(parse("DELETE", concat!("/my_bucket", $uri), None, None).authorization_type(), - Authorization::Owner("my_bucket")) + assert_eq!(parse("DELETE", concat!("/my_bucket", $uri), None, None).0.authorization_type(), + Authorization::Owner) }}; } #[test] fn test_bucket_extraction() { assert_eq!( - parse("GET", "/my/key", Some("my_bucket".to_owned()), None).get_bucket(), - parse("GET", "/my_bucket/my/key", None, None).get_bucket() + parse("GET", "/my/key", Some("my_bucket".to_owned()), None).1, + parse("GET", "/my_bucket/my/key", None, None).1 ); assert_eq!( - parse("GET", "/my_bucket/my/key", None, None) - .get_bucket() - .unwrap(), + parse("GET", "/my_bucket/my/key", None, None).1.unwrap(), "my_bucket" ); - assert!(parse("GET", "/", None, None).get_bucket().is_none()); + assert!(parse("GET", "/", None, None).1.is_none()); } #[test] fn test_key() { assert_eq!( - parse("GET", "/my/key", Some("my_bucket".to_owned()), None).get_key(), - parse("GET", "/my_bucket/my/key", None, None).get_key() + parse("GET", "/my/key", Some("my_bucket".to_owned()), None) + .0 + .get_key(), + parse("GET", "/my_bucket/my/key", None, None).0.get_key() ); assert_eq!( parse("GET", "/my_bucket/my/key", None, None) + .0 .get_key() .unwrap(), "my/key" ); assert_eq!( parse("GET", "/my_bucket/my/key?acl", None, None) + .0 .get_key() .unwrap(), "my/key" ); assert!(parse("GET", "/my_bucket/?list-type=2", None, None) + .0 .get_key() .is_none()); assert_eq!( parse("GET", "/my_bucket/%26%2B%3F%25%C3%A9/something", None, None) + .0 .get_key() .unwrap(), "&+?%é/something" @@ -1268,11 +1161,11 @@ mod tests { ); // no bucket, won't work with the rest of the test suite assert!(matches!( - parse("GET", "/", None, None), + parse("GET", "/", None, None).0, Endpoint::ListBuckets { .. } )); assert!(matches!( - parse("GET", "/", None, None).authorization_type(), + parse("GET", "/", None, None).0.authorization_type(), Authorization::None )); @@ -1283,16 +1176,8 @@ mod tests { "/Key+", Some("my_bucket".to_owned()), Some(("x-amz-copy-source", "some/key")) - ), - Endpoint::CopyObject { .. } - )); - assert!(matches!( - parse( - "PUT", - "/my_bucket/Key+", - None, - Some(("x-amz-copy-source", "some/key")) - ), + ) + .0, Endpoint::CopyObject { .. } )); assert!(matches!( @@ -1302,8 +1187,19 @@ mod tests { None, Some(("x-amz-copy-source", "some/key")) ) + .0, + Endpoint::CopyObject { .. } + )); + assert!(matches!( + parse( + "PUT", + "/my_bucket/Key+", + None, + Some(("x-amz-copy-source", "some/key")) + ) + .0 .authorization_type(), - Authorization::Write("my_bucket") + Authorization::Write )); // require a header @@ -1313,16 +1209,8 @@ mod tests { "/Key+?partNumber=2&uploadId=UploadId", Some("my_bucket".to_owned()), Some(("x-amz-copy-source", "some/key")) - ), - Endpoint::UploadPartCopy { .. } - )); - assert!(matches!( - parse( - "PUT", - "/my_bucket/Key+?partNumber=2&uploadId=UploadId", - None, - Some(("x-amz-copy-source", "some/key")) - ), + ) + .0, Endpoint::UploadPartCopy { .. } )); assert!(matches!( @@ -1332,8 +1220,19 @@ mod tests { None, Some(("x-amz-copy-source", "some/key")) ) + .0, + Endpoint::UploadPartCopy { .. } + )); + assert!(matches!( + parse( + "PUT", + "/my_bucket/Key+?partNumber=2&uploadId=UploadId", + None, + Some(("x-amz-copy-source", "some/key")) + ) + .0 .authorization_type(), - Authorization::Write("my_bucket") + Authorization::Write )); // POST request, but with GET semantic for permissions purpose @@ -1343,17 +1242,19 @@ mod tests { "/{Key+}?select&select-type=2", Some("my_bucket".to_owned()), None - ), + ) + .0, Endpoint::SelectObjectContent { .. } )); assert!(matches!( - parse("POST", "/my_bucket/{Key+}?select&select-type=2", None, None), + parse("POST", "/my_bucket/{Key+}?select&select-type=2", None, None).0, Endpoint::SelectObjectContent { .. } )); assert!(matches!( parse("POST", "/my_bucket/{Key+}?select&select-type=2", None, None) + .0 .authorization_type(), - Authorization::Read("my_bucket") + Authorization::Read )); } }