nginx_discovery/types/
location.rs1use crate::types::AccessLog;
3use std::path::PathBuf;
4#[derive(Debug, Clone, PartialEq)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub struct Location {
8 pub path: String,
10
11 pub modifier: LocationModifier,
13
14 pub root: Option<PathBuf>,
16
17 pub proxy_pass: Option<String>,
19
20 pub access_logs: Vec<AccessLog>,
22}
23
24impl Location {
25 pub fn new(path: impl Into<String>, modifier: LocationModifier) -> Self {
27 Self {
28 path: path.into(),
29 modifier,
30 root: None,
31 proxy_pass: None,
32 access_logs: Vec::new(),
33 }
34 }
35
36 #[must_use]
38 pub fn is_proxy(&self) -> bool {
39 self.proxy_pass.is_some()
40 }
41
42 #[must_use]
44 pub fn is_static(&self) -> bool {
45 self.root.is_some() && self.proxy_pass.is_none()
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub enum LocationModifier {
53 None,
55
56 Exact,
58
59 PrefixPriority,
61
62 Regex,
64
65 RegexCaseInsensitive,
67}
68
69impl LocationModifier {
70 #[must_use]
72 pub fn from_args(args: &[String]) -> (Self, String) {
73 if args.is_empty() {
74 return (Self::None, "/".to_string());
75 }
76
77 match args[0].as_str() {
79 "=" => {
80 let path = args.get(1).map_or("/", String::as_str);
81 (Self::Exact, path.to_string())
82 }
83 "^~" => {
84 let path = args.get(1).map_or("/", String::as_str);
85 (Self::PrefixPriority, path.to_string())
86 }
87 "~" => {
88 let pattern = args.get(1).map_or("", String::as_str);
89 (Self::Regex, pattern.to_string())
90 }
91 "~*" => {
92 let pattern = args.get(1).map_or("", String::as_str);
93 (Self::RegexCaseInsensitive, pattern.to_string())
94 }
95 _ => {
96 (Self::None, args[0].clone())
98 }
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_location_new() {
109 let location = Location::new("/api", LocationModifier::None);
110 assert_eq!(location.path, "/api");
111 assert_eq!(location.modifier, LocationModifier::None);
112 assert!(location.root.is_none());
113 assert!(location.proxy_pass.is_none());
114 assert!(location.access_logs.is_empty());
115 }
116
117 #[test]
118 fn test_is_proxy_true() {
119 let mut location = Location::new("/api", LocationModifier::None);
120 location.proxy_pass = Some("http://backend:8080".to_string());
121
122 assert!(location.is_proxy());
123 }
124
125 #[test]
126 fn test_is_proxy_false() {
127 let location = Location::new("/api", LocationModifier::None);
128 assert!(!location.is_proxy());
129 }
130
131 #[test]
132 fn test_is_static_true() {
133 let mut location = Location::new("/static", LocationModifier::None);
134 location.root = Some(PathBuf::from("/var/www/static"));
135
136 assert!(location.is_static());
137 }
138
139 #[test]
140 fn test_is_static_false_no_root() {
141 let location = Location::new("/", LocationModifier::None);
142 assert!(!location.is_static());
143 }
144
145 #[test]
146 fn test_is_static_false_has_proxy() {
147 let mut location = Location::new("/api", LocationModifier::None);
148 location.root = Some(PathBuf::from("/var/www"));
149 location.proxy_pass = Some("http://backend".to_string());
150
151 assert!(!location.is_static());
152 }
153
154 #[test]
155 fn test_location_modifier_none() {
156 let args = vec!["/path".to_string()];
157 let (modifier, path) = LocationModifier::from_args(&args);
158
159 assert_eq!(modifier, LocationModifier::None);
160 assert_eq!(path, "/path");
161 }
162
163 #[test]
164 fn test_location_modifier_exact() {
165 let args = vec!["=".to_string(), "/exact".to_string()];
166 let (modifier, path) = LocationModifier::from_args(&args);
167
168 assert_eq!(modifier, LocationModifier::Exact);
169 assert_eq!(path, "/exact");
170 }
171
172 #[test]
173 fn test_location_modifier_prefix_priority() {
174 let args = vec!["^~".to_string(), "/static/".to_string()];
175 let (modifier, path) = LocationModifier::from_args(&args);
176
177 assert_eq!(modifier, LocationModifier::PrefixPriority);
178 assert_eq!(path, "/static/");
179 }
180
181 #[test]
182 fn test_location_modifier_regex() {
183 let args = vec!["~".to_string(), r"\.php$".to_string()];
184 let (modifier, path) = LocationModifier::from_args(&args);
185
186 assert_eq!(modifier, LocationModifier::Regex);
187 assert_eq!(path, r"\.php$");
188 }
189
190 #[test]
191 fn test_location_modifier_regex_case_insensitive() {
192 let args = vec!["~*".to_string(), r"\.(jpg|png|gif)$".to_string()];
193 let (modifier, path) = LocationModifier::from_args(&args);
194
195 assert_eq!(modifier, LocationModifier::RegexCaseInsensitive);
196 assert_eq!(path, r"\.(jpg|png|gif)$");
197 }
198
199 #[test]
200 fn test_location_modifier_empty_args() {
201 let args: Vec<String> = vec![];
202 let (modifier, path) = LocationModifier::from_args(&args);
203
204 assert_eq!(modifier, LocationModifier::None);
205 assert_eq!(path, "/");
206 }
207
208 #[test]
209 fn test_location_modifier_exact_no_path() {
210 let args = vec!["=".to_string()];
211 let (modifier, path) = LocationModifier::from_args(&args);
212
213 assert_eq!(modifier, LocationModifier::Exact);
214 assert_eq!(path, "/");
215 }
216
217 #[test]
218 fn test_location_modifier_regex_no_pattern() {
219 let args = vec!["~".to_string()];
220 let (modifier, path) = LocationModifier::from_args(&args);
221
222 assert_eq!(modifier, LocationModifier::Regex);
223 assert_eq!(path, "");
224 }
225
226 #[test]
227 fn test_location_with_root() {
228 let mut location = Location::new("/images", LocationModifier::None);
229 location.root = Some(PathBuf::from("/var/www/images"));
230
231 assert_eq!(location.root, Some(PathBuf::from("/var/www/images")));
232 assert!(location.is_static());
233 }
234
235 #[test]
236 fn test_location_with_proxy_pass() {
237 let mut location = Location::new("/api/v1", LocationModifier::None);
238 location.proxy_pass = Some("http://api-backend:3000".to_string());
239
240 assert_eq!(
241 location.proxy_pass,
242 Some("http://api-backend:3000".to_string())
243 );
244 assert!(location.is_proxy());
245 }
246
247 #[test]
248 fn test_location_modifiers_equality() {
249 assert_eq!(LocationModifier::None, LocationModifier::None);
250 assert_eq!(LocationModifier::Exact, LocationModifier::Exact);
251 assert_ne!(LocationModifier::None, LocationModifier::Exact);
252 assert_ne!(
253 LocationModifier::Regex,
254 LocationModifier::RegexCaseInsensitive
255 );
256 }
257}