mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-23 13:02:28 +00:00
Add path matchers that are more capable than gotham's stock ones
This commit is contained in:
parent
d08d9bea8c
commit
4ce53bc361
7 changed files with 435 additions and 16 deletions
|
@ -25,18 +25,19 @@ gotham_derive = { git = "https://github.com/gotham-rs/gotham", version = "0.5.0-
|
||||||
gotham_middleware_diesel = { git = "https://github.com/gotham-rs/gotham", version = "0.1.0", optional = true }
|
gotham_middleware_diesel = { git = "https://github.com/gotham-rs/gotham", version = "0.1.0", optional = true }
|
||||||
gotham_restful_derive = { version = "0.0.4-dev" }
|
gotham_restful_derive = { version = "0.0.4-dev" }
|
||||||
indexmap = { version = "1.3.2", optional = true }
|
indexmap = { version = "1.3.2", optional = true }
|
||||||
|
itertools = "0.9.0"
|
||||||
jsonwebtoken = { version = "7.1.0", optional = true }
|
jsonwebtoken = { version = "7.1.0", optional = true }
|
||||||
log = { version = "0.4.8", optional = true }
|
log = { version = "0.4.8", optional = true }
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
openapiv3 = { version = "0.3", optional = true }
|
openapiv3 = { version = "0.3", optional = true }
|
||||||
serde = { version = "1.0.106", features = ["derive"] }
|
serde = { version = "1.0.106", features = ["derive"] }
|
||||||
serde_json = "1.0.51"
|
serde_json = "1.0.51"
|
||||||
|
thiserror = "1.0.15"
|
||||||
uuid = { version = ">= 0.1, < 0.9", optional = true }
|
uuid = { version = ">= 0.1, < 0.9", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures-executor = "0.3.4"
|
futures-executor = "0.3.4"
|
||||||
paste = "0.1.10"
|
paste = "0.1.10"
|
||||||
thiserror = "1"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -151,6 +151,8 @@ pub use auth::{
|
||||||
StaticAuthHandler
|
StaticAuthHandler
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod matcher;
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
mod openapi;
|
mod openapi;
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
|
|
214
gotham_restful/src/matcher/accept.rs
Normal file
214
gotham_restful/src/matcher/accept.rs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
use super::{LookupTable, LookupTableFromTypes};
|
||||||
|
use gotham::{
|
||||||
|
hyper::{
|
||||||
|
header::{HeaderMap, ACCEPT},
|
||||||
|
StatusCode
|
||||||
|
},
|
||||||
|
router::{non_match::RouteNonMatch, route::matcher::RouteMatcher},
|
||||||
|
state::{FromState, State}
|
||||||
|
};
|
||||||
|
use mime::Mime;
|
||||||
|
use std::{
|
||||||
|
num::ParseFloatError,
|
||||||
|
str::FromStr
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
||||||
|
/// A mime type that is optionally weighted with a quality.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct QMime
|
||||||
|
{
|
||||||
|
mime : Mime,
|
||||||
|
weight : Option<f32>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QMime
|
||||||
|
{
|
||||||
|
fn new(mime : Mime, weight : Option<f32>) -> Self
|
||||||
|
{
|
||||||
|
Self { mime, weight }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum QMimeError
|
||||||
|
{
|
||||||
|
#[error("Unable to parse mime type: {0}")]
|
||||||
|
MimeError(#[from] mime::FromStrError),
|
||||||
|
#[error("Unable to parse mime quality: {0}")]
|
||||||
|
NumError(#[from] ParseFloatError)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for QMime
|
||||||
|
{
|
||||||
|
type Err = QMimeError;
|
||||||
|
|
||||||
|
fn from_str(str : &str) -> Result<Self, Self::Err>
|
||||||
|
{
|
||||||
|
match str.find(";q=") {
|
||||||
|
None => Ok(Self::new(str.parse()?, None)),
|
||||||
|
Some(index) => {
|
||||||
|
let mime = str[..index].parse()?;
|
||||||
|
let weight = str[index+3..].parse()?;
|
||||||
|
Ok(Self::new(mime, Some(weight)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
A route matcher that checks for the presence of a supported content type.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
# use gotham::{helpers::http::response::create_response, hyper::StatusCode, router::builder::*};
|
||||||
|
# use gotham_restful::matcher::ContentTypeMatcher;
|
||||||
|
#
|
||||||
|
let types = vec![mime::TEXT_HTML, mime::TEXT_PLAIN];
|
||||||
|
let matcher = ContentTypeMatcher::new(types)
|
||||||
|
// optionally accept requests with no content type
|
||||||
|
.allow_no_type();
|
||||||
|
|
||||||
|
# build_simple_router(|route| {
|
||||||
|
// use the matcher for your request
|
||||||
|
route.post("/foo")
|
||||||
|
.extend_route_matcher(matcher)
|
||||||
|
.to(|state| {
|
||||||
|
let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, "Correct Content Type!");
|
||||||
|
(state, res)
|
||||||
|
});
|
||||||
|
# });
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AcceptHeaderMatcher
|
||||||
|
{
|
||||||
|
types : Vec<Mime>,
|
||||||
|
lookup_table : LookupTable
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcceptHeaderMatcher
|
||||||
|
{
|
||||||
|
/// Create a new `AcceptHeaderMatcher` with the given types that can be produced by the route.
|
||||||
|
pub fn new(types : Vec<Mime>) -> Self
|
||||||
|
{
|
||||||
|
let lookup_table = LookupTable::from_types(types.iter(), true);
|
||||||
|
Self { types, lookup_table }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn err() -> RouteNonMatch
|
||||||
|
{
|
||||||
|
RouteNonMatch::new(StatusCode::NOT_ACCEPTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouteMatcher for AcceptHeaderMatcher
|
||||||
|
{
|
||||||
|
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
|
||||||
|
{
|
||||||
|
HeaderMap::borrow_from(state).get(ACCEPT)
|
||||||
|
.map(|header| {
|
||||||
|
// parse mime types from the accept header
|
||||||
|
let acceptable = header.to_str()
|
||||||
|
.map_err(|_| err())?
|
||||||
|
.split(',')
|
||||||
|
.map(|str| str.trim().parse())
|
||||||
|
.collect::<Result<Vec<QMime>, _>>()
|
||||||
|
.map_err(|_| err())?;
|
||||||
|
|
||||||
|
for qmime in acceptable
|
||||||
|
{
|
||||||
|
// get mime type candidates from the lookup table
|
||||||
|
let essence = qmime.mime.essence_str();
|
||||||
|
let candidates = match self.lookup_table.get(essence) {
|
||||||
|
Some(candidates) => candidates,
|
||||||
|
None => continue
|
||||||
|
};
|
||||||
|
for i in candidates
|
||||||
|
{
|
||||||
|
let candidate = &self.types[*i];
|
||||||
|
|
||||||
|
// check that the candidates have the same suffix - this is not included in the
|
||||||
|
// essence string
|
||||||
|
if candidate.suffix() != qmime.mime.suffix()
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// this candidate matches - params don't play a role in accept header matching
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no candidates found
|
||||||
|
Err(err())
|
||||||
|
}).unwrap_or_else(|| {
|
||||||
|
// no accept header - assume all types are acceptable
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test
|
||||||
|
{
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn with_state<F>(accept : Option<&str>, block : F)
|
||||||
|
where F : FnOnce(&mut State) -> ()
|
||||||
|
{
|
||||||
|
State::with_new(|state| {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
if let Some(acc) = accept
|
||||||
|
{
|
||||||
|
headers.insert(ACCEPT, acc.parse().unwrap());
|
||||||
|
}
|
||||||
|
state.put(headers);
|
||||||
|
block(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_accept_header()
|
||||||
|
{
|
||||||
|
let matcher = AcceptHeaderMatcher::new(vec!(mime::TEXT_PLAIN));
|
||||||
|
with_state(None, |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_mime_type()
|
||||||
|
{
|
||||||
|
let matcher = AcceptHeaderMatcher::new(vec!(mime::TEXT_PLAIN, mime::IMAGE_PNG));
|
||||||
|
with_state(Some("text/plain"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
with_state(Some("text/html"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("image/png"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
with_state(Some("image/webp"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn star_star()
|
||||||
|
{
|
||||||
|
let matcher = AcceptHeaderMatcher::new(vec!(mime::IMAGE_PNG));
|
||||||
|
with_state(Some("*/*"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn image_star()
|
||||||
|
{
|
||||||
|
let matcher = AcceptHeaderMatcher::new(vec!(mime::IMAGE_PNG));
|
||||||
|
with_state(Some("image/*"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn complex_header()
|
||||||
|
{
|
||||||
|
let matcher = AcceptHeaderMatcher::new(vec!(mime::IMAGE_PNG));
|
||||||
|
with_state(Some("text/html,image/webp;q=0.8"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("text/html,image/webp;q=0.8,*/*;q=0.1"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
}
|
||||||
|
}
|
173
gotham_restful/src/matcher/content_type.rs
Normal file
173
gotham_restful/src/matcher/content_type.rs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
use super::{LookupTable, LookupTableFromTypes};
|
||||||
|
use gotham::{
|
||||||
|
hyper::{
|
||||||
|
header::{HeaderMap, CONTENT_TYPE},
|
||||||
|
StatusCode
|
||||||
|
},
|
||||||
|
router::{non_match::RouteNonMatch, route::matcher::RouteMatcher},
|
||||||
|
state::{FromState, State}
|
||||||
|
};
|
||||||
|
use mime::Mime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A route matcher that checks for the presence of a supported content type.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
# use gotham::{helpers::http::response::create_response, hyper::StatusCode, router::builder::*};
|
||||||
|
# use gotham_restful::matcher::ContentTypeMatcher;
|
||||||
|
#
|
||||||
|
let types = vec![mime::TEXT_HTML, mime::TEXT_PLAIN];
|
||||||
|
let matcher = ContentTypeMatcher::new(types)
|
||||||
|
// optionally accept requests with no content type
|
||||||
|
.allow_no_type();
|
||||||
|
|
||||||
|
# build_simple_router(|route| {
|
||||||
|
// use the matcher for your request
|
||||||
|
route.post("/foo")
|
||||||
|
.extend_route_matcher(matcher)
|
||||||
|
.to(|state| {
|
||||||
|
let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, "Correct Content Type!");
|
||||||
|
(state, res)
|
||||||
|
});
|
||||||
|
# });
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ContentTypeMatcher
|
||||||
|
{
|
||||||
|
types : Vec<Mime>,
|
||||||
|
lookup_table : LookupTable,
|
||||||
|
allow_no_type : bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentTypeMatcher
|
||||||
|
{
|
||||||
|
/// Create a new `ContentTypeMatcher` with the given supported types that does not allow requests
|
||||||
|
/// that don't include a content-type header.
|
||||||
|
pub fn new(types : Vec<Mime>) -> Self
|
||||||
|
{
|
||||||
|
let lookup_table = LookupTable::from_types(types.iter(), false);
|
||||||
|
Self { types, lookup_table, allow_no_type: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modify this matcher to allow requests that don't include a content-type header.
|
||||||
|
pub fn allow_no_type(mut self) -> Self
|
||||||
|
{
|
||||||
|
self.allow_no_type = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn err() -> RouteNonMatch
|
||||||
|
{
|
||||||
|
RouteNonMatch::new(StatusCode::UNSUPPORTED_MEDIA_TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouteMatcher for ContentTypeMatcher
|
||||||
|
{
|
||||||
|
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
|
||||||
|
{
|
||||||
|
HeaderMap::borrow_from(state).get(CONTENT_TYPE)
|
||||||
|
.map(|ty| {
|
||||||
|
// parse mime type from the content type header
|
||||||
|
let mime : Mime = ty.to_str()
|
||||||
|
.map_err(|_| err())?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| err())?;
|
||||||
|
|
||||||
|
// get mime type candidates from the lookup table
|
||||||
|
let essence = mime.essence_str();
|
||||||
|
let candidates = self.lookup_table.get(essence).ok_or_else(err)?;
|
||||||
|
for i in candidates
|
||||||
|
{
|
||||||
|
let candidate = &self.types[*i];
|
||||||
|
|
||||||
|
// check that the candidates have the same suffix - this is not included in the
|
||||||
|
// essence string
|
||||||
|
if candidate.suffix() != mime.suffix()
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that this candidate has at least the parameters that the content type
|
||||||
|
// has and that their values are equal
|
||||||
|
if candidate.params().any(|(key, value)| mime.get_param(key) != Some(value))
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// this candidate matches
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// no candidates found
|
||||||
|
Err(err())
|
||||||
|
}).unwrap_or_else(|| {
|
||||||
|
// no type present
|
||||||
|
if self.allow_no_type { Ok(()) } else { Err(err()) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test
|
||||||
|
{
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn with_state<F>(content_type : Option<&str>, block : F)
|
||||||
|
where F : FnOnce(&mut State) -> ()
|
||||||
|
{
|
||||||
|
State::with_new(|state| {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
if let Some(ty) = content_type
|
||||||
|
{
|
||||||
|
headers.insert(CONTENT_TYPE, ty.parse().unwrap());
|
||||||
|
}
|
||||||
|
state.put(headers);
|
||||||
|
block(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_type_list()
|
||||||
|
{
|
||||||
|
let matcher = ContentTypeMatcher::new(Vec::new());
|
||||||
|
with_state(None, |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("text/plain"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
|
||||||
|
let matcher = matcher.allow_no_type();
|
||||||
|
with_state(None, |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_type()
|
||||||
|
{
|
||||||
|
let matcher = ContentTypeMatcher::new(vec![mime::TEXT_PLAIN]);
|
||||||
|
with_state(None, |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("text/plain"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
with_state(Some("text/plain; charset=utf-8"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn complex_type()
|
||||||
|
{
|
||||||
|
let matcher = ContentTypeMatcher::new(vec!["image/svg+xml; charset=utf-8".parse().unwrap()]);
|
||||||
|
with_state(Some("image/svg"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("image/svg+xml"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("image/svg+xml; charset=utf-8"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
with_state(Some("image/svg+xml; charset=utf-8; eol=lf"), |state| assert!(matcher.is_match(&state).is_ok()));
|
||||||
|
with_state(Some("image/svg+xml; charset=us-ascii"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
with_state(Some("image/svg+json; charset=utf-8"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_mismatch()
|
||||||
|
{
|
||||||
|
let matcher = ContentTypeMatcher::new(vec![mime::TEXT_HTML]);
|
||||||
|
with_state(Some("text/plain"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
|
}
|
||||||
|
}
|
37
gotham_restful/src/matcher/mod.rs
Normal file
37
gotham_restful/src/matcher/mod.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use mime::Mime;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
mod accept;
|
||||||
|
pub use accept::AcceptHeaderMatcher;
|
||||||
|
|
||||||
|
mod content_type;
|
||||||
|
pub use content_type::ContentTypeMatcher;
|
||||||
|
|
||||||
|
type LookupTable = HashMap<String, Vec<usize>>;
|
||||||
|
|
||||||
|
trait LookupTableFromTypes
|
||||||
|
{
|
||||||
|
fn from_types<'a, I : Iterator<Item = &'a Mime>>(types : I, include_stars : bool) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LookupTableFromTypes for LookupTable
|
||||||
|
{
|
||||||
|
fn from_types<'a, I : Iterator<Item = &'a Mime>>(types : I, include_stars : bool) -> Self
|
||||||
|
{
|
||||||
|
if include_stars
|
||||||
|
{
|
||||||
|
types
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(i, mime)| vec![("*/*".to_owned(), i), (format!("{}/*", mime.type_()), i), (mime.essence_str().to_owned(), i)].into_iter())
|
||||||
|
.into_group_map()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
types
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, mime)| (mime.essence_str().to_owned(), i))
|
||||||
|
.into_group_map()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use futures_util::{future, future::FutureExt};
|
||||||
use gotham::hyper::Body;
|
use gotham::hyper::Body;
|
||||||
#[cfg(feature = "errorlog")]
|
#[cfg(feature = "errorlog")]
|
||||||
use log::error;
|
use log::error;
|
||||||
use mime::{Mime, APPLICATION_JSON, STAR_STAR};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
|
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -581,11 +581,6 @@ where
|
||||||
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
|
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
|
||||||
{
|
|
||||||
Some(vec![STAR_STAR])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
matcher::{AcceptHeaderMatcher, ContentTypeMatcher},
|
||||||
resource::*,
|
resource::*,
|
||||||
result::{ResourceError, ResourceResult, Response},
|
result::{ResourceError, ResourceResult, Response},
|
||||||
RequestBody,
|
RequestBody,
|
||||||
|
@ -15,11 +16,7 @@ use gotham::{
|
||||||
router::{
|
router::{
|
||||||
builder::*,
|
builder::*,
|
||||||
non_match::RouteNonMatch,
|
non_match::RouteNonMatch,
|
||||||
route::matcher::{
|
route::matcher::RouteMatcher
|
||||||
content_type::ContentTypeHeaderRouteMatcher,
|
|
||||||
AcceptHeaderRouteMatcher,
|
|
||||||
RouteMatcher
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
state::{FromState, State}
|
state::{FromState, State}
|
||||||
};
|
};
|
||||||
|
@ -253,7 +250,7 @@ fn delete_handler<Handler : ResourceDelete>(state : State) -> Pin<Box<HandlerFut
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MaybeMatchAcceptHeader
|
struct MaybeMatchAcceptHeader
|
||||||
{
|
{
|
||||||
matcher : Option<AcceptHeaderRouteMatcher>
|
matcher : Option<AcceptHeaderMatcher>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteMatcher for MaybeMatchAcceptHeader
|
impl RouteMatcher for MaybeMatchAcceptHeader
|
||||||
|
@ -276,7 +273,7 @@ impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader
|
||||||
types => types
|
types => types
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
matcher: types.map(AcceptHeaderRouteMatcher::new)
|
matcher: types.map(AcceptHeaderMatcher::new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,7 +281,7 @@ impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MaybeMatchContentTypeHeader
|
struct MaybeMatchContentTypeHeader
|
||||||
{
|
{
|
||||||
matcher : Option<ContentTypeHeaderRouteMatcher>
|
matcher : Option<ContentTypeMatcher>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteMatcher for MaybeMatchContentTypeHeader
|
impl RouteMatcher for MaybeMatchContentTypeHeader
|
||||||
|
@ -303,7 +300,7 @@ impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader
|
||||||
fn from(types : Option<Vec<Mime>>) -> Self
|
fn from(types : Option<Vec<Mime>>) -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
matcher: types.map(ContentTypeHeaderRouteMatcher::new)
|
matcher: types.map(ContentTypeMatcher::new).map(ContentTypeMatcher::allow_no_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue