diff --git a/src/api/api_server.rs b/src/api/api_server.rs index 24b5ee71..6d6e5b68 100644 --- a/src/api/api_server.rs +++ b/src/api/api_server.rs @@ -228,26 +228,33 @@ async fn handler_inner(garage: Arc, req: Request) -> Result { - handle_list( - garage, - &ListObjectsQuery { - is_v2: list_type == "v2", - bucket, - delimiter: delimiter.map(|d| d.to_string()), - max_keys: max_keys.unwrap_or(1000), - prefix: prefix.unwrap_or_default(), - marker: None, - continuation_token, - start_after, - urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), - }, - ) - .await + if list_type == "2" { + handle_list( + garage, + &ListObjectsQuery { + is_v2: true, + bucket, + delimiter: delimiter.map(|d| d.to_string()), + max_keys: max_keys.unwrap_or(1000), + prefix: prefix.unwrap_or_default(), + marker: None, + continuation_token, + start_after, + urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), + }, + ) + .await + } else { + Err(Error::BadRequest(format!( + "Invalid endpoint: list-type={}", + list_type + ))) + } } Endpoint::DeleteObjects { bucket } => { handle_delete_objects(garage, &bucket, req, content_sha256).await } - _ => Err(Error::BadRequest("Unsupported call".to_string())), + endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())), } } diff --git a/src/api/error.rs b/src/api/error.rs index 4ad8ef82..9bb8f8e2 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -65,6 +65,10 @@ pub enum Error { /// The client sent an invalid request #[error(display = "Bad request: {}", _0)] BadRequest(String), + + /// The client sent a request for an action not supported by garage + #[error(display = "Unimplemented action: {}", _0)] + NotImplemented(String), } impl From for Error { @@ -94,6 +98,7 @@ impl Error { StatusCode::INTERNAL_SERVER_ERROR } Error::InvalidRange(_) => StatusCode::RANGE_NOT_SATISFIABLE, + Error::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED, _ => StatusCode::BAD_REQUEST, } } diff --git a/src/api/s3_router.rs b/src/api/s3_router.rs index 5b409151..f205f310 100644 --- a/src/api/s3_router.rs +++ b/src/api/s3_router.rs @@ -81,8 +81,54 @@ macro_rules! s3_match { .parse() .map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))? }}; + (@func + $(#[$doc:meta])* + pub enum Endpoint { + $( + $(#[$outer:meta])* + $variant:ident $({ + bucket: String, + $($name:ident: $ty:ty,)* + })?, + )* + }) => { + $(#[$doc])* + pub enum Endpoint { + $( + $(#[$outer])* + $variant $({ + bucket: String, + $($name: $ty, )* + })?, + )* + } + impl Endpoint { + pub fn name(&self) -> &'static str { + match self { + $(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)*)) => { + $($then)* + }; + (@if () then ($($then:tt)*) else ($($else:tt)*)) => { + $($else)* + }; } +s3_match! {@func + /// List of all S3 API endpoints. /// /// For each endpoint, it contains the parameters this endpoint receive by url (bucket, key and @@ -318,7 +364,7 @@ pub enum Endpoint { }, ListObjectsV2 { bucket: String, - /// This value should always be 2. It is not checked when constructing the struct + // This value should always be 2. It is not checked when constructing the struct list_type: String, continuation_token: Option, delimiter: Option, @@ -440,7 +486,7 @@ pub enum Endpoint { SelectObjectContent { bucket: String, key: String, - /// This value should always be 2. It is not checked when constructing the struct + // This value should always be 2. It is not checked when constructing the struct select_type: String, }, UploadPart { @@ -455,7 +501,7 @@ pub enum Endpoint { part_number: u64, upload_id: String, }, -} +}} impl Endpoint { /// Determine which S3 endpoint a request is for using the request, and a bucket which was @@ -496,11 +542,9 @@ impl Endpoint { }; if let Some(message) = query.nonempty_message() { - // maybe this should just be a warn! ? - Err(Error::BadRequest(message.to_owned())) - } else { - Ok(res) + debug!("Unused query parameter: {}", message) } + Ok(res) } /// Determine which endpoint a request is for, knowing it is a GET. @@ -683,107 +727,6 @@ impl Endpoint { } } - /// Get the bucket the request target. Returns None for requests not related to a bucket. - pub fn get_bucket(&self) -> Option<&str> { - s3_match! { - @extract - self, - bucket, - [ - AbortMultipartUpload, - CompleteMultipartUpload, - CopyObject, - CreateBucket, - CreateMultipartUpload, - DeleteBucket, - DeleteBucketAnalyticsConfiguration, - DeleteBucketCors, - DeleteBucketEncryption, - DeleteBucketIntelligentTieringConfiguration, - DeleteBucketInventoryConfiguration, - DeleteBucketLifecycle, - DeleteBucketMetricsConfiguration, - DeleteBucketOwnershipControls, - DeleteBucketPolicy, - DeleteBucketReplication, - DeleteBucketTagging, - DeleteBucketWebsite, - DeleteObject, - DeleteObjects, - DeleteObjectTagging, - DeletePublicAccessBlock, - GetBucketAccelerateConfiguration, - GetBucketAcl, - GetBucketAnalyticsConfiguration, - GetBucketCors, - GetBucketEncryption, - GetBucketIntelligentTieringConfiguration, - GetBucketInventoryConfiguration, - GetBucketLifecycleConfiguration, - GetBucketLocation, - GetBucketLogging, - GetBucketMetricsConfiguration, - GetBucketNotificationConfiguration, - GetBucketOwnershipControls, - GetBucketPolicy, - GetBucketPolicyStatus, - GetBucketReplication, - GetBucketRequestPayment, - GetBucketTagging, - GetBucketVersioning, - GetBucketWebsite, - GetObject, - GetObjectAcl, - GetObjectLegalHold, - GetObjectLockConfiguration, - GetObjectRetention, - GetObjectTagging, - GetObjectTorrent, - GetPublicAccessBlock, - HeadBucket, - HeadObject, - ListBucketAnalyticsConfigurations, - ListBucketIntelligentTieringConfigurations, - ListBucketInventoryConfigurations, - ListBucketMetricsConfigurations, - ListMultipartUploads, - ListObjects, - ListObjectsV2, - ListObjectVersions, - ListParts, - PutBucketAccelerateConfiguration, - PutBucketAcl, - PutBucketAnalyticsConfiguration, - PutBucketCors, - PutBucketEncryption, - PutBucketIntelligentTieringConfiguration, - PutBucketInventoryConfiguration, - PutBucketLifecycleConfiguration, - PutBucketLogging, - PutBucketMetricsConfiguration, - PutBucketNotificationConfiguration, - PutBucketOwnershipControls, - PutBucketPolicy, - PutBucketReplication, - PutBucketRequestPayment, - PutBucketTagging, - PutBucketVersioning, - PutBucketWebsite, - PutObject, - PutObjectAcl, - PutObjectLegalHold, - PutObjectLockConfiguration, - PutObjectRetention, - PutObjectTagging, - PutPublicAccessBlock, - RestoreObject, - SelectObjectContent, - UploadPart, - UploadPartCopy, - ] - } - } - /// Get the key the request target. Returns None for requests which don't use a key. #[allow(dead_code)] pub fn get_key(&self) -> Option<&str> { @@ -929,10 +872,8 @@ macro_rules! generateQueryParameters { } continue; } else { - return Err(Error::BadRequest(format!( - "Unknown query parameter '{}'", - k - ))); + debug!("Received an unknown query parameter: '{}'", k); + false } } }; @@ -953,7 +894,7 @@ macro_rules! generateQueryParameters { Some("Keyword not used") } $( else if self.$name.is_some() { - Some(concat!("Query parameter not needed: '", $rest, "'" )) + Some(concat!("'", $rest, "'")) } )* else { None