1use std::borrow::Cow;
6
7use http::uri::{Authority, Scheme, Uri};
8use http::{Error as HttpError, Request};
9use regex::{Regex as LibRegex, Replacer};
10
11pub trait PathRewriter {
15 fn rewrite<'a>(&'a mut self, path: &'a str) -> Cow<'a, str>;
16
17 fn rewrite_uri<B>(
21 &mut self,
22 request: &mut Request<B>,
23 scheme: &Scheme,
24 authority: &Authority,
25 ) -> Result<(), HttpError> {
26 let original_uri = request.uri();
27 let path = self.rewrite(original_uri.path());
28
29 let rewritten_path = {
30 if let Some(query) = original_uri.query() {
31 let mut p_and_q = path.into_owned();
32 p_and_q.push('?');
33 p_and_q.push_str(query);
34
35 p_and_q
36 } else {
37 path.into()
38 }
39 };
40
41 let rewritten_uri = Uri::builder()
42 .scheme(scheme.clone())
43 .authority(authority.clone())
44 .path_and_query(rewritten_path)
45 .build()?;
46
47 *request.uri_mut() = rewritten_uri;
48
49 Ok(())
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct Identity;
61
62impl PathRewriter for Identity {
63 #[inline]
64 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
65 path.into()
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct Static<'a>(pub &'a str);
77
78impl PathRewriter for Static<'_> {
79 #[inline]
80 fn rewrite<'a>(&'a mut self, _path: &'a str) -> Cow<'a, str> {
81 self.0.into()
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct ReplaceAll<'a>(pub &'a str, pub &'a str);
93
94impl PathRewriter for ReplaceAll<'_> {
95 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
96 if path.contains(self.0) {
97 path.replace(self.0, self.1).into()
98 } else {
99 path.into()
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct ReplaceN<'a>(pub &'a str, pub &'a str, pub usize);
113
114impl PathRewriter for ReplaceN<'_> {
115 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
116 if path.contains(self.0) {
117 path.replacen(self.0, self.1, self.2).into()
118 } else {
119 path.into()
120 }
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct TrimPrefix<'a>(pub &'a str);
133
134impl PathRewriter for TrimPrefix<'_> {
135 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
136 if let Some(stripped) = path.strip_prefix(self.0) {
137 stripped.into()
138 } else {
139 path.into()
140 }
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct TrimSuffix<'a>(pub &'a str);
153
154impl PathRewriter for TrimSuffix<'_> {
155 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
156 if let Some(stripped) = path.strip_suffix(self.0) {
157 stripped.into()
158 } else {
159 path.into()
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct AppendPrefix<'a>(pub &'a str);
172
173impl PathRewriter for AppendPrefix<'_> {
174 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
175 let mut ret = String::with_capacity(self.0.len() + path.len());
176 ret.push_str(self.0);
177 ret.push_str(path);
178 ret.into()
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub struct AppendSuffix<'a>(pub &'a str);
190
191impl PathRewriter for AppendSuffix<'_> {
192 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
193 let mut ret = String::with_capacity(self.0.len() + path.len());
194 ret.push_str(path);
195 ret.push_str(self.0);
196 ret.into()
197 }
198}
199
200#[derive(Debug, Clone)]
215pub struct RegexAll<Rep>(pub LibRegex, pub Rep);
216
217impl<Rep: Replacer> PathRewriter for RegexAll<Rep> {
218 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
219 self.0.replace_all(path, self.1.by_ref())
220 }
221}
222
223#[derive(Debug, Clone)]
242pub struct RegexN<Rep>(pub LibRegex, pub Rep, pub usize);
243
244impl<Rep: Replacer> PathRewriter for RegexN<Rep> {
245 fn rewrite<'a>(&mut self, path: &'a str) -> Cow<'a, str> {
246 self.0.replacen(path, self.2, self.1.by_ref())
247 }
248}
249
250pub struct Func<F>(pub F);
260
261impl<F> PathRewriter for Func<F>
262where
263 for<'a> F: FnMut(&'a str) -> String,
264{
265 fn rewrite<'a>(&'a mut self, path: &'a str) -> Cow<'a, str> {
266 self.0(path).into()
267 }
268}
269
270#[cfg(test)]
271mod test {
272 use super::*;
273
274 #[test]
275 fn rewrite_static() {
276 let path = "/foo/bar";
277 let mut rw = Static("/baz");
278 assert_eq!(rw.rewrite(path), "/baz");
279 }
280
281 #[test]
282 fn replace() {
283 let path = "/foo/bar/foo/baz/foo";
284 let mut rw = ReplaceAll("foo", "FOO");
285 assert_eq!(rw.rewrite(path), "/FOO/bar/FOO/baz/FOO");
286
287 let path = "/foo/bar/foo/baz/foo";
288 let mut rw = ReplaceAll("/foo", "");
289 assert_eq!(rw.rewrite(path), "/bar/baz");
290
291 let path = "/foo/bar/foo/baz/foo";
292 let mut rw = ReplaceN("foo", "FOO", 2);
293 assert_eq!(rw.rewrite(path), "/FOO/bar/FOO/baz/foo");
294 }
295
296 #[test]
297 fn trim() {
298 let path = "/foo/foo/bar";
299 let mut rw = TrimPrefix("/foo");
300 assert_eq!(rw.rewrite(path), "/foo/bar");
301
302 let path = "/foo/foo/bar";
303 let mut rw = TrimPrefix("foo");
304 assert_eq!(rw.rewrite(path), "/foo/foo/bar");
305
306 let path = "/bar/foo/foo";
307 let mut rw = TrimSuffix("foo");
308 assert_eq!(rw.rewrite(path), "/bar/foo/");
309
310 let path = "/bar/foo/foo";
311 let mut rw = TrimSuffix("foo/");
312 assert_eq!(rw.rewrite(path), "/bar/foo/foo");
313 }
314
315 #[test]
316 fn append() {
317 let path = "/foo/bar";
318 let mut rw = AppendPrefix("/baz");
319 assert_eq!(rw.rewrite(path), "/baz/foo/bar");
320
321 let path = "/foo/bar";
322 let mut rw = AppendSuffix("/baz");
323 assert_eq!(rw.rewrite(path), "/foo/bar/baz");
324 }
325
326 #[test]
327 fn regex() {
328 let path = "/2021/10/21/2021/12/02/2022/01/13";
329 let mut rw = RegexAll(
330 LibRegex::new(r"(?P<y>\d{4})/(?P<m>\d{2})/(?P<d>\d{2})").unwrap(),
331 "$m-$d-$y",
332 );
333 assert_eq!(rw.rewrite(path), "/10-21-2021/12-02-2021/01-13-2022");
334
335 let path = "/2021/10/21/2021/12/02/2022/01/13";
336 let mut rw = RegexN(
337 LibRegex::new(r"(?P<y>\d{4})/(?P<m>\d{2})/(?P<d>\d{2})").unwrap(),
338 "$m-$d-$y",
339 2,
340 );
341 assert_eq!(rw.rewrite(path), "/10-21-2021/12-02-2021/2022/01/13");
342 }
343
344 #[test]
345 fn func() {
346 let path = "/abcdefg";
347 let mut rw = Func(|path: &str| path.len().to_string());
348 assert_eq!(rw.rewrite(path), "8");
349 }
350}