mirror of
https://github.com/actix/actix-web.git
synced 2024-12-24 17:10:33 +00:00
added HttpRequest::url_for
This commit is contained in:
parent
8d52e2bbd9
commit
0dd27bd224
9 changed files with 179 additions and 88 deletions
|
@ -1,59 +1,15 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use error::UriGenerationError;
|
|
||||||
use handler::{Reply, RouteHandler};
|
use handler::{Reply, RouteHandler};
|
||||||
|
use router::Router;
|
||||||
use resource::Resource;
|
use resource::Resource;
|
||||||
use recognizer::{RouteRecognizer, check_pattern, PatternElement};
|
use recognizer::check_pattern;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use channel::{HttpHandler, IntoHttpHandler};
|
use channel::{HttpHandler, IntoHttpHandler};
|
||||||
use pipeline::Pipeline;
|
use pipeline::Pipeline;
|
||||||
use middlewares::Middleware;
|
use middlewares::Middleware;
|
||||||
|
|
||||||
pub struct Router<S>(Rc<RouteRecognizer<Resource<S>>>);
|
|
||||||
|
|
||||||
impl<S: 'static> Router<S> {
|
|
||||||
pub fn new(prefix: String, map: HashMap<String, Resource<S>>) -> Router<S>
|
|
||||||
{
|
|
||||||
let mut resources = Vec::new();
|
|
||||||
for (path, resource) in map {
|
|
||||||
resources.push((path, resource.get_name(), resource))
|
|
||||||
}
|
|
||||||
|
|
||||||
Router(Rc::new(RouteRecognizer::new(prefix, resources)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_route(&self, path: &str) -> bool {
|
|
||||||
self.0.recognize(path).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resource_path<'a, U>(&self, prefix: &str, name: &str, elements: U)
|
|
||||||
-> Result<String, UriGenerationError>
|
|
||||||
where U: IntoIterator<Item=&'a str>
|
|
||||||
{
|
|
||||||
if let Some(pattern) = self.0.get_pattern(name) {
|
|
||||||
let mut iter = elements.into_iter();
|
|
||||||
let mut vec = vec![prefix];
|
|
||||||
for el in pattern.elements() {
|
|
||||||
match *el {
|
|
||||||
PatternElement::Str(ref s) => vec.push(s),
|
|
||||||
PatternElement::Var(_) => {
|
|
||||||
if let Some(val) = iter.next() {
|
|
||||||
vec.push(val)
|
|
||||||
} else {
|
|
||||||
return Err(UriGenerationError::NotEnoughElements)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let s = vec.join("/").to_owned();
|
|
||||||
Ok(s)
|
|
||||||
} else {
|
|
||||||
Err(UriGenerationError::ResourceNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application
|
/// Application
|
||||||
pub struct HttpApplication<S> {
|
pub struct HttpApplication<S> {
|
||||||
state: Rc<S>,
|
state: Rc<S>,
|
||||||
|
@ -66,12 +22,12 @@ pub struct HttpApplication<S> {
|
||||||
impl<S: 'static> HttpApplication<S> {
|
impl<S: 'static> HttpApplication<S> {
|
||||||
|
|
||||||
fn run(&self, req: HttpRequest) -> Reply {
|
fn run(&self, req: HttpRequest) -> Reply {
|
||||||
let mut req = req.with_state(Rc::clone(&self.state));
|
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone());
|
||||||
|
|
||||||
if let Some((params, h)) = self.router.0.recognize(req.path()) {
|
if let Some((params, h)) = self.router.query(req.path()) {
|
||||||
if let Some(params) = params {
|
if let Some(params) = params {
|
||||||
req.set_match_info(params);
|
req.set_match_info(params);
|
||||||
req.set_prefix(self.router.0.prefix());
|
req.set_prefix(self.router.prefix().len());
|
||||||
}
|
}
|
||||||
h.handle(req)
|
h.handle(req)
|
||||||
} else {
|
} else {
|
||||||
|
@ -214,14 +170,10 @@ impl<S> Application<S> where S: 'static {
|
||||||
/// Finish application configuration and create HttpHandler object
|
/// Finish application configuration and create HttpHandler object
|
||||||
pub fn finish(&mut self) -> HttpApplication<S> {
|
pub fn finish(&mut self) -> HttpApplication<S> {
|
||||||
let parts = self.parts.take().expect("Use after finish");
|
let parts = self.parts.take().expect("Use after finish");
|
||||||
let prefix = if parts.prefix.ends_with('/') {
|
let prefix = parts.prefix.trim().trim_right_matches('/');
|
||||||
parts.prefix
|
|
||||||
} else {
|
|
||||||
parts.prefix + "/"
|
|
||||||
};
|
|
||||||
HttpApplication {
|
HttpApplication {
|
||||||
state: Rc::new(parts.state),
|
state: Rc::new(parts.state),
|
||||||
prefix: prefix.clone(),
|
prefix: prefix.to_owned(),
|
||||||
default: parts.default,
|
default: parts.default,
|
||||||
router: Router::new(prefix, parts.resources),
|
router: Router::new(prefix, parts.resources),
|
||||||
middlewares: Rc::new(parts.middlewares),
|
middlewares: Rc::new(parts.middlewares),
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
// dev specific
|
// dev specific
|
||||||
pub use info::ConnectionInfo;
|
pub use info::ConnectionInfo;
|
||||||
pub use handler::Handler;
|
pub use handler::Handler;
|
||||||
|
pub use router::Router;
|
||||||
pub use pipeline::Pipeline;
|
pub use pipeline::Pipeline;
|
||||||
pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler};
|
pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler};
|
||||||
pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement};
|
pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement};
|
||||||
|
|
16
src/error.rs
16
src/error.rs
|
@ -15,6 +15,7 @@ use http::{header, StatusCode, Error as HttpError};
|
||||||
use http::uri::InvalidUriBytes;
|
use http::uri::InvalidUriBytes;
|
||||||
use http_range::HttpRangeParseError;
|
use http_range::HttpRangeParseError;
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
|
use url::ParseError as UrlParseError;
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use cookie::{ParseError as CookieParseError};
|
pub use cookie::{ParseError as CookieParseError};
|
||||||
|
@ -405,11 +406,24 @@ impl ResponseError for UriSegmentError {
|
||||||
|
|
||||||
/// Errors which can occur when attempting to generate resource uri.
|
/// Errors which can occur when attempting to generate resource uri.
|
||||||
#[derive(Fail, Debug, PartialEq)]
|
#[derive(Fail, Debug, PartialEq)]
|
||||||
pub enum UriGenerationError {
|
pub enum UrlGenerationError {
|
||||||
#[fail(display="Resource not found")]
|
#[fail(display="Resource not found")]
|
||||||
ResourceNotFound,
|
ResourceNotFound,
|
||||||
#[fail(display="Not all path pattern covered")]
|
#[fail(display="Not all path pattern covered")]
|
||||||
NotEnoughElements,
|
NotEnoughElements,
|
||||||
|
#[fail(display="Router is not available")]
|
||||||
|
RouterNotAvailable,
|
||||||
|
#[fail(display="{}", _0)]
|
||||||
|
ParseError(#[cause] UrlParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `InternalServerError` for `UrlGeneratorError`
|
||||||
|
impl ResponseError for UrlGenerationError {}
|
||||||
|
|
||||||
|
impl From<UrlParseError> for UrlGenerationError {
|
||||||
|
fn from(err: UrlParseError) -> Self {
|
||||||
|
UrlGenerationError::ParseError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -5,15 +5,16 @@ use std::net::SocketAddr;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures::{Async, Future, Stream, Poll};
|
use futures::{Async, Future, Stream, Poll};
|
||||||
use url::form_urlencoded;
|
use url::{Url, form_urlencoded};
|
||||||
use http::{header, Uri, Method, Version, HeaderMap, Extensions};
|
use http::{header, Uri, Method, Version, HeaderMap, Extensions};
|
||||||
|
|
||||||
use {Cookie, HttpRange};
|
use {Cookie, HttpRange};
|
||||||
use info::ConnectionInfo;
|
use info::ConnectionInfo;
|
||||||
|
use router::Router;
|
||||||
use recognizer::Params;
|
use recognizer::Params;
|
||||||
use payload::Payload;
|
use payload::Payload;
|
||||||
use multipart::Multipart;
|
use multipart::Multipart;
|
||||||
use error::{ParseError, PayloadError,
|
use error::{ParseError, PayloadError, UrlGenerationError,
|
||||||
MultipartError, CookieParseError, HttpRangeError, UrlencodedError};
|
MultipartError, CookieParseError, HttpRangeError, UrlencodedError};
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ struct HttpMessage {
|
||||||
addr: Option<SocketAddr>,
|
addr: Option<SocketAddr>,
|
||||||
payload: Payload,
|
payload: Payload,
|
||||||
info: Option<ConnectionInfo<'static>>,
|
info: Option<ConnectionInfo<'static>>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HttpMessage {
|
impl Default for HttpMessage {
|
||||||
|
@ -53,7 +55,7 @@ impl Default for HttpMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An HTTP Request
|
/// An HTTP Request
|
||||||
pub struct HttpRequest<S=()>(Rc<HttpMessage>, Rc<S>);
|
pub struct HttpRequest<S=()>(Rc<HttpMessage>, Rc<S>, Option<Router<S>>);
|
||||||
|
|
||||||
impl HttpRequest<()> {
|
impl HttpRequest<()> {
|
||||||
/// Construct a new Request.
|
/// Construct a new Request.
|
||||||
|
@ -76,13 +78,14 @@ impl HttpRequest<()> {
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
info: None,
|
info: None,
|
||||||
}),
|
}),
|
||||||
Rc::new(())
|
Rc::new(()),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct new http request with state.
|
/// Construct new http request with state.
|
||||||
pub fn with_state<S>(self, state: Rc<S>) -> HttpRequest<S> {
|
pub fn with_state<S>(self, state: Rc<S>, router: Router<S>) -> HttpRequest<S> {
|
||||||
HttpRequest(self.0, state)
|
HttpRequest(self.0, state, Some(router))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,10 +93,11 @@ impl<S> HttpRequest<S> {
|
||||||
|
|
||||||
/// Construct new http request without state.
|
/// Construct new http request without state.
|
||||||
pub fn clone_without_state(&self) -> HttpRequest {
|
pub fn clone_without_state(&self) -> HttpRequest {
|
||||||
HttpRequest(Rc::clone(&self.0), Rc::new(()))
|
HttpRequest(Rc::clone(&self.0), Rc::new(()), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get mutable reference for inner message
|
/// get mutable reference for inner message
|
||||||
|
#[inline]
|
||||||
fn as_mut(&mut self) -> &mut HttpMessage {
|
fn as_mut(&mut self) -> &mut HttpMessage {
|
||||||
let r: &HttpMessage = self.0.as_ref();
|
let r: &HttpMessage = self.0.as_ref();
|
||||||
#[allow(mutable_transmutes)]
|
#[allow(mutable_transmutes)]
|
||||||
|
@ -101,6 +105,7 @@ impl<S> HttpRequest<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared application state
|
/// Shared application state
|
||||||
|
#[inline]
|
||||||
pub fn state(&self) -> &S {
|
pub fn state(&self) -> &S {
|
||||||
&self.1
|
&self.1
|
||||||
}
|
}
|
||||||
|
@ -111,6 +116,7 @@ impl<S> HttpRequest<S> {
|
||||||
&mut self.as_mut().extensions
|
&mut self.as_mut().extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn set_prefix(&mut self, idx: usize) {
|
pub(crate) fn set_prefix(&mut self, idx: usize) {
|
||||||
self.as_mut().prefix = idx;
|
self.as_mut().prefix = idx;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +168,6 @@ impl<S> HttpRequest<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load *ConnectionInfo* for currect request.
|
/// Load *ConnectionInfo* for currect request.
|
||||||
#[inline]
|
|
||||||
pub fn load_connection_info(&mut self) -> &ConnectionInfo {
|
pub fn load_connection_info(&mut self) -> &ConnectionInfo {
|
||||||
if self.0.info.is_none() {
|
if self.0.info.is_none() {
|
||||||
let info: ConnectionInfo<'static> = unsafe{
|
let info: ConnectionInfo<'static> = unsafe{
|
||||||
|
@ -172,17 +177,35 @@ impl<S> HttpRequest<S> {
|
||||||
self.0.info.as_ref().unwrap()
|
self.0.info.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn url_for<U, I>(&mut self, name: &str, elements: U) -> Result<Url, UrlGenerationError>
|
||||||
|
where U: IntoIterator<Item=I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
if self.router().is_none() {
|
||||||
|
Err(UrlGenerationError::RouterNotAvailable)
|
||||||
|
} else {
|
||||||
|
let path = self.router().unwrap().resource_path(name, elements)?;
|
||||||
|
let conn = self.load_connection_info();
|
||||||
|
Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn router(&self) -> Option<&Router<S>> {
|
||||||
|
self.2.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn peer_addr(&self) -> Option<&SocketAddr> {
|
pub fn peer_addr(&self) -> Option<&SocketAddr> {
|
||||||
self.0.addr.as_ref()
|
self.0.addr.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
|
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
|
||||||
self.as_mut().addr = addr
|
self.as_mut().addr = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new iterator that yields pairs of `Cow<str>` for query parameters
|
/// Return a new iterator that yields pairs of `Cow<str>` for query parameters
|
||||||
#[inline]
|
|
||||||
pub fn query(&self) -> HashMap<String, String> {
|
pub fn query(&self) -> HashMap<String, String> {
|
||||||
let mut q: HashMap<String, String> = HashMap::new();
|
let mut q: HashMap<String, String> = HashMap::new();
|
||||||
if let Some(query) = self.0.uri.query().as_ref() {
|
if let Some(query) = self.0.uri.query().as_ref() {
|
||||||
|
@ -206,6 +229,7 @@ impl<S> HttpRequest<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return request cookies.
|
/// Return request cookies.
|
||||||
|
#[inline]
|
||||||
pub fn cookies(&self) -> &Vec<Cookie<'static>> {
|
pub fn cookies(&self) -> &Vec<Cookie<'static>> {
|
||||||
&self.0.cookies
|
&self.0.cookies
|
||||||
}
|
}
|
||||||
|
@ -245,6 +269,7 @@ impl<S> HttpRequest<S> {
|
||||||
pub fn match_info(&self) -> &Params { &self.0.params }
|
pub fn match_info(&self) -> &Params { &self.0.params }
|
||||||
|
|
||||||
/// Set request Params.
|
/// Set request Params.
|
||||||
|
#[inline]
|
||||||
pub fn set_match_info(&mut self, params: Params) {
|
pub fn set_match_info(&mut self, params: Params) {
|
||||||
self.as_mut().params = params;
|
self.as_mut().params = params;
|
||||||
}
|
}
|
||||||
|
@ -324,6 +349,7 @@ impl<S> HttpRequest<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return payload
|
/// Return payload
|
||||||
|
#[inline]
|
||||||
pub fn take_payload(&mut self) -> Payload {
|
pub fn take_payload(&mut self) -> Payload {
|
||||||
mem::replace(&mut self.as_mut().payload, Payload::empty())
|
mem::replace(&mut self.as_mut().payload, Payload::empty())
|
||||||
}
|
}
|
||||||
|
@ -387,13 +413,13 @@ impl Default for HttpRequest<()> {
|
||||||
|
|
||||||
/// Construct default request
|
/// Construct default request
|
||||||
fn default() -> HttpRequest {
|
fn default() -> HttpRequest {
|
||||||
HttpRequest(Rc::new(HttpMessage::default()), Rc::new(()))
|
HttpRequest(Rc::new(HttpMessage::default()), Rc::new(()), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Clone for HttpRequest<S> {
|
impl<S> Clone for HttpRequest<S> {
|
||||||
fn clone(&self) -> HttpRequest<S> {
|
fn clone(&self) -> HttpRequest<S> {
|
||||||
HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1))
|
HttpRequest(Rc::clone(&self.0), Rc::clone(&self.1), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,9 +480,10 @@ impl Future for UrlEncoded {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use http::Uri;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use payload::Payload;
|
use payload::Payload;
|
||||||
use http::Uri;
|
use resource::Resource;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_urlencoded_error() {
|
fn test_urlencoded_error() {
|
||||||
|
@ -502,4 +529,32 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType);
|
assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_url_for() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(header::HOST,
|
||||||
|
header::HeaderValue::from_static("www.rust-lang.org"));
|
||||||
|
let mut req = HttpRequest::new(
|
||||||
|
Method::GET, Uri::from_str("/").unwrap(),
|
||||||
|
Version::HTTP_11, headers, Payload::empty());
|
||||||
|
|
||||||
|
let mut resource = Resource::default();
|
||||||
|
resource.name("index");
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("/user/{name}.{ext}".to_owned(), resource);
|
||||||
|
let router = Router::new("", map);
|
||||||
|
|
||||||
|
assert_eq!(req.url_for("unknown", &["test"]),
|
||||||
|
Err(UrlGenerationError::RouterNotAvailable));
|
||||||
|
|
||||||
|
let mut req = req.with_state(Rc::new(()), router);
|
||||||
|
|
||||||
|
assert_eq!(req.url_for("unknown", &["test"]),
|
||||||
|
Err(UrlGenerationError::ResourceNotFound));
|
||||||
|
assert_eq!(req.url_for("index", &["test"]),
|
||||||
|
Err(UrlGenerationError::NotEnoughElements));
|
||||||
|
let url = req.url_for("index", &["test", "html"]);
|
||||||
|
assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/user/test.html");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ mod httpresponse;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod info;
|
mod info;
|
||||||
mod route;
|
mod route;
|
||||||
|
mod router;
|
||||||
mod resource;
|
mod resource;
|
||||||
mod recognizer;
|
mod recognizer;
|
||||||
mod handler;
|
mod handler;
|
||||||
|
|
|
@ -204,16 +204,17 @@ FROM_STR!(std::net::SocketAddrV6);
|
||||||
|
|
||||||
pub struct RouteRecognizer<T> {
|
pub struct RouteRecognizer<T> {
|
||||||
re: RegexSet,
|
re: RegexSet,
|
||||||
prefix: usize,
|
prefix: String,
|
||||||
routes: Vec<(Pattern, T)>,
|
routes: Vec<(Pattern, T)>,
|
||||||
patterns: HashMap<String, Pattern>,
|
patterns: HashMap<String, Pattern>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> RouteRecognizer<T> {
|
impl<T> RouteRecognizer<T> {
|
||||||
|
|
||||||
pub fn new<P: Into<String>, U, K>(prefix: P, routes: U) -> Self
|
pub fn new<P, U, K>(prefix: P, routes: U) -> Self
|
||||||
where U: IntoIterator<Item=(K, Option<String>, T)>,
|
where U: IntoIterator<Item=(K, Option<String>, T)>,
|
||||||
K: Into<String>,
|
K: Into<String>,
|
||||||
|
P: Into<String>,
|
||||||
{
|
{
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
let mut handlers = Vec::new();
|
let mut handlers = Vec::new();
|
||||||
|
@ -231,7 +232,7 @@ impl<T> RouteRecognizer<T> {
|
||||||
|
|
||||||
RouteRecognizer {
|
RouteRecognizer {
|
||||||
re: regset.unwrap(),
|
re: regset.unwrap(),
|
||||||
prefix: prefix.into().len() - 1,
|
prefix: prefix.into(),
|
||||||
routes: handlers,
|
routes: handlers,
|
||||||
patterns: patterns,
|
patterns: patterns,
|
||||||
}
|
}
|
||||||
|
@ -242,29 +243,20 @@ impl<T> RouteRecognizer<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Length of the prefix
|
/// Length of the prefix
|
||||||
pub fn prefix(&self) -> usize {
|
pub fn prefix(&self) -> &str {
|
||||||
self.prefix
|
&self.prefix
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_prefix<P: Into<String>>(&mut self, prefix: P) {
|
|
||||||
let p = prefix.into();
|
|
||||||
if p.ends_with('/') {
|
|
||||||
self.prefix = p.len() - 1;
|
|
||||||
} else {
|
|
||||||
self.prefix = p.len();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize(&self, path: &str) -> Option<(Option<Params>, &T)> {
|
pub fn recognize(&self, path: &str) -> Option<(Option<Params>, &T)> {
|
||||||
let p = &path[self.prefix..];
|
let p = &path[self.prefix.len()..];
|
||||||
if p.is_empty() {
|
if p.is_empty() {
|
||||||
if let Some(idx) = self.re.matches("/").into_iter().next() {
|
if let Some(idx) = self.re.matches("/").into_iter().next() {
|
||||||
let (ref pattern, ref route) = self.routes[idx];
|
let (ref pattern, ref route) = self.routes[idx];
|
||||||
return Some((pattern.match_info(&path[self.prefix..]), route))
|
return Some((pattern.match_info(&path[self.prefix.len()..]), route))
|
||||||
}
|
}
|
||||||
} else if let Some(idx) = self.re.matches(p).into_iter().next() {
|
} else if let Some(idx) = self.re.matches(p).into_iter().next() {
|
||||||
let (ref pattern, ref route) = self.routes[idx];
|
let (ref pattern, ref route) = self.routes[idx];
|
||||||
return Some((pattern.match_info(&path[self.prefix..]), route))
|
return Some((pattern.match_info(&path[self.prefix.len()..]), route))
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -400,7 +392,7 @@ mod tests {
|
||||||
("/v{val}/{val2}/index.html", None, 4),
|
("/v{val}/{val2}/index.html", None, 4),
|
||||||
("/v/{tail:.*}", None, 5),
|
("/v/{tail:.*}", None, 5),
|
||||||
];
|
];
|
||||||
let rec = RouteRecognizer::new("/", routes);
|
let rec = RouteRecognizer::new("", routes);
|
||||||
|
|
||||||
let (params, val) = rec.recognize("/name").unwrap();
|
let (params, val) = rec.recognize("/name").unwrap();
|
||||||
assert_eq!(*val, 1);
|
assert_eq!(*val, 1);
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl<S> Default for Resource<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Resource<S> where S: 'static {
|
impl<S> Resource<S> {
|
||||||
|
|
||||||
pub(crate) fn default_not_found() -> Self {
|
pub(crate) fn default_not_found() -> Self {
|
||||||
Resource {
|
Resource {
|
||||||
|
@ -62,6 +62,9 @@ impl<S> Resource<S> where S: 'static {
|
||||||
pub(crate) fn get_name(&self) -> Option<String> {
|
pub(crate) fn get_name(&self) -> Option<String> {
|
||||||
if self.name.is_empty() { None } else { Some(self.name.clone()) }
|
if self.name.is_empty() { None } else { Some(self.name.clone()) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> Resource<S> {
|
||||||
|
|
||||||
/// Register a new route and return mutable reference to *Route* object.
|
/// Register a new route and return mutable reference to *Route* object.
|
||||||
/// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
|
/// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
|
||||||
|
|
73
src/router.rs
Normal file
73
src/router.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use error::UrlGenerationError;
|
||||||
|
use resource::Resource;
|
||||||
|
use recognizer::{Params, RouteRecognizer, PatternElement};
|
||||||
|
|
||||||
|
|
||||||
|
/// Interface for application router.
|
||||||
|
pub struct Router<S>(Rc<RouteRecognizer<Resource<S>>>);
|
||||||
|
|
||||||
|
impl<S> Router<S> {
|
||||||
|
pub(crate) fn new(prefix: &str, map: HashMap<String, Resource<S>>) -> Router<S>
|
||||||
|
{
|
||||||
|
let prefix = prefix.trim().trim_right_matches('/').to_owned();
|
||||||
|
let mut resources = Vec::new();
|
||||||
|
for (path, resource) in map {
|
||||||
|
resources.push((path, resource.get_name(), resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
Router(Rc::new(RouteRecognizer::new(prefix, resources)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Router prefix
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn prefix(&self) -> &str {
|
||||||
|
self.0.prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query for matched resource
|
||||||
|
pub fn query(&self, path: &str) -> Option<(Option<Params>, &Resource<S>)> {
|
||||||
|
self.0.recognize(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if application contains matching route.
|
||||||
|
pub fn has_route(&self, path: &str) -> bool {
|
||||||
|
self.0.recognize(path).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build named resource path
|
||||||
|
pub fn resource_path<U, I>(&self, name: &str, elements: U)
|
||||||
|
-> Result<String, UrlGenerationError>
|
||||||
|
where U: IntoIterator<Item=I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
if let Some(pattern) = self.0.get_pattern(name) {
|
||||||
|
let mut path = String::from(self.prefix());
|
||||||
|
path.push('/');
|
||||||
|
let mut iter = elements.into_iter();
|
||||||
|
for el in pattern.elements() {
|
||||||
|
match *el {
|
||||||
|
PatternElement::Str(ref s) => path.push_str(s),
|
||||||
|
PatternElement::Var(_) => {
|
||||||
|
if let Some(val) = iter.next() {
|
||||||
|
path.push_str(val.as_ref())
|
||||||
|
} else {
|
||||||
|
return Err(UrlGenerationError::NotEnoughElements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(path)
|
||||||
|
} else {
|
||||||
|
Err(UrlGenerationError::ResourceNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> Clone for Router<S> {
|
||||||
|
fn clone(&self) -> Router<S> {
|
||||||
|
Router(Rc::clone(&self.0))
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,7 +92,7 @@ fn test_request_match_info() {
|
||||||
let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(),
|
let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(),
|
||||||
Version::HTTP_11, HeaderMap::new(), Payload::empty());
|
Version::HTTP_11, HeaderMap::new(), Payload::empty());
|
||||||
|
|
||||||
let rec = RouteRecognizer::new("/".to_owned(), vec![("/{key}/".to_owned(), None, 1)]);
|
let rec = RouteRecognizer::new("", vec![("/{key}/".to_owned(), None, 1)]);
|
||||||
let (params, _) = rec.recognize(req.path()).unwrap();
|
let (params, _) = rec.recognize(req.path()).unwrap();
|
||||||
let params = params.unwrap();
|
let params = params.unwrap();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue