diff --git a/Cargo.toml b/Cargo.toml
index f9e2266e..5e2027c5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -72,11 +72,13 @@ actix-utils = { git = "https://github.com/actix/actix-net.git" }
actix-http = { git = "https://github.com/actix/actix-http.git" }
actix-router = { git = "https://github.com/actix/actix-net.git" }
actix-server = { git = "https://github.com/actix/actix-net.git" }
+#actix-router = { path="../actix-net/router" }
bytes = "0.4"
derive_more = "0.14"
encoding = "0.2"
futures = "0.1"
+hashbrown = "0.1.8"
log = "0.4"
lazy_static = "1.2"
mime = "0.3"
@@ -89,6 +91,7 @@ serde_json = "1.0"
serde_urlencoded = "^0.5.3"
threadpool = "1.7"
time = "0.1"
+url = { version="1.7", features=["query_encoding"] }
# middlewares
# actix-session = { path="session", optional = true }
diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs
index 5dd98dcc..17efdd81 100644
--- a/actix-files/src/lib.rs
+++ b/actix-files/src/lib.rs
@@ -1090,4 +1090,35 @@ mod tests {
// assert_eq!(response.status(), StatusCode::OK);
// }
+ #[test]
+ fn test_path_buf() {
+ assert_eq!(
+ PathBuf::from_param("/test/.tt"),
+ Err(UriSegmentError::BadStart('.'))
+ );
+ assert_eq!(
+ PathBuf::from_param("/test/*tt"),
+ Err(UriSegmentError::BadStart('*'))
+ );
+ assert_eq!(
+ PathBuf::from_param("/test/tt:"),
+ Err(UriSegmentError::BadEnd(':'))
+ );
+ assert_eq!(
+ PathBuf::from_param("/test/tt<"),
+ Err(UriSegmentError::BadEnd('<'))
+ );
+ assert_eq!(
+ PathBuf::from_param("/test/tt>"),
+ Err(UriSegmentError::BadEnd('>'))
+ );
+ assert_eq!(
+ PathBuf::from_param("/seg1/seg2/"),
+ Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))
+ );
+ assert_eq!(
+ PathBuf::from_param("/seg1/../seg2/"),
+ Ok(PathBuf::from_iter(vec!["seg2"]))
+ );
+ }
}
diff --git a/src/app.rs b/src/app.rs
index c1c019a3..b4f6e535 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -16,6 +16,7 @@ use futures::{Async, Future, IntoFuture, Poll};
use crate::config::AppConfig;
use crate::guard::Guard;
use crate::resource::Resource;
+use crate::rmap::ResourceMap;
use crate::route::Route;
use crate::service::{
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
@@ -449,19 +450,29 @@ where
.into_iter()
.for_each(|mut srv| srv.register(&mut config));
- // set factory
+ let mut rmap = ResourceMap::new(ResourceDef::new(""));
+
+ // complete pipeline creation
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
default,
services: Rc::new(
config
.into_services()
.into_iter()
- .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards)))
+ .map(|(mut rdef, srv, guards, nested)| {
+ rmap.add(&mut rdef, nested);
+ (rdef, srv, RefCell::new(guards))
+ })
.collect(),
),
});
+ // complete ResourceMap tree creation
+ let rmap = Rc::new(rmap);
+ rmap.finish(rmap.clone());
+
AppInit {
+ rmap,
chain: self.chain,
state: self.state,
extensions: Rc::new(RefCell::new(Rc::new(self.extensions))),
@@ -561,8 +572,7 @@ impl
Future for AppRoutingFactoryResponse
{
.fold(Router::build(), |mut router, item| {
match item {
CreateAppRoutingItem::Service(path, guards, service) => {
- router.rdef(path, service);
- router.set_user_data(guards);
+ router.rdef(path, service).2 = guards;
}
CreateAppRoutingItem::Future(_, _, _) => unreachable!(),
}
@@ -683,6 +693,7 @@ where
C: NewService>,
{
chain: C,
+ rmap: Rc,
state: Vec>,
extensions: Rc>>,
}
@@ -702,6 +713,7 @@ where
chain: self.chain.new_service(&()),
state: self.state.iter().map(|s| s.construct()).collect(),
extensions: self.extensions.clone(),
+ rmap: self.rmap.clone(),
}
}
}
@@ -712,6 +724,7 @@ where
C: NewService, InitError = ()>,
{
chain: C::Future,
+ rmap: Rc,
state: Vec>,
extensions: Rc>>,
}
@@ -744,6 +757,7 @@ where
Ok(Async::Ready(AppInitService {
chain,
+ rmap: self.rmap.clone(),
extensions: self.extensions.borrow().clone(),
}))
}
@@ -755,6 +769,7 @@ where
C: Service>,
{
chain: C,
+ rmap: Rc,
extensions: Rc,
}
@@ -774,6 +789,7 @@ where
let req = ServiceRequest::new(
Path::new(Url::new(req.uri().clone())),
req,
+ self.rmap.clone(),
self.extensions.clone(),
);
self.chain.call(req)
diff --git a/src/config.rs b/src/config.rs
index 483b0a50..4afd213c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -5,6 +5,7 @@ use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};
use crate::guard::Guard;
+use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse};
type Guards = Vec>;
@@ -18,7 +19,12 @@ pub struct AppConfig {
host: String,
root: bool,
default: Rc>,
- services: Vec<(ResourceDef, HttpNewService, Option)>,
+ services: Vec<(
+ ResourceDef,
+ HttpNewService,
+ Option,
+ Option>,
+ )>,
}
impl AppConfig {
@@ -46,7 +52,12 @@ impl AppConfig {
pub(crate) fn into_services(
self,
- ) -> Vec<(ResourceDef, HttpNewService
, Option)> {
+ ) -> Vec<(
+ ResourceDef,
+ HttpNewService,
+ Option,
+ Option>,
+ )> {
self.services
}
@@ -85,6 +96,7 @@ impl AppConfig {
rdef: ResourceDef,
guards: Option>>,
service: F,
+ nested: Option>,
) where
F: IntoNewService>,
S: NewService<
@@ -98,6 +110,7 @@ impl AppConfig {
rdef,
boxed::new_service(service.into_new_service()),
guards,
+ nested,
));
}
}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 00000000..d1c0d3ca
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,20 @@
+pub use actix_http::error::*;
+use derive_more::{Display, From};
+use url::ParseError as UrlParseError;
+
+/// Errors which can occur when attempting to generate resource uri.
+#[derive(Debug, PartialEq, Display, From)]
+pub enum UrlGenerationError {
+ /// Resource not found
+ #[display(fmt = "Resource not found")]
+ ResourceNotFound,
+ /// Not all path pattern covered
+ #[display(fmt = "Not all path pattern covered")]
+ NotEnoughElements,
+ /// URL parse error
+ #[display(fmt = "{}", _0)]
+ ParseError(UrlParseError),
+}
+
+/// `InternalServerError` for `UrlGeneratorError`
+impl ResponseError for UrlGenerationError {}
diff --git a/src/lib.rs b/src/lib.rs
index a61387e8..94bf1ba7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,11 +6,13 @@ mod handler;
// mod info;
pub mod blocking;
mod config;
+pub mod error;
pub mod guard;
pub mod middleware;
mod request;
mod resource;
mod responder;
+mod rmap;
mod route;
mod scope;
mod server;
@@ -27,7 +29,7 @@ pub use actix_web_codegen::*;
// re-export for convenience
pub use actix_http::Response as HttpResponse;
-pub use actix_http::{error, http, Error, HttpMessage, ResponseError, Result};
+pub use actix_http::{http, Error, HttpMessage, ResponseError, Result};
pub use crate::app::App;
pub use crate::extract::FromRequest;
diff --git a/src/request.rs b/src/request.rs
index 1c86cac3..6655f1ba 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -7,7 +7,9 @@ use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
use actix_router::{Path, Url};
+use crate::error::UrlGenerationError;
use crate::extract::FromRequest;
+use crate::rmap::ResourceMap;
use crate::service::ServiceFromRequest;
#[derive(Clone)]
@@ -15,6 +17,7 @@ use crate::service::ServiceFromRequest;
pub struct HttpRequest {
pub(crate) head: Message,
pub(crate) path: Path,
+ rmap: Rc,
extensions: Rc,
}
@@ -23,11 +26,13 @@ impl HttpRequest {
pub(crate) fn new(
head: Message,
path: Path,
+ rmap: Rc,
extensions: Rc,
) -> HttpRequest {
HttpRequest {
head,
path,
+ rmap,
extensions,
}
}
@@ -93,6 +98,47 @@ impl HttpRequest {
&self.extensions
}
+ /// Generate url for named resource
+ ///
+ /// ```rust
+ /// # extern crate actix_web;
+ /// # use actix_web::{App, HttpRequest, HttpResponse, http};
+ /// #
+ /// fn index(req: HttpRequest) -> HttpResponse {
+ /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
+ /// HttpResponse::Ok().into()
+ /// }
+ ///
+ /// fn main() {
+ /// let app = App::new()
+ /// .resource("/test/{one}/{two}/{three}", |r| {
+ /// r.name("foo"); // <- set resource name, then it could be used in `url_for`
+ /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
+ /// })
+ /// .finish();
+ /// }
+ /// ```
+ pub fn url_for(
+ &self,
+ name: &str,
+ elements: U,
+ ) -> Result
+ where
+ U: IntoIterator- ,
+ I: AsRef,
+ {
+ self.rmap.url_for(&self, name, elements)
+ }
+
+ /// Generate url for named resource
+ ///
+ /// This method is similar to `HttpRequest::url_for()` but it can be used
+ /// for urls that do not contain variable parts.
+ pub fn url_for_static(&self, name: &str) -> Result {
+ const NO_PARAMS: [&str; 0] = [];
+ self.url_for(name, &NO_PARAMS)
+ }
+
// /// Get *ConnectionInfo* for the correct request.
// #[inline]
// pub fn connection_info(&self) -> Ref {
diff --git a/src/resource.rs b/src/resource.rs
index 13afff70..a1177ca0 100644
--- a/src/resource.rs
+++ b/src/resource.rs
@@ -288,7 +288,7 @@ where
} else {
ResourceDef::new(&self.rdef)
};
- config.register_service(rdef, guards, self)
+ config.register_service(rdef, guards, self, None)
}
}
diff --git a/src/rmap.rs b/src/rmap.rs
new file mode 100644
index 00000000..4922084b
--- /dev/null
+++ b/src/rmap.rs
@@ -0,0 +1,188 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use actix_router::ResourceDef;
+use hashbrown::HashMap;
+use url::Url;
+
+use crate::error::UrlGenerationError;
+use crate::request::HttpRequest;
+
+#[derive(Clone, Debug)]
+pub struct ResourceMap {
+ root: ResourceDef,
+ parent: RefCell