eve_rs/
middleware_handler.rs

1use std::{fmt::Debug, marker::PhantomData};
2
3use regex::Regex;
4
5use crate::{Context, Middleware};
6
7pub(crate) struct MiddlewareHandler<TContext, TMiddleware, TErrorData>
8where
9	TContext: Context + Debug + Send + Sync,
10	TMiddleware: Middleware<TContext, TErrorData> + Clone + Send + Sync,
11	TErrorData: Default + Send + Sync,
12{
13	pub(crate) is_endpoint: bool,
14	pub(crate) mounted_url: String,
15	pub(crate) path_match: Regex,
16	pub(crate) handler: TMiddleware,
17	phantom_context: PhantomData<TContext>,
18	phantom_error: PhantomData<TErrorData>,
19}
20
21impl<TContext, TMiddleware, TErrorData> Clone
22	for MiddlewareHandler<TContext, TMiddleware, TErrorData>
23where
24	TContext: Context + Debug + Send + Sync,
25	TMiddleware: Middleware<TContext, TErrorData> + Clone + Send + Sync,
26	TErrorData: Default + Send + Sync,
27{
28	fn clone(&self) -> Self {
29		MiddlewareHandler {
30			is_endpoint: self.is_endpoint,
31			mounted_url: self.mounted_url.clone(),
32			path_match: self.path_match.clone(),
33			handler: self.handler.clone(),
34			phantom_context: PhantomData,
35			phantom_error: PhantomData,
36		}
37	}
38}
39
40impl<TContext, TMiddleware, TErrorData>
41	MiddlewareHandler<TContext, TMiddleware, TErrorData>
42where
43	TContext: Context + Debug + Send + Sync,
44	TMiddleware: Middleware<TContext, TErrorData> + Clone + Send + Sync,
45	TErrorData: Default + Send + Sync,
46{
47	pub(crate) fn new(
48		path: &str,
49		handler: TMiddleware,
50		is_endpoint: bool,
51	) -> Self {
52		let mut mounted_url = path.to_string();
53
54		// Make sure it always begins with a /
55		if mounted_url.starts_with("./") {
56			mounted_url = mounted_url[1..].to_string();
57		} else if !path.starts_with('/') {
58			mounted_url = format!("/{}", mounted_url);
59		}
60
61		// if there's a trailing /, remove it
62		if mounted_url.ends_with('/') {
63			mounted_url = path[..(path.len() - 1)].to_string();
64		}
65
66		// If there's nothing left, set the middleware to /
67		if mounted_url.is_empty() {
68			mounted_url.push('/');
69		}
70
71		let mut regex_path = mounted_url
72			.replace('\\', "\\\\")
73			.replace('[', "\\[")
74			.replace(']', "\\]")
75			.replace('?', "\\?")
76			.replace('+', "\\+")
77			.replace('{', "\\{")
78			.replace('}', "\\}")
79			.replace('(', "\\(")
80			.replace(')', "\\)")
81			.replace('|', "\\|")
82			.replace('^', "\\^")
83			.replace('$', "\\$")
84			.replace('.', "\\.") // Specifically, match the dot. This ain't a regex character
85			.replace("**", "(.+)") // Match anything [ NOTE: first replace `**` and then replace
86			// remaining `*` ]
87			.replace('*', "([^/]+)"); // Match anything that's not a /, but at least 1 character
88
89		// Make a variable out of anything that begins with a : and has a-z,
90		// A-Z, 0-9, '_'
91		regex_path = Regex::new(":(?P<var>([a-zA-Z0-9_]+))")
92			.unwrap()
93			// Match that variable with anything that isn't a `/`
94			.replace_all(&regex_path, "(?P<$var>([^\\s/]+))")
95			.to_string();
96
97		if regex_path != "/" {
98			// If there's something to match with,
99			// add the Regex to mention that both / and non / should match at
100			// the end of the url
101			regex_path.push_str("[/]?");
102		}
103
104		// If this is only supposed to match an endpoint URL, make sure the
105		// Regex only allows the end of the path
106		if is_endpoint {
107			regex_path.push('$');
108		}
109
110		MiddlewareHandler {
111			is_endpoint,
112			mounted_url,
113			path_match: Regex::new(&regex_path).unwrap(),
114			handler,
115			phantom_context: PhantomData,
116			phantom_error: PhantomData,
117		}
118	}
119
120	pub(crate) fn is_match(&self, url: &str) -> bool {
121		self.path_match.is_match(url)
122	}
123}