mirror of https://github.com/fafhrd91/actix-web
feat(web): implement `HttpRequest::url_for_iter`/`url_for_map` (#3895)
This commit is contained in:
parent
cf2b097de6
commit
69edde9662
|
|
@ -3,6 +3,9 @@
|
|||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.88.
|
||||
- Add `HttpRequest::url_for_map` and `HttpRequest::url_for_iter` methods for named URL parameters. [#3895]
|
||||
|
||||
[#3895]: https://github.com/actix/actix-web/pull/3895
|
||||
|
||||
## 4.12.1
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
fmt, net,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
hash::{BuildHasher, Hash},
|
||||
net,
|
||||
rc::Rc,
|
||||
str,
|
||||
};
|
||||
|
|
@ -242,6 +245,76 @@ impl HttpRequest {
|
|||
self.resource_map().url_for(self, name, elements)
|
||||
}
|
||||
|
||||
/// Generates URL for a named resource using a map of dynamic segment values.
|
||||
///
|
||||
/// This substitutes URL parameters by name from `elements`, including parameters from parent
|
||||
/// scopes.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::collections::HashMap;
|
||||
/// # use actix_web::{web, App, HttpRequest, HttpResponse};
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// let mut params = HashMap::new();
|
||||
/// params.insert("one", "1");
|
||||
/// params.insert("two", "2");
|
||||
/// let url = req.url_for_map("foo", ¶ms); // <- generate URL for "foo" resource
|
||||
/// HttpResponse::Ok().into()
|
||||
/// }
|
||||
///
|
||||
/// let app = App::new()
|
||||
/// .service(web::resource("/test/{one}/{two}")
|
||||
/// .name("foo") // <- set resource name so it can be used in `url_for_map`
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn url_for_map<K, V, S>(
|
||||
&self,
|
||||
name: &str,
|
||||
elements: &HashMap<K, V, S>,
|
||||
) -> Result<url::Url, UrlGenerationError>
|
||||
where
|
||||
K: std::borrow::Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
S: BuildHasher,
|
||||
{
|
||||
self.resource_map().url_for_map(self, name, elements)
|
||||
}
|
||||
|
||||
/// Generates URL for a named resource using an iterator of key-value pairs.
|
||||
///
|
||||
/// This is a convenience wrapper around [`HttpRequest::url_for_map`].
|
||||
///
|
||||
/// Note: passing a borrowed map (e.g. `&HashMap<String, String>`) directly does not satisfy the
|
||||
/// trait bounds because the iterator yields `(&String, &String)`. Prefer `url_for_map` for
|
||||
/// borrowed maps, or map entries to `&str`:
|
||||
///
|
||||
/// ```
|
||||
/// # use std::collections::HashMap;
|
||||
/// # use actix_web::{web, App, HttpRequest, HttpResponse};
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// let mut params = HashMap::new();
|
||||
/// params.insert("one".to_string(), "1".to_string());
|
||||
/// params.insert("two".to_string(), "2".to_string());
|
||||
///
|
||||
/// let iter = params.iter().map(|(k, v)| (k.as_str(), v.as_str()));
|
||||
/// let url = req.url_for_iter("foo", iter);
|
||||
/// HttpResponse::Ok().into()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn url_for_iter<K, V, I>(
|
||||
&self,
|
||||
name: &str,
|
||||
elements: I,
|
||||
) -> Result<url::Url, UrlGenerationError>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: std::borrow::Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
self.resource_map().url_for_iter(self, name, elements)
|
||||
}
|
||||
|
||||
/// Generate URL for named resource
|
||||
///
|
||||
/// This method is similar to `HttpRequest::url_for()` but it can be used
|
||||
|
|
@ -550,6 +623,8 @@ impl HttpRequestPool {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
|
|
@ -638,6 +713,59 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_map() {
|
||||
let mut res = ResourceDef::new("/user/{name}.{ext}");
|
||||
res.set_name("index");
|
||||
|
||||
let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
|
||||
rmap.add(&mut res, None);
|
||||
|
||||
let req = TestRequest::default()
|
||||
.insert_header((header::HOST, "www.actix.rs"))
|
||||
.rmap(rmap)
|
||||
.to_http_request();
|
||||
|
||||
let mut params = HashMap::new();
|
||||
params.insert("name", "test");
|
||||
params.insert("ext", "html");
|
||||
|
||||
let url = req.url_for_map("index", ¶ms);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"http://www.actix.rs/user/test.html"
|
||||
);
|
||||
|
||||
params.remove("ext");
|
||||
assert_eq!(
|
||||
req.url_for_map("index", ¶ms),
|
||||
Err(UrlGenerationError::NotEnoughElements)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_iter() {
|
||||
let mut res = ResourceDef::new("/user/{name}.{ext}");
|
||||
res.set_name("index");
|
||||
|
||||
let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
|
||||
rmap.add(&mut res, None);
|
||||
|
||||
let req = TestRequest::default()
|
||||
.insert_header((header::HOST, "www.actix.rs"))
|
||||
.rmap(rmap)
|
||||
.to_http_request();
|
||||
|
||||
let url = req.url_for_iter("index", [("ext", "html"), ("name", "test")]);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"http://www.actix.rs/user/test.html"
|
||||
);
|
||||
|
||||
let url = req.url_for_iter("index", [("name", "test")]);
|
||||
assert_eq!(url, Err(UrlGenerationError::NotEnoughElements));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_static() {
|
||||
let mut rdef = ResourceDef::new("/index.html");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
borrow::{Borrow, Cow},
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
fmt::Write as _,
|
||||
hash::{BuildHasher, Hash},
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
|
|
@ -140,6 +142,56 @@ impl ResourceMap {
|
|||
})
|
||||
.ok_or(UrlGenerationError::NotEnoughElements)?;
|
||||
|
||||
self.url_from_path(req, path)
|
||||
}
|
||||
|
||||
/// Generate URL for named resource using map of dynamic segment values.
|
||||
///
|
||||
/// Check [`HttpRequest::url_for_map`] for detailed information.
|
||||
pub fn url_for_map<K, V, S>(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
name: &str,
|
||||
elements: &HashMap<K, V, S>,
|
||||
) -> Result<Url, UrlGenerationError>
|
||||
where
|
||||
K: Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
S: BuildHasher,
|
||||
{
|
||||
let path = self
|
||||
.named
|
||||
.get(name)
|
||||
.ok_or(UrlGenerationError::ResourceNotFound)?
|
||||
.root_rmap_fn(String::with_capacity(AVG_PATH_LEN), |mut acc, node| {
|
||||
node.pattern
|
||||
.resource_path_from_map(&mut acc, elements)
|
||||
.then_some(acc)
|
||||
})
|
||||
.ok_or(UrlGenerationError::NotEnoughElements)?;
|
||||
|
||||
self.url_from_path(req, path)
|
||||
}
|
||||
|
||||
/// Generate URL for named resource using an iterator of key-value pairs.
|
||||
///
|
||||
/// Check [`HttpRequest::url_for_iter`] for detailed information.
|
||||
pub fn url_for_iter<K, V, I>(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
name: &str,
|
||||
elements: I,
|
||||
) -> Result<Url, UrlGenerationError>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let elements = elements.into_iter().collect::<FoldHashMap<K, V>>();
|
||||
self.url_for_map(req, name, &elements)
|
||||
}
|
||||
|
||||
fn url_from_path(&self, req: &HttpRequest, path: String) -> Result<Url, UrlGenerationError> {
|
||||
let (base, path): (Cow<'_, _>, _) = if path.starts_with('/') {
|
||||
// build full URL from connection info parts and resource path
|
||||
let conn = req.connection_info();
|
||||
|
|
|
|||
Loading…
Reference in New Issue