rust_serv/vhost/
matcher.rs1use std::collections::HashMap;
4use std::sync::{Arc, RwLock};
5
6use super::config::VHostConfig;
7use super::host::VirtualHost;
8
9pub struct HostMatcher {
11 hosts: RwLock<HashMap<String, Arc<VirtualHost>>>,
13 default_host: RwLock<Option<Arc<VirtualHost>>>,
15}
16
17impl HostMatcher {
18 pub fn new() -> Self {
20 Self {
21 hosts: RwLock::new(HashMap::new()),
22 default_host: RwLock::new(None),
23 }
24 }
25
26 pub fn add_host(&self, config: VHostConfig) {
28 let vhost = Arc::new(VirtualHost::new(config.clone()));
29 let mut hosts = self.hosts.write().unwrap();
30 hosts.insert(config.host.to_lowercase(), vhost);
31 }
32
33 pub fn set_default(&self, root: impl Into<std::path::PathBuf>) {
35 let vhost = Arc::new(VirtualHost::default_host(root));
36 let mut default = self.default_host.write().unwrap();
37 *default = Some(vhost);
38 }
39
40 pub fn match_host(&self, hostname: &str) -> Option<Arc<VirtualHost>> {
42 let hosts = self.hosts.read().unwrap();
44 let hostname_lower = hostname.to_lowercase();
45
46 if let Some(vhost) = hosts.get(&hostname_lower) {
47 return Some(Arc::clone(vhost));
48 }
49
50 let default = self.default_host.read().unwrap();
52 if let Some(vhost) = default.as_ref() {
53 return Some(Arc::clone(vhost));
54 }
55
56 None
57 }
58
59 pub fn remove_host(&self, hostname: &str) -> bool {
61 let mut hosts = self.hosts.write().unwrap();
62 hosts.remove(&hostname.to_lowercase()).is_some()
63 }
64
65 pub fn host_count(&self) -> usize {
67 self.hosts.read().unwrap().len()
68 }
69
70 pub fn has_host(&self, hostname: &str) -> bool {
72 self.hosts.read().unwrap().contains_key(&hostname.to_lowercase())
73 }
74
75 pub fn clear(&self) {
77 let mut hosts = self.hosts.write().unwrap();
78 hosts.clear();
79
80 let mut default = self.default_host.write().unwrap();
81 *default = None;
82 }
83
84 pub fn has_default(&self) -> bool {
86 self.default_host.read().unwrap().is_some()
87 }
88
89 pub fn hostnames(&self) -> Vec<String> {
91 self.hosts.read().unwrap().keys().cloned().collect()
92 }
93}
94
95impl Default for HostMatcher {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use std::path::PathBuf;
105
106 #[test]
107 fn test_matcher_creation() {
108 let matcher = HostMatcher::new();
109 assert_eq!(matcher.host_count(), 0);
110 assert!(!matcher.has_default());
111 }
112
113 #[test]
114 fn test_add_host() {
115 let matcher = HostMatcher::new();
116 let config = VHostConfig::new("example.com", "/var/www");
117
118 matcher.add_host(config);
119
120 assert_eq!(matcher.host_count(), 1);
121 assert!(matcher.has_host("example.com"));
122 assert!(matcher.has_host("EXAMPLE.COM"));
123 }
124
125 #[test]
126 fn test_match_host_exact() {
127 let matcher = HostMatcher::new();
128 let config = VHostConfig::new("example.com", "/var/www/example");
129 matcher.add_host(config);
130
131 let vhost = matcher.match_host("example.com").unwrap();
132 assert_eq!(vhost.host(), "example.com");
133 assert_eq!(vhost.root(), &PathBuf::from("/var/www/example"));
134 }
135
136 #[test]
137 fn test_match_host_case_insensitive() {
138 let matcher = HostMatcher::new();
139 let config = VHostConfig::new("Example.Com", "/var/www");
140 matcher.add_host(config);
141
142 let vhost = matcher.match_host("EXAMPLE.COM").unwrap();
143 assert_eq!(vhost.host(), "Example.Com");
144 }
145
146 #[test]
147 fn test_match_host_default() {
148 let matcher = HostMatcher::new();
149 matcher.set_default("/var/default");
150
151 let vhost = matcher.match_host("anything.com").unwrap();
153 assert!(vhost.is_default);
154 }
155
156 #[test]
157 fn test_match_host_no_match() {
158 let matcher = HostMatcher::new();
159 let config = VHostConfig::new("example.com", "/var/www");
160 matcher.add_host(config);
161
162 let result = matcher.match_host("other.com");
164 assert!(result.is_none());
165 }
166
167 #[test]
168 fn test_match_host_prefer_exact_over_default() {
169 let matcher = HostMatcher::new();
170
171 matcher.set_default("/var/default");
173
174 let config = VHostConfig::new("example.com", "/var/www/example");
176 matcher.add_host(config);
177
178 let vhost = matcher.match_host("example.com").unwrap();
180 assert_eq!(vhost.host(), "example.com");
181 assert!(!vhost.is_default);
182
183 let vhost = matcher.match_host("other.com").unwrap();
185 assert!(vhost.is_default);
186 }
187
188 #[test]
189 fn test_remove_host() {
190 let matcher = HostMatcher::new();
191 let config = VHostConfig::new("example.com", "/var/www");
192 matcher.add_host(config);
193
194 assert!(matcher.remove_host("example.com"));
195 assert_eq!(matcher.host_count(), 0);
196 assert!(!matcher.has_host("example.com"));
197 }
198
199 #[test]
200 fn test_remove_nonexistent_host() {
201 let matcher = HostMatcher::new();
202 assert!(!matcher.remove_host("nonexistent.com"));
203 }
204
205 #[test]
206 fn test_clear() {
207 let matcher = HostMatcher::new();
208
209 matcher.add_host(VHostConfig::new("example.com", "/var/www"));
210 matcher.add_host(VHostConfig::new("other.com", "/var/other"));
211 matcher.set_default("/var/default");
212
213 matcher.clear();
214
215 assert_eq!(matcher.host_count(), 0);
216 assert!(!matcher.has_default());
217 }
218
219 #[test]
220 fn test_hostnames() {
221 let matcher = HostMatcher::new();
222
223 matcher.add_host(VHostConfig::new("example.com", "/var/www"));
224 matcher.add_host(VHostConfig::new("other.com", "/var/other"));
225
226 let mut names = matcher.hostnames();
227 names.sort();
228
229 assert_eq!(names, vec!["example.com", "other.com"]);
230 }
231
232 #[test]
233 fn test_multiple_hosts() {
234 let matcher = HostMatcher::new();
235
236 matcher.add_host(VHostConfig::new("blog.example.com", "/var/www/blog"));
237 matcher.add_host(VHostConfig::new("api.example.com", "/var/www/api"));
238 matcher.add_host(VHostConfig::new("www.example.com", "/var/www/main"));
239
240 assert_eq!(matcher.host_count(), 3);
241
242 let blog = matcher.match_host("blog.example.com").unwrap();
243 assert_eq!(blog.root(), &PathBuf::from("/var/www/blog"));
244
245 let api = matcher.match_host("api.example.com").unwrap();
246 assert_eq!(api.root(), &PathBuf::from("/var/www/api"));
247 }
248
249 #[test]
250 fn test_default() {
251 let matcher = HostMatcher::default();
252 assert_eq!(matcher.host_count(), 0);
253 }
254
255 #[test]
256 fn test_overwrite_host() {
257 let matcher = HostMatcher::new();
258
259 matcher.add_host(VHostConfig::new("example.com", "/var/www/v1"));
260 matcher.add_host(VHostConfig::new("example.com", "/var/www/v2"));
261
262 assert_eq!(matcher.host_count(), 1);
264
265 let vhost = matcher.match_host("example.com").unwrap();
267 assert_eq!(vhost.root(), &PathBuf::from("/var/www/v2"));
268 }
269}