1use std::{cell::RefCell, rc::Rc};
2
3use urly::{Url, quoting::Component, quoting::quote};
4
5use crate::router::ResourceDef;
6use crate::util::HashMap;
7use crate::web::httprequest::HttpRequest;
8
9#[derive(Clone, Debug)]
15pub struct ResourceMap {
16 #[allow(dead_code)]
17 root: ResourceDef,
18 parent: RefCell<Option<Rc<ResourceMap>>>,
19 named: HashMap<String, ResourceDef>,
20 patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
21}
22
23impl ResourceMap {
24 pub fn new(root: ResourceDef) -> Self {
26 ResourceMap {
27 root,
28 parent: RefCell::new(None),
29 named: HashMap::default(),
30 patterns: Vec::new(),
31 }
32 }
33
34 pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option<Rc<ResourceMap>>) {
40 pattern.set_id(self.patterns.len() as u16);
41 self.patterns.push((pattern.clone(), nested));
42 if !pattern.name().is_empty() {
43 self.named
44 .insert(pattern.name().to_string(), pattern.clone());
45 }
46 }
47
48 pub(crate) fn build(&self, current: &Rc<ResourceMap>) {
49 for (_, nested) in &self.patterns {
50 if let Some(nested) = nested {
51 *nested.parent.borrow_mut() = Some(current.clone());
52 nested.build(nested);
53 }
54 }
55 }
56}
57
58impl ResourceMap {
59 pub fn url_for<U, I>(
63 &self,
64 req: &HttpRequest,
65 name: &str,
66 elements: U,
67 ) -> Result<Url, super::error::UrlGenerationError>
68 where
69 U: IntoIterator<Item = I>,
70 I: AsRef<str>,
71 {
72 let mut path = String::new();
73 let mut elements = elements
74 .into_iter()
75 .map(|element| quote(element.as_ref(), Component::Opaque).into_owned());
76
77 if self.patterns_for(name, &mut path, &mut elements)?.is_some() {
78 if path.starts_with('/') {
79 let conn = req.connection_info();
80 let (scheme, host) = (conn.scheme(), conn.host());
81 let mut url = String::with_capacity(scheme.len() + host.len() + path.len() + 3);
82 url.push_str(scheme);
83 url.push_str("://");
84 url.push_str(host);
85 url.push_str(&path);
86 path = url;
87 }
88 Ok(Url::try_from(path)?)
89 } else {
90 Err(super::error::UrlGenerationError::ResourceNotFound)
91 }
92 }
93
94 fn patterns_for<U, I>(
110 &self,
111 name: &str,
112 path: &mut String,
113 elements: &mut U,
114 ) -> Result<Option<()>, super::error::UrlGenerationError>
115 where
116 U: Iterator<Item = I>,
117 I: AsRef<str>,
118 {
119 if self.pattern_for(name, path, elements)?.is_some() {
120 Ok(Some(()))
121 } else {
122 self.parent_pattern_for(name, path, elements)
123 }
124 }
125
126 fn pattern_for<U, I>(
127 &self,
128 name: &str,
129 path: &mut String,
130 elements: &mut U,
131 ) -> Result<Option<()>, super::error::UrlGenerationError>
132 where
133 U: Iterator<Item = I>,
134 I: AsRef<str>,
135 {
136 if let Some(pattern) = self.named.get(name) {
137 if pattern.pattern().starts_with('/') {
138 self.fill_root(path, elements)?;
139 }
140 if pattern.build_path(path, elements) {
141 Ok(Some(()))
142 } else {
143 Err(super::error::UrlGenerationError::NotEnoughElements)
144 }
145 } else {
146 for (_, rmap) in &self.patterns {
147 if let Some(rmap) = rmap
148 && rmap.pattern_for(name, path, elements)?.is_some()
149 {
150 return Ok(Some(()));
151 }
152 }
153 Ok(None)
154 }
155 }
156
157 fn fill_root<U, I>(
158 &self,
159 path: &mut String,
160 elements: &mut U,
161 ) -> Result<(), super::error::UrlGenerationError>
162 where
163 U: Iterator<Item = I>,
164 I: AsRef<str>,
165 {
166 if let Some(ref parent) = *self.parent.borrow() {
167 parent.fill_root(path, elements)?;
168 }
169 if self.root.build_path(path, elements) {
170 Ok(())
171 } else {
172 Err(super::error::UrlGenerationError::NotEnoughElements)
173 }
174 }
175
176 fn parent_pattern_for<U, I>(
177 &self,
178 name: &str,
179 path: &mut String,
180 elements: &mut U,
181 ) -> Result<Option<()>, super::error::UrlGenerationError>
182 where
183 U: Iterator<Item = I>,
184 I: AsRef<str>,
185 {
186 if let Some(ref parent) = *self.parent.borrow() {
187 parent.patterns_for(name, path, elements)
188 } else {
189 Ok(None)
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::web::test::TestRequest;
198
199 #[test]
200 fn url_for_parent() {
201 let mut root = ResourceMap::new(ResourceDef::new(""));
203 let mut index = ResourceDef::new("/index/{id}");
204 index.set_name("index");
205 root.add(&mut index, None);
206 let mut ext = ResourceDef::new("https://youtube.com/watch/{id}");
207 ext.set_name("youtube");
208 root.add(&mut ext, None);
209
210 let mut nested = ResourceMap::new(ResourceDef::root_prefix("/a"));
211 let mut res = ResourceDef::new("/{id}");
212 res.set_name("nested");
213 nested.add(&mut res, None);
214 let nested = Rc::new(nested);
215 root.add(&mut ResourceDef::root_prefix("/a"), Some(nested.clone()));
216
217 let mut sibling = ResourceMap::new(ResourceDef::root_prefix("/b"));
218 let mut res = ResourceDef::new("/{id}");
219 res.set_name("sibling");
220 sibling.add(&mut res, None);
221 root.add(&mut ResourceDef::root_prefix("/b"), Some(Rc::new(sibling)));
222
223 let root = Rc::new(root);
224 root.build(&root);
225
226 let req = TestRequest::default().to_http_request();
227 for rmap in [&root, &nested] {
228 assert_eq!(
229 rmap.url_for(&req, "index", ["1"]).unwrap().as_str(),
230 "http://localhost:8080/index/1"
231 );
232 assert_eq!(
233 rmap.url_for(&req, "youtube", ["2"]).unwrap().as_str(),
234 "https://youtube.com/watch/2"
235 );
236 assert_eq!(
237 rmap.url_for(&req, "nested", ["3"]).unwrap().as_str(),
238 "http://localhost:8080/a/3"
239 );
240 assert_eq!(
241 rmap.url_for(&req, "sibling", ["4"]).unwrap().as_str(),
242 "http://localhost:8080/b/4"
243 );
244 assert_eq!(
245 rmap.url_for(&req, "index", ["a/b?c#d%"]).unwrap().as_str(),
246 "http://localhost:8080/index/a%2Fb%3Fc%23d%25"
247 );
248 assert_eq!(
249 rmap.url_for(&req, "youtube", ["a/b?c#d%"])
250 .unwrap()
251 .as_str(),
252 "https://youtube.com/watch/a%2Fb%3Fc%23d%25"
253 );
254 assert!(rmap.url_for(&req, "index", [""; 0]).is_err());
255 assert!(rmap.url_for(&req, "unknown", [""; 0]).is_err());
256 }
257 }
258}