K2V #293
7 changed files with 223 additions and 214 deletions
|
@ -7,6 +7,19 @@ use garage_model::key_table::Key;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
||||||
|
/// What kind of authorization is required to perform a given action
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Authorization {
|
||||||
|
/// No authorization is required
|
||||||
|
None,
|
||||||
|
/// Having Read permission on bucket
|
||||||
|
Read,
|
||||||
|
/// Having Write permission on bucket
|
||||||
|
Write,
|
||||||
|
/// Having Owner permission on bucket
|
||||||
|
Owner,
|
||||||
|
}
|
||||||
|
|
||||||
/// Host to bucket
|
/// Host to bucket
|
||||||
///
|
///
|
||||||
/// Convert a host, like "bucket.garage-site.tld" to the corresponding bucket "bucket",
|
/// Convert a host, like "bucket.garage-site.tld" to the corresponding bucket "bucket",
|
||||||
|
|
1
src/api/k2v/api_server.rs
Normal file
1
src/api/k2v/api_server.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use crate::generic_server::*;
|
|
@ -1,2 +1 @@
|
||||||
mod api_server;
|
pub mod api_server;
|
||||||
pub use api_server::run_api_server;
|
|
||||||
|
|
|
@ -4,12 +4,13 @@ extern crate tracing;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub mod helpers;
|
|
||||||
|
|
||||||
mod encoding;
|
mod encoding;
|
||||||
mod generic_server;
|
mod generic_server;
|
||||||
|
pub mod helpers;
|
||||||
|
mod router_macros;
|
||||||
/// This mode is public only to help testing. Don't expect stability here
|
/// This mode is public only to help testing. Don't expect stability here
|
||||||
pub mod signature;
|
pub mod signature;
|
||||||
|
|
||||||
|
pub mod k2v;
|
||||||
pub mod s3;
|
pub mod s3;
|
||||||
|
|
193
src/api/router_macros.rs
Normal file
193
src/api/router_macros.rs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
|
||||||
|
|
||||||
|
/// This macro is used to generate very repetitive match {} blocks in this module
|
||||||
|
/// It is _not_ made to be used anywhere else
|
||||||
|
macro_rules! router_match {
|
||||||
|
(@match $enum:expr , [ $($endpoint:ident,)* ]) => {{
|
||||||
|
// usage: router_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: router_match {@extract my_enum, field_name, [ VariantWithField1, VariantWithField2 ..] }
|
||||||
|
// returns Some(field_value), or None if the variant was not one of the listed variants.
|
||||||
|
use Endpoint::*;
|
||||||
|
match $enum {
|
||||||
|
$(
|
||||||
|
$endpoint {$param, ..} => Some($param),
|
||||||
|
)*
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
(@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: router_match {@gen_parser (keyword, key, query, header),
|
||||||
|
// key: [
|
||||||
|
// SOME_KEYWORD => VariantWithKey,
|
||||||
|
// ...
|
||||||
|
// ],
|
||||||
|
// no_key: [
|
||||||
|
// SOME_KEYWORD => VariantWithoutKey,
|
||||||
|
// ...
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// See in from_{method} for more detailed usage.
|
||||||
|
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 {
|
||||||
|
key: $key,
|
||||||
|
$($(
|
||||||
|
$param_k: router_match!(@@parse_param $query, $conv_k, $param_k),
|
||||||
|
)*)?
|
||||||
|
}),
|
||||||
|
)*
|
||||||
|
$(
|
||||||
|
($kw_nk, false) $(if $query.$required_nk.is_some())? $(if $header.contains($header_nk))? => Ok($api_nk {
|
||||||
|
$($(
|
||||||
|
$param_nk: router_match!(@@parse_param $query, $conv_nk, $param_nk),
|
||||||
|
)*)?
|
||||||
|
}),
|
||||||
|
)*
|
||||||
|
(kw, _) => Err(Error::BadRequest(format!("Invalid endpoint: {}", kw)))
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
(@@parse_param $query:expr, query_opt, $param:ident) => {{
|
||||||
|
// extract optional query parameter
|
||||||
|
$query.$param.take().map(|param| param.into_owned())
|
||||||
|
}};
|
||||||
|
(@@parse_param $query:expr, query, $param:ident) => {{
|
||||||
|
// extract mendatory query parameter
|
||||||
|
$query.$param.take().ok_or_bad_request("Missing argument for endpoint")?.into_owned()
|
||||||
|
}};
|
||||||
|
(@@parse_param $query:expr, opt_parse, $param:ident) => {{
|
||||||
|
// extract and parse optional query parameter
|
||||||
|
// missing parameter is file, however parse error is reported as an error
|
||||||
|
$query.$param
|
||||||
|
.take()
|
||||||
|
.map(|param| param.parse())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))?
|
||||||
|
}};
|
||||||
|
(@@parse_param $query:expr, parse, $param:ident) => {{
|
||||||
|
// extract and parse mandatory query parameter
|
||||||
|
// both missing and un-parseable parameters are reported as errors
|
||||||
|
$query.$param.take().ok_or_bad_request("Missing argument for endpoint")?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))?
|
||||||
|
}};
|
||||||
|
(@func
|
||||||
|
$(#[$doc:meta])*
|
||||||
|
pub enum Endpoint {
|
||||||
|
$(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
$variant:ident $({
|
||||||
|
$($name:ident: $ty:ty,)*
|
||||||
|
})?,
|
||||||
|
)*
|
||||||
|
}) => {
|
||||||
|
$(#[$doc])*
|
||||||
|
pub enum Endpoint {
|
||||||
|
$(
|
||||||
|
$(#[$outer])*
|
||||||
|
$variant $({
|
||||||
|
$($name: $ty, )*
|
||||||
|
})?,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
impl Endpoint {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(Endpoint::$variant $({ $($name: _,)* .. })? => stringify!($variant),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(@if ($($cond:tt)+) then ($($then:tt)*) else ($($else:tt)*)) => {
|
||||||
|
$($then)*
|
||||||
|
};
|
||||||
|
(@if () then ($($then:tt)*) else ($($else:tt)*)) => {
|
||||||
|
$($else)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// This macro is used to generate part of the code in this module. It must be called only one, and
|
||||||
|
/// is useless outside of this module.
|
||||||
|
macro_rules! generateQueryParameters {
|
||||||
|
( $($rest:expr => $name:ident),* ) => {
|
||||||
|
/// Struct containing all query parameters used in endpoints. Think of it as an HashMap,
|
||||||
|
/// but with keys statically known.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct QueryParameters<'a> {
|
||||||
|
keyword: Option<Cow<'a, str>>,
|
||||||
|
$(
|
||||||
|
$name: Option<Cow<'a, str>>,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> QueryParameters<'a> {
|
||||||
|
/// Build this struct from the query part of an URI.
|
||||||
|
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()) {
|
||||||
|
let repeated = match k.as_ref() {
|
||||||
|
$(
|
||||||
|
$rest => if !v.is_empty() {
|
||||||
|
res.$name.replace(v).is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
_ => {
|
||||||
|
if k.starts_with("response-") || k.starts_with("X-Amz-") {
|
||||||
|
false
|
||||||
|
} else if v.as_ref().is_empty() {
|
||||||
|
if res.keyword.replace(k).is_some() {
|
||||||
|
return Err(Error::BadRequest("Multiple keywords".to_owned()));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
debug!("Received an unknown query parameter: '{}'", k);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if repeated {
|
||||||
|
return Err(Error::BadRequest(format!(
|
||||||
|
"Query parameter repeated: '{}'",
|
||||||
|
k
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an error message in case not all parameters where used when extracting them to
|
||||||
|
/// build an Enpoint variant
|
||||||
|
fn nonempty_message(&self) -> Option<&str> {
|
||||||
|
if self.keyword.is_some() {
|
||||||
|
Some("Keyword not used")
|
||||||
|
} $(
|
||||||
|
else if self.$name.is_some() {
|
||||||
|
Some(concat!("'", $rest, "'"))
|
||||||
|
}
|
||||||
|
)* else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use router_match;
|
||||||
|
pub(crate) use generateQueryParameters;
|
|
@ -31,7 +31,7 @@ use crate::s3::get::*;
|
||||||
use crate::s3::list::*;
|
use crate::s3::list::*;
|
||||||
use crate::s3::post_object::handle_post_object;
|
use crate::s3::post_object::handle_post_object;
|
||||||
use crate::s3::put::*;
|
use crate::s3::put::*;
|
||||||
use crate::s3::router::{Authorization, Endpoint};
|
use crate::s3::router::{Endpoint};
|
||||||
use crate::s3::website::*;
|
use crate::s3::website::*;
|
||||||
|
|
||||||
pub struct S3ApiServer {
|
pub struct S3ApiServer {
|
||||||
|
|
|
@ -5,127 +5,10 @@ use std::borrow::Cow;
|
||||||
use hyper::header::HeaderValue;
|
use hyper::header::HeaderValue;
|
||||||
use hyper::{HeaderMap, Method, Request};
|
use hyper::{HeaderMap, Method, Request};
|
||||||
|
|
||||||
/// This macro is used to generate very repetitive match {} blocks in this module
|
use crate::router_macros::{router_match, generateQueryParameters};
|
||||||
/// It is _not_ made to be used anywhere else
|
use crate::helpers::Authorization;
|
||||||
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.
|
|
||||||
use Endpoint::*;
|
|
||||||
match $enum {
|
|
||||||
$(
|
|
||||||
$endpoint {$param, ..} => Some($param),
|
|
||||||
)*
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
(@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, query, header),
|
|
||||||
// key: [
|
|
||||||
// SOME_KEYWORD => VariantWithKey,
|
|
||||||
// ...
|
|
||||||
// ],
|
|
||||||
// no_key: [
|
|
||||||
// SOME_KEYWORD => VariantWithoutKey,
|
|
||||||
// ...
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// See in from_{method} for more detailed usage.
|
|
||||||
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 {
|
|
||||||
key: $key,
|
|
||||||
$($(
|
|
||||||
$param_k: s3_match!(@@parse_param $query, $conv_k, $param_k),
|
|
||||||
)*)?
|
|
||||||
}),
|
|
||||||
)*
|
|
||||||
$(
|
|
||||||
($kw_nk, false) $(if $query.$required_nk.is_some())? $(if $header.contains($header_nk))? => Ok($api_nk {
|
|
||||||
$($(
|
|
||||||
$param_nk: s3_match!(@@parse_param $query, $conv_nk, $param_nk),
|
|
||||||
)*)?
|
|
||||||
}),
|
|
||||||
)*
|
|
||||||
(kw, _) => Err(Error::BadRequest(format!("Invalid endpoint: {}", kw)))
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
(@@parse_param $query:expr, query_opt, $param:ident) => {{
|
router_match! {@func
|
||||||
// extract optional query parameter
|
|
||||||
$query.$param.take().map(|param| param.into_owned())
|
|
||||||
}};
|
|
||||||
(@@parse_param $query:expr, query, $param:ident) => {{
|
|
||||||
// extract mendatory query parameter
|
|
||||||
$query.$param.take().ok_or_bad_request("Missing argument for endpoint")?.into_owned()
|
|
||||||
}};
|
|
||||||
(@@parse_param $query:expr, opt_parse, $param:ident) => {{
|
|
||||||
// extract and parse optional query parameter
|
|
||||||
// missing parameter is file, however parse error is reported as an error
|
|
||||||
$query.$param
|
|
||||||
.take()
|
|
||||||
.map(|param| param.parse())
|
|
||||||
.transpose()
|
|
||||||
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))?
|
|
||||||
}};
|
|
||||||
(@@parse_param $query:expr, parse, $param:ident) => {{
|
|
||||||
// extract and parse mandatory query parameter
|
|
||||||
// both missing and un-parseable parameters are reported as errors
|
|
||||||
$query.$param.take().ok_or_bad_request("Missing argument for endpoint")?
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))?
|
|
||||||
}};
|
|
||||||
(@func
|
|
||||||
$(#[$doc:meta])*
|
|
||||||
pub enum Endpoint {
|
|
||||||
$(
|
|
||||||
$(#[$outer:meta])*
|
|
||||||
$variant:ident $({
|
|
||||||
$($name:ident: $ty:ty,)*
|
|
||||||
})?,
|
|
||||||
)*
|
|
||||||
}) => {
|
|
||||||
$(#[$doc])*
|
|
||||||
pub enum Endpoint {
|
|
||||||
$(
|
|
||||||
$(#[$outer])*
|
|
||||||
$variant $({
|
|
||||||
$($name: $ty, )*
|
|
||||||
})?,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
impl Endpoint {
|
|
||||||
pub fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
$(Endpoint::$variant $({ $($name: _,)* .. })? => stringify!($variant),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(@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.
|
/// List of all S3 API endpoints.
|
||||||
///
|
///
|
||||||
|
@ -471,7 +354,7 @@ impl Endpoint {
|
||||||
|
|
||||||
/// Determine which endpoint a request is for, knowing it is a GET.
|
/// Determine which endpoint a request is for, knowing it is a GET.
|
||||||
fn from_get(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
fn from_get(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
||||||
s3_match! {
|
router_match! {
|
||||||
@gen_parser
|
@gen_parser
|
||||||
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
||||||
key: [
|
key: [
|
||||||
|
@ -528,7 +411,7 @@ impl Endpoint {
|
||||||
|
|
||||||
/// Determine which endpoint a request is for, knowing it is a HEAD.
|
/// Determine which endpoint a request is for, knowing it is a HEAD.
|
||||||
fn from_head(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
fn from_head(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
||||||
s3_match! {
|
router_match! {
|
||||||
@gen_parser
|
@gen_parser
|
||||||
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
||||||
key: [
|
key: [
|
||||||
|
@ -542,7 +425,7 @@ impl Endpoint {
|
||||||
|
|
||||||
/// Determine which endpoint a request is for, knowing it is a POST.
|
/// Determine which endpoint a request is for, knowing it is a POST.
|
||||||
fn from_post(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
fn from_post(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
||||||
s3_match! {
|
router_match! {
|
||||||
@gen_parser
|
@gen_parser
|
||||||
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
||||||
key: [
|
key: [
|
||||||
|
@ -564,7 +447,7 @@ impl Endpoint {
|
||||||
query: &mut QueryParameters<'_>,
|
query: &mut QueryParameters<'_>,
|
||||||
headers: &HeaderMap<HeaderValue>,
|
headers: &HeaderMap<HeaderValue>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
s3_match! {
|
router_match! {
|
||||||
@gen_parser
|
@gen_parser
|
||||||
(query.keyword.take().unwrap_or_default().as_ref(), key, query, headers),
|
(query.keyword.take().unwrap_or_default().as_ref(), key, query, headers),
|
||||||
key: [
|
key: [
|
||||||
|
@ -606,7 +489,7 @@ impl Endpoint {
|
||||||
|
|
||||||
/// Determine which endpoint a request is for, knowing it is a DELETE.
|
/// Determine which endpoint a request is for, knowing it is a DELETE.
|
||||||
fn from_delete(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
fn from_delete(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> {
|
||||||
s3_match! {
|
router_match! {
|
||||||
@gen_parser
|
@gen_parser
|
||||||
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
(query.keyword.take().unwrap_or_default().as_ref(), key, query, None),
|
||||||
key: [
|
key: [
|
||||||
|
@ -636,7 +519,7 @@ impl Endpoint {
|
||||||
/// Get the key the request target. Returns None for requests which don't use a key.
|
/// Get the key the request target. Returns None for requests which don't use a key.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_key(&self) -> Option<&str> {
|
pub fn get_key(&self) -> Option<&str> {
|
||||||
s3_match! {
|
router_match! {
|
||||||
@extract
|
@extract
|
||||||
self,
|
self,
|
||||||
key,
|
key,
|
||||||
|
@ -673,7 +556,7 @@ impl Endpoint {
|
||||||
if let Endpoint::ListBuckets = self {
|
if let Endpoint::ListBuckets = self {
|
||||||
return Authorization::None;
|
return Authorization::None;
|
||||||
};
|
};
|
||||||
let readonly = s3_match! {
|
let readonly = router_match! {
|
||||||
@match
|
@match
|
||||||
self,
|
self,
|
||||||
[
|
[
|
||||||
|
@ -717,7 +600,7 @@ impl Endpoint {
|
||||||
SelectObjectContent,
|
SelectObjectContent,
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
let owner = s3_match! {
|
let owner = router_match! {
|
||||||
@match
|
@match
|
||||||
self,
|
self,
|
||||||
[
|
[
|
||||||
|
@ -740,87 +623,6 @@ impl Endpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What kind of authorization is required to perform a given action
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Authorization {
|
|
||||||
/// No authorization is required
|
|
||||||
None,
|
|
||||||
/// 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
|
|
||||||
/// is useless outside of this module.
|
|
||||||
macro_rules! generateQueryParameters {
|
|
||||||
( $($rest:expr => $name:ident),* ) => {
|
|
||||||
/// Struct containing all query parameters used in endpoints. Think of it as an HashMap,
|
|
||||||
/// but with keys statically known.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct QueryParameters<'a> {
|
|
||||||
keyword: Option<Cow<'a, str>>,
|
|
||||||
$(
|
|
||||||
$name: Option<Cow<'a, str>>,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> QueryParameters<'a> {
|
|
||||||
/// Build this struct from the query part of an URI.
|
|
||||||
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()) {
|
|
||||||
let repeated = match k.as_ref() {
|
|
||||||
$(
|
|
||||||
$rest => if !v.is_empty() {
|
|
||||||
res.$name.replace(v).is_some()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
},
|
|
||||||
)*
|
|
||||||
_ => {
|
|
||||||
if k.starts_with("response-") || k.starts_with("X-Amz-") {
|
|
||||||
false
|
|
||||||
} else if v.as_ref().is_empty() {
|
|
||||||
if res.keyword.replace(k).is_some() {
|
|
||||||
return Err(Error::BadRequest("Multiple keywords".to_owned()));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
debug!("Received an unknown query parameter: '{}'", k);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if repeated {
|
|
||||||
return Err(Error::BadRequest(format!(
|
|
||||||
"Query parameter repeated: '{}'",
|
|
||||||
k
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an error message in case not all parameters where used when extracting them to
|
|
||||||
/// build an Enpoint variant
|
|
||||||
fn nonempty_message(&self) -> Option<&str> {
|
|
||||||
if self.keyword.is_some() {
|
|
||||||
Some("Keyword not used")
|
|
||||||
} $(
|
|
||||||
else if self.$name.is_some() {
|
|
||||||
Some(concat!("'", $rest, "'"))
|
|
||||||
}
|
|
||||||
)* else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parameter name => struct field
|
// parameter name => struct field
|
||||||
generateQueryParameters! {
|
generateQueryParameters! {
|
||||||
"continuation-token" => continuation_token,
|
"continuation-token" => continuation_token,
|
||||||
|
|
Loading…
Reference in a new issue