add proper request router for s3 api #163
2 changed files with 903 additions and 0 deletions
|
@ -19,4 +19,5 @@ mod s3_delete;
|
|||
pub mod s3_get;
|
||||
mod s3_list;
|
||||
mod s3_put;
|
||||
mod s3_router;
|
||||
mod s3_xml;
|
||||
|
|
902
src/api/s3_router.rs
Normal file
902
src/api/s3_router.rs
Normal file
|
@ -0,0 +1,902 @@
|
|||
use crate::error::{Error, OkOrBadRequest};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::{HeaderMap, Method, Uri};
|
||||
|
||||
macro_rules! s3_match {
|
||||
(@extract $enum:expr , $param:ident, [ $($endpoint:ident,)* ]) => {{
|
||||
use Endpoint::*;
|
||||
match $enum {
|
||||
$(
|
||||
$endpoint {$param, ..} => Some($param),
|
||||
)*
|
||||
_ => None
|
||||
}
|
||||
}};
|
||||
(@gen_parser ($keyword:expr, $key:expr, $bucket:expr, $query:expr, $header:expr),
|
||||
key: [$($kw_k:ident $(if $required_k:ident)? $(header $header_k:expr)? => $api_k:ident $(($($conv_k:ident :: $what_k:ident),*))?,)*],
|
||||
no_key: [$($kw_nk:ident $(if $required_nk:ident)? $(if_header $header_nk:expr)? => $api_nk:ident $(($($conv_nk:ident :: $what_nk:ident),*))?,)*]) => {{
|
||||
use Endpoint::*;
|
||||
use keywords::*;
|
||||
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,
|
||||
$($(
|
||||
$what_k: s3_match!(@@parse_param $query, $conv_k, $what_k),
|
||||
)*)?
|
||||
}),
|
||||
)*
|
||||
$(
|
||||
($kw_nk, false) $(if $query.$required_nk.is_some())? $(if $header.contains($header_nk))? => Ok($api_nk {
|
||||
bucket: $bucket,
|
||||
$($(
|
||||
$what_nk: s3_match!(@@parse_param $query, $conv_nk, $what_nk),
|
||||
)*)?
|
||||
}),
|
||||
)*
|
||||
_ => Err(Error::BadRequest("Invalid endpoint".to_string())),
|
||||
}
|
||||
}};
|
||||
|
||||
(@@parse_param $query:expr, query_opt, $param:ident) => {{
|
||||
$query.$param.take().map(|param| param.into_owned())
|
||||
}};
|
||||
(@@parse_param $query:expr, query, $param:ident) => {{
|
||||
$query.$param.take().ok_or_bad_request("Invalid endpoint")?.into_owned()
|
||||
}};
|
||||
(@@parse_param $query:expr, opt_parse, $param:ident) => {{
|
||||
$query.$param
|
||||
.take()
|
||||
.map(|param| param.parse())
|
||||
.transpose()
|
||||
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_string()))?
|
||||
}};
|
||||
(@@parse_param $query:expr, parse, $param:ident) => {{
|
||||
$query.$param.take().ok_or_bad_request("Invalid endpoint")?
|
||||
.parse()
|
||||
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_string()))?
|
||||
}};
|
||||
}
|
||||
|
||||
/// List of all S3 API endpoints.
|
||||
#[derive(Debug, Clone)]
|
||||
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<String>,
|
||||
},
|
||||
DeleteObjects {
|
||||
bucket: String,
|
||||
},
|
||||
DeleteObjectTagging {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
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<u16>,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
GetObjectAcl {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
GetObjectLegalHold {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
GetObjectLockConfiguration {
|
||||
bucket: String,
|
||||
},
|
||||
GetObjectRetention {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
GetObjectTagging {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
GetObjectTorrent {
|
||||
bucket: String,
|
||||
key: String,
|
||||
},
|
||||
GetPublicAccessBlock {
|
||||
bucket: String,
|
||||
},
|
||||
HeadBucket {
|
||||
bucket: String,
|
||||
},
|
||||
HeadObject {
|
||||
bucket: String,
|
||||
key: String,
|
||||
part_number: Option<u16>,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
ListBucketAnalyticsConfigurations {
|
||||
bucket: String,
|
||||
continuation_token: Option<String>,
|
||||
},
|
||||
ListBucketIntelligentTieringConfigurations {
|
||||
bucket: String,
|
||||
continuation_token: Option<String>,
|
||||
},
|
||||
ListBucketInventoryConfigurations {
|
||||
bucket: String,
|
||||
continuation_token: Option<String>,
|
||||
},
|
||||
ListBucketMetricsConfigurations {
|
||||
bucket: String,
|
||||
continuation_token: Option<String>,
|
||||
},
|
||||
ListBuckets,
|
||||
ListMultipartUploads {
|
||||
bucket: String,
|
||||
delimiter: Option<char>,
|
||||
encoding_type: Option<String>,
|
||||
key_marker: Option<String>,
|
||||
max_uploads: Option<u16>,
|
||||
prefix: Option<String>,
|
||||
upload_id_marker: Option<String>,
|
||||
},
|
||||
ListObjects {
|
||||
bucket: String,
|
||||
delimiter: Option<char>,
|
||||
encoding_type: Option<String>,
|
||||
marker: Option<String>,
|
||||
max_keys: Option<u16>,
|
||||
prefix: Option<String>,
|
||||
},
|
||||
ListObjectsV2 {
|
||||
bucket: String,
|
||||
list_type: String, // must be 2
|
||||
continuation_token: Option<String>,
|
||||
delimiter: Option<char>,
|
||||
encoding_type: Option<String>,
|
||||
fetch_owner: Option<bool>,
|
||||
max_keys: Option<u16>,
|
||||
prefix: Option<String>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
ListObjectVersions {
|
||||
bucket: String,
|
||||
delimiter: Option<char>,
|
||||
encoding_type: Option<String>,
|
||||
key_marker: Option<String>,
|
||||
max_keys: Option<u16>,
|
||||
prefix: Option<String>,
|
||||
version_id_marker: Option<String>,
|
||||
},
|
||||
ListParts {
|
||||
bucket: String,
|
||||
key: String,
|
||||
max_parts: Option<u16>,
|
||||
part_number_marker: Option<u16>,
|
||||
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<String>,
|
||||
},
|
||||
PutObjectLegalHold {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
PutObjectLockConfiguration {
|
||||
bucket: String,
|
||||
},
|
||||
PutObjectRetention {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
PutObjectTagging {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
PutPublicAccessBlock {
|
||||
bucket: String,
|
||||
},
|
||||
RestoreObject {
|
||||
bucket: String,
|
||||
key: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
SelectObjectContent {
|
||||
bucket: String,
|
||||
key: String,
|
||||
select_type: String, // should always be 2
|
||||
},
|
||||
UploadPart {
|
||||
bucket: String,
|
||||
key: String,
|
||||
part_number: u16,
|
||||
upload_id: String,
|
||||
},
|
||||
UploadPartCopy {
|
||||
bucket: String,
|
||||
key: String,
|
||||
part_number: u16,
|
||||
upload_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub fn from_request(
|
||||
bucket: Option<String>,
|
||||
uri: &Uri,
|
||||
method: &Method,
|
||||
headers: &HeaderMap<HeaderValue>,
|
||||
) -> Result<Self, Error> {
|
||||
let path = uri.path().trim_start_matches('/');
|
||||
let query = uri.query();
|
||||
if bucket.is_none() && path.is_empty() {
|
||||
if query.is_none() {
|
||||
return Ok(Self::ListBuckets);
|
||||
} else {
|
||||
return Err(Error::BadRequest("Invalid endpoint".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let (bucket, key) = if let Some(bucket) = bucket {
|
||||
(bucket, path.to_string())
|
||||
} else {
|
||||
path.split_once('/')
|
||||
.map(|(b, p)| (b.to_string(), p.trim_start_matches('/').to_string()))
|
||||
.unwrap_or((path.to_string(), String::new()))
|
||||
};
|
||||
|
||||
let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
|
||||
|
||||
let res = match 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, headers)?,
|
||||
&Method::DELETE => Self::from_delete(bucket, key, &mut query)?,
|
||||
_ => return Err(Error::BadRequest("Invalid endpoint".to_string())),
|
||||
};
|
||||
|
||||
if let Some(message) = query.nonempty_message() {
|
||||
// maybe this should just be a warn! ?
|
||||
Err(Error::BadRequest(message.to_string()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_get(
|
||||
bucket: String,
|
||||
key: String,
|
||||
query: &mut QueryParameters<'_>,
|
||||
) -> Result<Self, Error> {
|
||||
s3_match! {
|
||||
@gen_parser
|
||||
(query.keyword.take().unwrap_or_default().as_ref(), key, bucket, 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),
|
||||
ACL => GetObjectAcl (query_opt::version_id),
|
||||
LEGAL_HOLD => GetObjectLegalHold (query_opt::version_id),
|
||||
RETENTION => GetObjectRetention (query_opt::version_id),
|
||||
TAGGING => GetObjectTagging (query_opt::version_id),
|
||||
TORRENT => GetObjectTorrent,
|
||||
],
|
||||
no_key: [
|
||||
EMPTY if list_type => ListObjectsV2 (query::list_type, query_opt::continuation_token,
|
||||
opt_parse::delimiter, query_opt::encoding_type,
|
||||
opt_parse::fetch_owner, opt_parse::max_keys,
|
||||
query_opt::prefix, query_opt::start_after),
|
||||
EMPTY => ListObjects (opt_parse::delimiter, query_opt::encoding_type, query_opt::marker,
|
||||
opt_parse::max_keys, opt_parse::prefix),
|
||||
ACCELERATE => GetBucketAccelerateConfiguration,
|
||||
ACL => GetBucketAcl,
|
||||
ANALYTICS if id => GetBucketAnalyticsConfiguration (query::id),
|
||||
ANALYTICS => ListBucketAnalyticsConfigurations (query_opt::continuation_token),
|
||||
CORS => GetBucketCors,
|
||||
ENCRYPTION => GetBucketEncryption,
|
||||
INTELLIGENT_TIERING if id => GetBucketIntelligentTieringConfiguration (query::id),
|
||||
INTELLIGENT_TIERING => ListBucketIntelligentTieringConfigurations (query_opt::continuation_token),
|
||||
INVENTORY if id => GetBucketInventoryConfiguration (query::id),
|
||||
INVENTORY => ListBucketInventoryConfigurations (query_opt::continuation_token),
|
||||
LIFECYCLE => GetBucketLifecycleConfiguration,
|
||||
LOCATION => GetBucketLocation,
|
||||
LOGGING => GetBucketLogging,
|
||||
METRICS if id => GetBucketMetricsConfiguration (query::id),
|
||||
METRICS => ListBucketMetricsConfigurations (query_opt::continuation_token),
|
||||
NOTIFICATION => GetBucketNotificationConfiguration,
|
||||
OBJECT_LOCK => GetObjectLockConfiguration,
|
||||
OWNERSHIP_CONTROLS => GetBucketOwnershipControls,
|
||||
POLICY => GetBucketPolicy,
|
||||
POLICY_STATUS => GetBucketPolicyStatus,
|
||||
PUBLIC_ACCESS_BLOCK => GetPublicAccessBlock,
|
||||
REPLICATION => GetBucketReplication,
|
||||
REQUEST_PAYMENT => GetBucketRequestPayment,
|
||||
TAGGING => GetBucketTagging,
|
||||
UPLOADS => ListMultipartUploads (opt_parse::delimiter, query_opt::encoding_type,
|
||||
query_opt::key_marker, opt_parse::max_uploads,
|
||||
query_opt::prefix, query_opt::upload_id_marker),
|
||||
VERSIONING => GetBucketVersioning,
|
||||
VERSIONS => ListObjectVersions (opt_parse::delimiter, query_opt::encoding_type,
|
||||
query_opt::key_marker, opt_parse::max_keys,
|
||||
query_opt::prefix, query_opt::version_id_marker),
|
||||
WEBSITE => GetBucketWebsite,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn from_head(
|
||||
bucket: String,
|
||||
key: String,
|
||||
query: &mut QueryParameters<'_>,
|
||||
) -> Result<Self, Error> {
|
||||
s3_match! {
|
||||
@gen_parser
|
||||
(query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None),
|
||||
key: [
|
||||
EMPTY => HeadObject(opt_parse::part_number, query_opt::version_id),
|
||||
],
|
||||
no_key: [
|
||||
EMPTY => HeadBucket,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn from_post(
|
||||
bucket: String,
|
||||
key: String,
|
||||
query: &mut QueryParameters<'_>,
|
||||
) -> Result<Self, Error> {
|
||||
s3_match! {
|
||||
@gen_parser
|
||||
(query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None),
|
||||
key: [
|
||||
EMPTY if upload_id => CompleteMultipartUpload (query::upload_id),
|
||||
RESTORE => RestoreObject (query_opt::version_id),
|
||||
SELECT => SelectObjectContent (query::select_type),
|
||||
UPLOADS => CreateMultipartUpload,
|
||||
],
|
||||
no_key: [
|
||||
DELETE => DeleteObjects,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn from_put(
|
||||
bucket: String,
|
||||
key: String,
|
||||
query: &mut QueryParameters<'_>,
|
||||
headers: &HeaderMap<HeaderValue>,
|
||||
) -> Result<Self, Error> {
|
||||
s3_match! {
|
||||
@gen_parser
|
||||
(query.keyword.take().unwrap_or_default().as_ref(), key, bucket, 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,
|
||||
EMPTY if part_number => UploadPart (parse::part_number, query::upload_id),
|
||||
EMPTY => PutObject,
|
||||
ACL => PutObjectAcl (query_opt::version_id),
|
||||
LEGAL_HOLD => PutObjectLegalHold (query_opt::version_id),
|
||||
RETENTION => PutObjectRetention (query_opt::version_id),
|
||||
TAGGING => PutObjectTagging (query_opt::version_id),
|
||||
|
||||
],
|
||||
no_key: [
|
||||
EMPTY => CreateBucket,
|
||||
ACCELERATE => PutBucketAccelerateConfiguration,
|
||||
ACL => PutBucketAcl,
|
||||
ANALYTICS => PutBucketAnalyticsConfiguration (query::id),
|
||||
CORS => PutBucketCors,
|
||||
ENCRYPTION => PutBucketEncryption,
|
||||
INTELLIGENT_TIERING => PutBucketIntelligentTieringConfiguration(query::id),
|
||||
INVENTORY => PutBucketInventoryConfiguration(query::id),
|
||||
LIFECYCLE => PutBucketLifecycleConfiguration,
|
||||
LOGGING => PutBucketLogging,
|
||||
METRICS => PutBucketMetricsConfiguration(query::id),
|
||||
NOTIFICATION => PutBucketNotificationConfiguration,
|
||||
OBJECT_LOCK => PutObjectLockConfiguration,
|
||||
OWNERSHIP_CONTROLS => PutBucketOwnershipControls,
|
||||
POLICY => PutBucketPolicy,
|
||||
PUBLIC_ACCESS_BLOCK => PutPublicAccessBlock,
|
||||
REPLICATION => PutBucketReplication,
|
||||
REQUEST_PAYMENT => PutBucketRequestPayment,
|
||||
TAGGING => PutBucketTagging,
|
||||
VERSIONING => PutBucketVersioning,
|
||||
WEBSITE => PutBucketWebsite,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn from_delete(
|
||||
bucket: String,
|
||||
key: String,
|
||||
query: &mut QueryParameters<'_>,
|
||||
) -> Result<Self, Error> {
|
||||
s3_match! {
|
||||
@gen_parser
|
||||
(query.keyword.take().unwrap_or_default().as_ref(), key, bucket, query, None),
|
||||
key: [
|
||||
EMPTY if upload_id => AbortMultipartUpload (query::upload_id),
|
||||
EMPTY => DeleteObject (query_opt::version_id),
|
||||
TAGGING => DeleteObjectTagging (query_opt::version_id),
|
||||
],
|
||||
no_key: [
|
||||
EMPTY => DeleteBucket,
|
||||
ANALYTICS => DeleteBucketAnalyticsConfiguration (query::id),
|
||||
CORS => DeleteBucketCors,
|
||||
ENCRYPTION => DeleteBucketEncryption,
|
||||
INTELLIGENT_TIERING => DeleteBucketIntelligentTieringConfiguration (query::id),
|
||||
INVENTORY => DeleteBucketInventoryConfiguration (query::id),
|
||||
LIFECYCLE => DeleteBucketLifecycle,
|
||||
METRICS => DeleteBucketMetricsConfiguration (query::id),
|
||||
OWNERSHIP_CONTROLS => DeleteBucketOwnershipControls,
|
||||
POLICY => DeleteBucketPolicy,
|
||||
PUBLIC_ACCESS_BLOCK => DeletePublicAccessBlock,
|
||||
REPLICATION => DeleteBucketReplication,
|
||||
TAGGING => DeleteBucketTagging,
|
||||
WEBSITE => DeleteBucketWebsite,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> Option<&str> {
|
||||
s3_match! {
|
||||
@extract
|
||||
self,
|
||||
key,
|
||||
[
|
||||
AbortMultipartUpload,
|
||||
CompleteMultipartUpload,
|
||||
CopyObject,
|
||||
CreateMultipartUpload,
|
||||
DeleteObject,
|
||||
DeleteObjectTagging,
|
||||
GetObject,
|
||||
GetObjectAcl,
|
||||
GetObjectLegalHold,
|
||||
GetObjectRetention,
|
||||
GetObjectTagging,
|
||||
GetObjectTorrent,
|
||||
HeadObject,
|
||||
ListParts,
|
||||
PutObject,
|
||||
PutObjectAcl,
|
||||
PutObjectLegalHold,
|
||||
PutObjectRetention,
|
||||
PutObjectTagging,
|
||||
RestoreObject,
|
||||
SelectObjectContent,
|
||||
UploadPart,
|
||||
UploadPartCopy,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! generateQueryParameters {
|
||||
( $($rest:expr => $name:ident),* ) => {
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, Default)]
|
||||
struct QueryParameters<'a> {
|
||||
keyword: Option<Cow<'a, str>>,
|
||||
$(
|
||||
$name: Option<Cow<'a, str>>,
|
||||
)*
|
||||
}
|
||||
|
||||
impl<'a> QueryParameters<'a> {
|
||||
fn from_query(query: &'a str) -> Result<Self, Error> {
|
||||
let mut res: Self = Default::default();
|
||||
for (k, v) in url::form_urlencoded::parse(query.as_bytes()) {
|
||||
if v.as_ref().is_empty() {
|
||||
if res.keyword.replace(k).is_some() {
|
||||
return Err(Error::BadRequest("Multiple keywords".to_string()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let repeated = match k.as_ref() {
|
||||
$(
|
||||
$rest => res.$name.replace(v).is_none(),
|
||||
)*
|
||||
_ => {
|
||||
if k.starts_with("response-") {
|
||||
true
|
||||
} else {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"Unknown query parameter '{}'",
|
||||
k
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
if repeated {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"Query parameter repeated: '{}'",
|
||||
k
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn nonempty_message(&self) -> Option<&str> {
|
||||
if self.keyword.is_some() {
|
||||
Some("Keyword not used")
|
||||
} $(
|
||||
else if self.$name.is_some() {
|
||||
Some(concat!("Query parameter not needed: '", $rest, "'" ))
|
||||
}
|
||||
)* else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generateQueryParameters! {
|
||||
"continuation-token" => continuation_token,
|
||||
"delimiter" => delimiter,
|
||||
"encoding-type" => encoding_type,
|
||||
"fetch-owner" => fetch_owner,
|
||||
"id" => id,
|
||||
"key-marker" => key_marker,
|
||||
"list-type" => list_type,
|
||||
"marker" => marker,
|
||||
"max-keys" => max_keys,
|
||||
"max-parts" => max_parts,
|
||||
"max-uploads" => max_uploads,
|
||||
"partNumber" => part_number,
|
||||
"part-number-marker" => part_number_marker,
|
||||
"prefix" => prefix,
|
||||
"select-type" => select_type,
|
||||
"start-after" => start_after,
|
||||
"uploadId" => upload_id,
|
||||
"upload-id-marker" => upload_id_marker,
|
||||
"versionId" => version_id,
|
||||
"version-id-marker" => version_id_marker
|
||||
}
|
||||
|
||||
mod keywords {
|
||||
pub const EMPTY: &str = "";
|
||||
|
||||
pub const ACCELERATE: &str = "accelerate";
|
||||
pub const ACL: &str = "acl";
|
||||
pub const ANALYTICS: &str = "analytics";
|
||||
pub const CORS: &str = "cors";
|
||||
pub const DELETE: &str = "delete";
|
||||
pub const ENCRYPTION: &str = "encryption";
|
||||
pub const INTELLIGENT_TIERING: &str = "intelligent-tiering";
|
||||
pub const INVENTORY: &str = "inventory";
|
||||
pub const LEGAL_HOLD: &str = "legal-hold";
|
||||
pub const LIFECYCLE: &str = "lifecycle";
|
||||
pub const LOCATION: &str = "location";
|
||||
pub const LOGGING: &str = "logging";
|
||||
pub const METRICS: &str = "metrics";
|
||||
pub const NOTIFICATION: &str = "notification";
|
||||
pub const OBJECT_LOCK: &str = "object-lock";
|
||||
pub const OWNERSHIP_CONTROLS: &str = "ownershipControls";
|
||||
pub const POLICY: &str = "policy";
|
||||
pub const POLICY_STATUS: &str = "policyStatus";
|
||||
pub const PUBLIC_ACCESS_BLOCK: &str = "publicAccessBlock";
|
||||
pub const REPLICATION: &str = "replication";
|
||||
pub const REQUEST_PAYMENT: &str = "requestPayment";
|
||||
pub const RESTORE: &str = "restore";
|
||||
pub const RETENTION: &str = "retention";
|
||||
pub const SELECT: &str = "select";
|
||||
pub const TAGGING: &str = "tagging";
|
||||
pub const TORRENT: &str = "torrent";
|
||||
pub const UPLOADS: &str = "uploads";
|
||||
pub const VERSIONING: &str = "versioning";
|
||||
pub const VERSIONS: &str = "versions";
|
||||
pub const WEBSITE: &str = "website";
|
||||
}
|
Loading…
Reference in a new issue