mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-30 18:34:36 +01:00
HttpRequest::resource() returns current matched resource
This commit is contained in:
parent
b2e771df2c
commit
17c27ef42d
@ -9,6 +9,8 @@
|
||||
|
||||
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
|
||||
|
||||
* Add `HttpRequest::resource()`, returns current matched resource
|
||||
|
||||
* Router cannot parse Non-ASCII characters in URL #137
|
||||
|
||||
* Fix long client urls #129
|
||||
|
@ -68,6 +68,7 @@ smallvec = "0.6"
|
||||
time = "0.1"
|
||||
encoding = "0.2"
|
||||
language-tags = "0.2"
|
||||
lazy_static = "1.0"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.10", features=["percent-encode"] }
|
||||
brotli2 = { version="^0.3.2", optional = true }
|
||||
|
@ -34,16 +34,14 @@ use db::{CreateUser, DbExecutor};
|
||||
|
||||
|
||||
/// State with DbExecutor address
|
||||
struct State {
|
||||
struct App {
|
||||
db: Addr<Syn, DbExecutor>,
|
||||
}
|
||||
|
||||
/// Async request handler
|
||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
let name = &req.match_info()["name"];
|
||||
|
||||
fn index(name: Path<(String,)>, state: State<App>) -> FutureResponse<HttpResponse> {
|
||||
// send async `CreateUser` message to a `DbExecutor`
|
||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
||||
state.db.send(CreateUser{name: name.into_inner()})
|
||||
.from_err()
|
||||
.and_then(|res| {
|
||||
match res {
|
||||
@ -72,7 +70,7 @@ fn main() {
|
||||
App::with_state(State{db: addr.clone()})
|
||||
// enable logger
|
||||
.middleware(middleware::Logger::default())
|
||||
.resource("/{name}", |r| r.method(http::Method::GET).a(index))})
|
||||
.resource("/{name}", |r| r.method(http::Method::GET).with2(index))})
|
||||
.bind("127.0.0.1:8080").unwrap()
|
||||
.start();
|
||||
|
||||
|
@ -4,8 +4,8 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use handler::Reply;
|
||||
use router::{Router, Pattern};
|
||||
use resource::Resource;
|
||||
use router::{Router, Resource};
|
||||
use resource::{ResourceHandler};
|
||||
use header::ContentEncoding;
|
||||
use handler::{Handler, RouteHandler, WrapHandler};
|
||||
use httprequest::HttpRequest;
|
||||
@ -27,10 +27,10 @@ pub struct HttpApplication<S=()> {
|
||||
|
||||
pub(crate) struct Inner<S> {
|
||||
prefix: usize,
|
||||
default: Resource<S>,
|
||||
default: ResourceHandler<S>,
|
||||
encoding: ContentEncoding,
|
||||
router: Router,
|
||||
resources: Vec<Resource<S>>,
|
||||
resources: Vec<ResourceHandler<S>>,
|
||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||
}
|
||||
|
||||
@ -103,10 +103,10 @@ struct ApplicationParts<S> {
|
||||
state: S,
|
||||
prefix: String,
|
||||
settings: ServerSettings,
|
||||
default: Resource<S>,
|
||||
resources: Vec<(Pattern, Option<Resource<S>>)>,
|
||||
default: ResourceHandler<S>,
|
||||
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
|
||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||
external: HashMap<String, Pattern>,
|
||||
external: HashMap<String, Resource>,
|
||||
encoding: ContentEncoding,
|
||||
middlewares: Vec<Box<Middleware<S>>>,
|
||||
}
|
||||
@ -126,7 +126,7 @@ impl App<()> {
|
||||
state: (),
|
||||
prefix: "/".to_owned(),
|
||||
settings: ServerSettings::default(),
|
||||
default: Resource::default_not_found(),
|
||||
default: ResourceHandler::default_not_found(),
|
||||
resources: Vec::new(),
|
||||
handlers: Vec::new(),
|
||||
external: HashMap::new(),
|
||||
@ -156,7 +156,7 @@ impl<S> App<S> where S: 'static {
|
||||
state,
|
||||
prefix: "/".to_owned(),
|
||||
settings: ServerSettings::default(),
|
||||
default: Resource::default_not_found(),
|
||||
default: ResourceHandler::default_not_found(),
|
||||
resources: Vec::new(),
|
||||
handlers: Vec::new(),
|
||||
external: HashMap::new(),
|
||||
@ -236,16 +236,16 @@ impl<S> App<S> where S: 'static {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn resource<F>(mut self, path: &str, f: F) -> App<S>
|
||||
where F: FnOnce(&mut Resource<S>) + 'static
|
||||
where F: FnOnce(&mut ResourceHandler<S>) + 'static
|
||||
{
|
||||
{
|
||||
let parts = self.parts.as_mut().expect("Use after finish");
|
||||
|
||||
// add resource
|
||||
let mut resource = Resource::default();
|
||||
let mut resource = ResourceHandler::default();
|
||||
f(&mut resource);
|
||||
|
||||
let pattern = Pattern::new(resource.get_name(), path);
|
||||
let pattern = Resource::new(resource.get_name(), path);
|
||||
parts.resources.push((pattern, Some(resource)));
|
||||
}
|
||||
self
|
||||
@ -253,7 +253,7 @@ impl<S> App<S> where S: 'static {
|
||||
|
||||
/// Default resource is used if no matched route could be found.
|
||||
pub fn default_resource<F>(mut self, f: F) -> App<S>
|
||||
where F: FnOnce(&mut Resource<S>) + 'static
|
||||
where F: FnOnce(&mut ResourceHandler<S>) + 'static
|
||||
{
|
||||
{
|
||||
let parts = self.parts.as_mut().expect("Use after finish");
|
||||
@ -305,7 +305,8 @@ impl<S> App<S> where S: 'static {
|
||||
panic!("External resource {:?} is registered.", name.as_ref());
|
||||
}
|
||||
parts.external.insert(
|
||||
String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref()));
|
||||
String::from(name.as_ref()),
|
||||
Resource::external(name.as_ref(), url.as_ref()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
12
src/de.rs
12
src/de.rs
@ -554,8 +554,8 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
|
||||
mod tests {
|
||||
use futures::{Async, Future};
|
||||
use super::*;
|
||||
use router::{Router, Pattern};
|
||||
use resource::Resource;
|
||||
use router::{Router, Resource};
|
||||
use resource::ResourceHandler;
|
||||
use test::TestRequest;
|
||||
use server::ServerSettings;
|
||||
|
||||
@ -580,10 +580,10 @@ mod tests {
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
|
||||
|
||||
let mut resource = Resource::<()>::default();
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Pattern::new("index", "/{key}/{value}/"), Some(resource)));
|
||||
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
@ -639,10 +639,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_signle() {
|
||||
let mut resource = Resource::<()>::default();
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Pattern::new("index", "/{value}/"), Some(resource)));
|
||||
routes.push((Resource::new("index", "/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/32/").finish();
|
||||
|
@ -17,7 +17,7 @@ use percent_encoding::percent_decode;
|
||||
use body::Body;
|
||||
use info::ConnectionInfo;
|
||||
use param::Params;
|
||||
use router::Router;
|
||||
use router::{Router, Resource};
|
||||
use payload::Payload;
|
||||
use handler::FromRequest;
|
||||
use httpmessage::HttpMessage;
|
||||
@ -39,6 +39,7 @@ pub struct HttpInnerMessage {
|
||||
pub addr: Option<SocketAddr>,
|
||||
pub payload: Option<Payload>,
|
||||
pub info: Option<ConnectionInfo<'static>>,
|
||||
pub resource: i16,
|
||||
}
|
||||
|
||||
impl Default for HttpInnerMessage {
|
||||
@ -57,6 +58,7 @@ impl Default for HttpInnerMessage {
|
||||
payload: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
resource: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,9 +95,15 @@ impl HttpInnerMessage {
|
||||
self.addr = None;
|
||||
self.info = None;
|
||||
self.payload = None;
|
||||
self.resource = -1;
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static!{
|
||||
static ref RESOURCE: Resource = Resource::default();
|
||||
}
|
||||
|
||||
|
||||
/// An HTTP Request
|
||||
pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
|
||||
|
||||
@ -120,6 +128,7 @@ impl HttpRequest<()> {
|
||||
addr: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
resource: -1,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
@ -318,6 +327,22 @@ impl<S> HttpRequest<S> {
|
||||
self.2.as_ref()
|
||||
}
|
||||
|
||||
/// This method returns reference to matched `Resource` object.
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &Resource {
|
||||
let idx = self.as_ref().resource;
|
||||
if idx >= 0 {
|
||||
if let Some(ref router) = self.2 {
|
||||
return router.get_resource(idx as usize)
|
||||
}
|
||||
}
|
||||
&*RESOURCE
|
||||
}
|
||||
|
||||
pub(crate) fn set_resource(&mut self, idx: usize) {
|
||||
self.as_mut().resource = idx as i16;
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
@ -544,8 +569,8 @@ impl<S> fmt::Debug for HttpRequest<S> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::{Uri, HttpTryFrom};
|
||||
use router::Pattern;
|
||||
use resource::Resource;
|
||||
use router::Resource;
|
||||
use resource::ResourceHandler;
|
||||
use test::TestRequest;
|
||||
use server::ServerSettings;
|
||||
|
||||
@ -607,10 +632,10 @@ mod tests {
|
||||
fn test_request_match_info() {
|
||||
let mut req = TestRequest::with_uri("/value/?id=test").finish();
|
||||
|
||||
let mut resource = Resource::<()>::default();
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Pattern::new("index", "/{key}/"), Some(resource)));
|
||||
routes.push((Resource::new("index", "/{key}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
@ -623,9 +648,9 @@ mod tests {
|
||||
assert_eq!(req2.url_for("unknown", &["test"]),
|
||||
Err(UrlGenerationError::RouterNotAvailable));
|
||||
|
||||
let mut resource = Resource::<()>::default();
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec!((Pattern::new("index", "/user/{name}.{ext}"), Some(resource)));
|
||||
let routes = vec!((Resource::new("index", "/user/{name}.{ext}"), Some(resource)));
|
||||
let (router, _) = Router::new("/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/test/unknown"));
|
||||
@ -645,26 +670,27 @@ mod tests {
|
||||
fn test_url_for_with_prefix() {
|
||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
|
||||
|
||||
let mut resource = Resource::<()>::default();
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![(Pattern::new("index", "/user/{name}.{ext}"), Some(resource))];
|
||||
let routes = vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
|
||||
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/prefix/user/test.html"));
|
||||
|
||||
let req = req.with_state(Rc::new(()), router);
|
||||
let url = req.url_for("index", &["test", "html"]);
|
||||
assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/prefix/user/test.html");
|
||||
assert_eq!(url.ok().unwrap().as_str(),
|
||||
"http://www.rust-lang.org/prefix/user/test.html");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_external() {
|
||||
let req = HttpRequest::default();
|
||||
|
||||
let mut resource = Resource::<()>::default();
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![
|
||||
(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None)];
|
||||
(Resource::external("youtube", "https://youtube.com/watch/{video_id}"), None)];
|
||||
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
|
||||
assert!(!router.has_route("https://youtube.com/watch/unknown"));
|
||||
|
||||
|
@ -60,6 +60,8 @@ extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate futures_cpupool;
|
||||
extern crate tokio_io;
|
||||
@ -174,12 +176,12 @@ pub mod dev {
|
||||
|
||||
pub use body::BodyStream;
|
||||
pub use context::Drain;
|
||||
pub use json::JsonBody;
|
||||
pub use info::ConnectionInfo;
|
||||
pub use handler::{Handler, Reply, FromRequest};
|
||||
pub use route::Route;
|
||||
pub use resource::Resource;
|
||||
pub use json::JsonBody;
|
||||
pub use router::{Router, Pattern};
|
||||
pub use router::{Router, Resource};
|
||||
pub use resource::ResourceHandler;
|
||||
pub use param::{FromParam, Params};
|
||||
pub use httpmessage::{UrlEncoded, MessageBody};
|
||||
pub use httpresponse::HttpResponseBuilder;
|
||||
|
@ -10,7 +10,7 @@
|
||||
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
|
||||
//!
|
||||
//! Cors middleware could be used as parameter for `App::middleware()` or
|
||||
//! `Resource::middleware()` methods. But you have to use `Cors::register()` method to
|
||||
//! `ResourceHandler::middleware()` methods. But you have to use `Cors::register()` method to
|
||||
//! support *preflight* OPTIONS request.
|
||||
//!
|
||||
//!
|
||||
@ -52,7 +52,7 @@ use http::{self, Method, HttpTryFrom, Uri, StatusCode};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
|
||||
use error::{Result, ResponseError};
|
||||
use resource::Resource;
|
||||
use resource::ResourceHandler;
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
@ -212,10 +212,10 @@ impl Cors {
|
||||
/// This method register cors middleware with resource and
|
||||
/// adds route for *OPTIONS* preflight requests.
|
||||
///
|
||||
/// It is possible to register *Cors* middleware with `Resource::middleware()`
|
||||
/// It is possible to register *Cors* middleware with `ResourceHandler::middleware()`
|
||||
/// method, but in that case *Cors* middleware wont be able to handle *OPTIONS*
|
||||
/// requests.
|
||||
pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
|
||||
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
|
||||
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
|
||||
resource.middleware(self);
|
||||
}
|
||||
|
@ -31,16 +31,16 @@ use httpresponse::HttpResponse;
|
||||
/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
|
||||
/// .finish();
|
||||
/// }
|
||||
pub struct Resource<S=()> {
|
||||
pub struct ResourceHandler<S=()> {
|
||||
name: String,
|
||||
state: PhantomData<S>,
|
||||
routes: SmallVec<[Route<S>; 3]>,
|
||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||
}
|
||||
|
||||
impl<S> Default for Resource<S> {
|
||||
impl<S> Default for ResourceHandler<S> {
|
||||
fn default() -> Self {
|
||||
Resource {
|
||||
ResourceHandler {
|
||||
name: String::new(),
|
||||
state: PhantomData,
|
||||
routes: SmallVec::new(),
|
||||
@ -48,10 +48,10 @@ impl<S> Default for Resource<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Resource<S> {
|
||||
impl<S> ResourceHandler<S> {
|
||||
|
||||
pub(crate) fn default_not_found() -> Self {
|
||||
Resource {
|
||||
ResourceHandler {
|
||||
name: String::new(),
|
||||
state: PhantomData,
|
||||
routes: SmallVec::new(),
|
||||
@ -68,7 +68,7 @@ impl<S> Resource<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Resource<S> {
|
||||
impl<S: 'static> ResourceHandler<S> {
|
||||
|
||||
/// Register a new route and return mutable reference to *Route* object.
|
||||
/// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
|
||||
@ -97,7 +97,7 @@ impl<S: 'static> Resource<S> {
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index)
|
||||
/// Application::resource("/", |r| r.route().filter(pred::Get()).f(index)
|
||||
/// ```
|
||||
pub fn method(&mut self, method: Method) -> &mut Route<S> {
|
||||
self.routes.push(Route::default());
|
||||
@ -109,7 +109,7 @@ impl<S: 'static> Resource<S> {
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Resource::resource("/", |r| r.route().h(handler)
|
||||
/// Application::resource("/", |r| r.route().h(handler)
|
||||
/// ```
|
||||
pub fn h<H: Handler<S>>(&mut self, handler: H) {
|
||||
self.routes.push(Route::default());
|
||||
@ -121,7 +121,7 @@ impl<S: 'static> Resource<S> {
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Resource::resource("/", |r| r.route().f(index)
|
||||
/// Application::resource("/", |r| r.route().f(index)
|
||||
/// ```
|
||||
pub fn f<F, R>(&mut self, handler: F)
|
||||
where F: Fn(HttpRequest<S>) -> R + 'static,
|
||||
@ -136,7 +136,7 @@ impl<S: 'static> Resource<S> {
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Resource::resource("/", |r| r.route().with(index)
|
||||
/// Application::resource("/", |r| r.route().with(index)
|
||||
/// ```
|
||||
pub fn with<T, F, R>(&mut self, handler: F)
|
||||
where F: Fn(T) -> R + 'static,
|
||||
@ -147,7 +147,7 @@ impl<S: 'static> Resource<S> {
|
||||
self.routes.last_mut().unwrap().with(handler)
|
||||
}
|
||||
|
||||
/// Register a middleware
|
||||
/// Register a resource middleware
|
||||
///
|
||||
/// This is similar to `App's` middlewares, but
|
||||
/// middlewares get invoked on resource level.
|
||||
@ -157,7 +157,7 @@ impl<S: 'static> Resource<S> {
|
||||
|
||||
pub(crate) fn handle(&mut self,
|
||||
mut req: HttpRequest<S>,
|
||||
default: Option<&mut Resource<S>>) -> Reply
|
||||
default: Option<&mut ResourceHandler<S>>) -> Reply
|
||||
{
|
||||
for route in &mut self.routes {
|
||||
if route.check(&mut req) {
|
||||
|
149
src/router.rs
149
src/router.rs
@ -6,9 +6,9 @@ use std::collections::HashMap;
|
||||
use regex::{Regex, escape};
|
||||
use percent_encoding::percent_decode;
|
||||
|
||||
use error::UrlGenerationError;
|
||||
use param::Params;
|
||||
use resource::Resource;
|
||||
use error::UrlGenerationError;
|
||||
use resource::ResourceHandler;
|
||||
use httprequest::HttpRequest;
|
||||
use server::ServerSettings;
|
||||
|
||||
@ -19,8 +19,8 @@ pub struct Router(Rc<Inner>);
|
||||
struct Inner {
|
||||
prefix: String,
|
||||
prefix_len: usize,
|
||||
named: HashMap<String, (Pattern, bool)>,
|
||||
patterns: Vec<Pattern>,
|
||||
named: HashMap<String, (Resource, bool)>,
|
||||
patterns: Vec<Resource>,
|
||||
srv: ServerSettings,
|
||||
}
|
||||
|
||||
@ -28,7 +28,8 @@ impl Router {
|
||||
/// Create new router
|
||||
pub fn new<S>(prefix: &str,
|
||||
settings: ServerSettings,
|
||||
map: Vec<(Pattern, Option<Resource<S>>)>) -> (Router, Vec<Resource<S>>)
|
||||
map: Vec<(Resource, Option<ResourceHandler<S>>)>)
|
||||
-> (Router, Vec<ResourceHandler<S>>)
|
||||
{
|
||||
let prefix = prefix.trim().trim_right_matches('/').to_owned();
|
||||
let mut named = HashMap::new();
|
||||
@ -64,6 +65,10 @@ impl Router {
|
||||
&self.0.srv
|
||||
}
|
||||
|
||||
pub(crate) fn get_resource(&self, idx: usize) -> &Resource {
|
||||
&self.0.patterns[idx]
|
||||
}
|
||||
|
||||
/// Query for matched resource
|
||||
pub fn recognize<S>(&self, req: &mut HttpRequest<S>) -> Option<usize> {
|
||||
if self.0.prefix_len > req.path().len() {
|
||||
@ -75,6 +80,7 @@ impl Router {
|
||||
|
||||
for (idx, pattern) in self.0.patterns.iter().enumerate() {
|
||||
if pattern.match_with_params(p.as_ref(), req.match_info_mut()) {
|
||||
req.set_resource(idx);
|
||||
return Some(idx)
|
||||
}
|
||||
}
|
||||
@ -108,11 +114,7 @@ impl Router {
|
||||
I: AsRef<str>,
|
||||
{
|
||||
if let Some(pattern) = self.0.named.get(name) {
|
||||
if pattern.1 {
|
||||
pattern.0.path(None, elements)
|
||||
} else {
|
||||
pattern.0.path(Some(&self.0.prefix), elements)
|
||||
}
|
||||
pattern.0.resource_path(self, elements)
|
||||
} else {
|
||||
Err(UrlGenerationError::ResourceNotFound)
|
||||
}
|
||||
@ -125,6 +127,7 @@ impl Clone for Router {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum PatternElement {
|
||||
Str(String),
|
||||
@ -137,25 +140,48 @@ enum PatternType {
|
||||
Dynamic(Regex, Vec<String>),
|
||||
}
|
||||
|
||||
/// Reslource type describes an entry in resources table
|
||||
#[derive(Clone)]
|
||||
pub struct Pattern {
|
||||
pub struct Resource {
|
||||
tp: PatternType,
|
||||
name: String,
|
||||
pattern: String,
|
||||
elements: Vec<PatternElement>,
|
||||
external: bool,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
impl Default for Resource {
|
||||
fn default() -> Resource {
|
||||
Resource {
|
||||
tp: PatternType::Static("".to_owned()),
|
||||
name: "".to_owned(),
|
||||
pattern: "".to_owned(),
|
||||
elements: Vec::new(),
|
||||
external: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource {
|
||||
/// Parse path pattern and create new `Resource` instance.
|
||||
///
|
||||
/// Panics if path pattern is wrong.
|
||||
pub fn new(name: &str, path: &str) -> Self {
|
||||
Pattern::with_prefix(name, path, "/")
|
||||
Resource::with_prefix(name, path, "/")
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance with custom prefix
|
||||
/// Construct external resource
|
||||
///
|
||||
/// Panics if path pattern is wrong.
|
||||
pub fn external(name: &str, path: &str) -> Self {
|
||||
let mut resource = Resource::with_prefix(name, path, "/");
|
||||
resource.external = true;
|
||||
resource
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Resource` instance with custom prefix
|
||||
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
|
||||
let (pattern, elements, is_dynamic) = Pattern::parse(path, prefix);
|
||||
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
|
||||
|
||||
let tp = if is_dynamic {
|
||||
let re = match Regex::new(&pattern) {
|
||||
@ -170,20 +196,21 @@ impl Pattern {
|
||||
PatternType::Static(pattern.clone())
|
||||
};
|
||||
|
||||
Pattern {
|
||||
Resource {
|
||||
tp,
|
||||
pattern,
|
||||
elements,
|
||||
name: name.into(),
|
||||
pattern: path.to_owned(),
|
||||
external: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns name of the pattern
|
||||
/// Name of the resource
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns path of the pattern
|
||||
/// Path pattern of the resource
|
||||
pub fn pattern(&self) -> &str {
|
||||
&self.pattern
|
||||
}
|
||||
@ -219,14 +246,15 @@ impl Pattern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build pattern path.
|
||||
pub fn path<U, I>(&self, prefix: Option<&str>, elements: U) -> Result<String, UrlGenerationError>
|
||||
/// Build reousrce path.
|
||||
pub fn resource_path<U, I>(&self, router: &Router, elements: U)
|
||||
-> Result<String, UrlGenerationError>
|
||||
where U: IntoIterator<Item=I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
let mut iter = elements.into_iter();
|
||||
let mut path = if let Some(prefix) = prefix {
|
||||
format!("{}/", prefix)
|
||||
let mut path = if !self.external {
|
||||
format!("{}/", router.prefix())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
@ -309,15 +337,15 @@ impl Pattern {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Pattern {
|
||||
fn eq(&self, other: &Pattern) -> bool {
|
||||
impl PartialEq for Resource {
|
||||
fn eq(&self, other: &Resource) -> bool {
|
||||
self.pattern == other.pattern
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pattern {}
|
||||
impl Eq for Resource {}
|
||||
|
||||
impl Hash for Pattern {
|
||||
impl Hash for Resource {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.pattern.hash(state);
|
||||
}
|
||||
@ -331,13 +359,20 @@ mod tests {
|
||||
#[test]
|
||||
fn test_recognizer() {
|
||||
let routes = vec![
|
||||
(Pattern::new("", "/name"), Some(Resource::default())),
|
||||
(Pattern::new("", "/name/{val}"), Some(Resource::default())),
|
||||
(Pattern::new("", "/name/{val}/index.html"), Some(Resource::default())),
|
||||
(Pattern::new("", "/file/{file}.{ext}"), Some(Resource::default())),
|
||||
(Pattern::new("", "/v{val}/{val2}/index.html"), Some(Resource::default())),
|
||||
(Pattern::new("", "/v/{tail:.*}"), Some(Resource::default())),
|
||||
(Pattern::new("", "{test}/index.html"), Some(Resource::default()))];
|
||||
(Resource::new("", "/name"),
|
||||
Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/name/{val}"),
|
||||
Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/name/{val}/index.html"),
|
||||
Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/file/{file}.{ext}"),
|
||||
Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/v{val}/{val2}/index.html"),
|
||||
Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/v/{tail:.*}"),
|
||||
Some(ResourceHandler::default())),
|
||||
(Resource::new("", "{test}/index.html"),
|
||||
Some(ResourceHandler::default()))];
|
||||
let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/name").finish();
|
||||
@ -375,8 +410,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_recognizer_2() {
|
||||
let routes = vec![
|
||||
(Pattern::new("", "/index.json"), Some(Resource::default())),
|
||||
(Pattern::new("", "/{source}.json"), Some(Resource::default()))];
|
||||
(Resource::new("", "/index.json"), Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/{source}.json"), Some(ResourceHandler::default()))];
|
||||
let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/index.json").finish();
|
||||
@ -389,8 +424,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_recognizer_with_prefix() {
|
||||
let routes = vec![
|
||||
(Pattern::new("", "/name"), Some(Resource::default())),
|
||||
(Pattern::new("", "/name/{val}"), Some(Resource::default()))];
|
||||
(Resource::new("", "/name"), Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/name/{val}"), Some(ResourceHandler::default()))];
|
||||
let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/name").finish();
|
||||
@ -406,8 +441,8 @@ mod tests {
|
||||
|
||||
// same patterns
|
||||
let routes = vec![
|
||||
(Pattern::new("", "/name"), Some(Resource::default())),
|
||||
(Pattern::new("", "/name/{val}"), Some(Resource::default()))];
|
||||
(Resource::new("", "/name"), Some(ResourceHandler::default())),
|
||||
(Resource::new("", "/name/{val}"), Some(ResourceHandler::default()))];
|
||||
let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/name").finish();
|
||||
@ -423,22 +458,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_static() {
|
||||
let re = Pattern::new("test", "/");
|
||||
let re = Resource::new("test", "/");
|
||||
assert!(re.is_match("/"));
|
||||
assert!(!re.is_match("/a"));
|
||||
|
||||
let re = Pattern::new("test", "/name");
|
||||
let re = Resource::new("test", "/name");
|
||||
assert!(re.is_match("/name"));
|
||||
assert!(!re.is_match("/name1"));
|
||||
assert!(!re.is_match("/name/"));
|
||||
assert!(!re.is_match("/name~"));
|
||||
|
||||
let re = Pattern::new("test", "/name/");
|
||||
let re = Resource::new("test", "/name/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(!re.is_match("/name"));
|
||||
assert!(!re.is_match("/name/gs"));
|
||||
|
||||
let re = Pattern::new("test", "/user/profile");
|
||||
let re = Resource::new("test", "/user/profile");
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(!re.is_match("/user/profile/profile"));
|
||||
}
|
||||
@ -447,7 +482,7 @@ mod tests {
|
||||
fn test_parse_param() {
|
||||
let mut req = HttpRequest::default();
|
||||
|
||||
let re = Pattern::new("test", "/user/{id}");
|
||||
let re = Resource::new("test", "/user/{id}");
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(!re.is_match("/user/2345/"));
|
||||
@ -461,7 +496,7 @@ mod tests {
|
||||
assert!(re.match_with_params("/user/1245125", req.match_info_mut()));
|
||||
assert_eq!(req.match_info().get("id").unwrap(), "1245125");
|
||||
|
||||
let re = Pattern::new("test", "/v{version}/resource/{id}");
|
||||
let re = Resource::new("test", "/v{version}/resource/{id}");
|
||||
assert!(re.is_match("/v1/resource/320120"));
|
||||
assert!(!re.is_match("/v/resource/1"));
|
||||
assert!(!re.is_match("/resource"));
|
||||
@ -471,4 +506,24 @@ mod tests {
|
||||
assert_eq!(req.match_info().get("version").unwrap(), "151");
|
||||
assert_eq!(req.match_info().get("id").unwrap(), "adahg32");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_resource() {
|
||||
let routes = vec![
|
||||
(Resource::new("r1", "/index.json"), Some(ResourceHandler::default())),
|
||||
(Resource::new("r2", "/test.json"), Some(ResourceHandler::default()))];
|
||||
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/index.json")
|
||||
.finish_with_router(router.clone());
|
||||
assert_eq!(router.recognize(&mut req), Some(0));
|
||||
let resource = req.resource();
|
||||
assert_eq!(resource.name(), "r1");
|
||||
|
||||
let mut req = TestRequest::with_uri("/test.json")
|
||||
.finish_with_router(router.clone());
|
||||
assert_eq!(router.recognize(&mut req), Some(1));
|
||||
let resource = req.resource();
|
||||
assert_eq!(resource.name(), "r2");
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ use application::{App, HttpApplication};
|
||||
use param::Params;
|
||||
use router::Router;
|
||||
use payload::Payload;
|
||||
use resource::Resource;
|
||||
use resource::ResourceHandler;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use server::{HttpServer, IntoHttpHandler, ServerSettings};
|
||||
@ -352,7 +352,7 @@ impl<S: 'static> TestApp<S> {
|
||||
/// Register resource. This method is similar
|
||||
/// to `App::resource()` method.
|
||||
pub fn resource<F>(&mut self, path: &str, f: F) -> &mut TestApp<S>
|
||||
where F: FnOnce(&mut Resource<S>) + 'static
|
||||
where F: FnOnce(&mut ResourceHandler<S>) + 'static
|
||||
{
|
||||
self.app = Some(self.app.take().unwrap().resource(path, f));
|
||||
self
|
||||
|
Loading…
Reference in New Issue
Block a user