use crate::types::AccessLog;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Location {
pub path: String,
pub modifier: LocationModifier,
pub root: Option<PathBuf>,
pub proxy_pass: Option<String>,
pub access_logs: Vec<AccessLog>,
}
impl Location {
pub fn new(path: impl Into<String>, modifier: LocationModifier) -> Self {
Self {
path: path.into(),
modifier,
root: None,
proxy_pass: None,
access_logs: Vec::new(),
}
}
#[must_use]
pub fn is_proxy(&self) -> bool {
self.proxy_pass.is_some()
}
#[must_use]
pub fn is_static(&self) -> bool {
self.root.is_some() && self.proxy_pass.is_none()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LocationModifier {
None,
Exact,
PrefixPriority,
Regex,
RegexCaseInsensitive,
}
impl LocationModifier {
#[must_use]
pub fn from_args(args: &[String]) -> (Self, String) {
if args.is_empty() {
return (Self::None, "/".to_string());
}
match args[0].as_str() {
"=" => {
let path = args.get(1).map_or("/", String::as_str);
(Self::Exact, path.to_string())
}
"^~" => {
let path = args.get(1).map_or("/", String::as_str);
(Self::PrefixPriority, path.to_string())
}
"~" => {
let pattern = args.get(1).map_or("", String::as_str);
(Self::Regex, pattern.to_string())
}
"~*" => {
let pattern = args.get(1).map_or("", String::as_str);
(Self::RegexCaseInsensitive, pattern.to_string())
}
_ => {
(Self::None, args[0].clone())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_location_new() {
let location = Location::new("/api", LocationModifier::None);
assert_eq!(location.path, "/api");
assert_eq!(location.modifier, LocationModifier::None);
assert!(location.root.is_none());
assert!(location.proxy_pass.is_none());
assert!(location.access_logs.is_empty());
}
#[test]
fn test_is_proxy_true() {
let mut location = Location::new("/api", LocationModifier::None);
location.proxy_pass = Some("http://backend:8080".to_string());
assert!(location.is_proxy());
}
#[test]
fn test_is_proxy_false() {
let location = Location::new("/api", LocationModifier::None);
assert!(!location.is_proxy());
}
#[test]
fn test_is_static_true() {
let mut location = Location::new("/static", LocationModifier::None);
location.root = Some(PathBuf::from("/var/www/static"));
assert!(location.is_static());
}
#[test]
fn test_is_static_false_no_root() {
let location = Location::new("/", LocationModifier::None);
assert!(!location.is_static());
}
#[test]
fn test_is_static_false_has_proxy() {
let mut location = Location::new("/api", LocationModifier::None);
location.root = Some(PathBuf::from("/var/www"));
location.proxy_pass = Some("http://backend".to_string());
assert!(!location.is_static());
}
#[test]
fn test_location_modifier_none() {
let args = vec!["/path".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::None);
assert_eq!(path, "/path");
}
#[test]
fn test_location_modifier_exact() {
let args = vec!["=".to_string(), "/exact".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::Exact);
assert_eq!(path, "/exact");
}
#[test]
fn test_location_modifier_prefix_priority() {
let args = vec!["^~".to_string(), "/static/".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::PrefixPriority);
assert_eq!(path, "/static/");
}
#[test]
fn test_location_modifier_regex() {
let args = vec!["~".to_string(), r"\.php$".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::Regex);
assert_eq!(path, r"\.php$");
}
#[test]
fn test_location_modifier_regex_case_insensitive() {
let args = vec!["~*".to_string(), r"\.(jpg|png|gif)$".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::RegexCaseInsensitive);
assert_eq!(path, r"\.(jpg|png|gif)$");
}
#[test]
fn test_location_modifier_empty_args() {
let args: Vec<String> = vec![];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::None);
assert_eq!(path, "/");
}
#[test]
fn test_location_modifier_exact_no_path() {
let args = vec!["=".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::Exact);
assert_eq!(path, "/");
}
#[test]
fn test_location_modifier_regex_no_pattern() {
let args = vec!["~".to_string()];
let (modifier, path) = LocationModifier::from_args(&args);
assert_eq!(modifier, LocationModifier::Regex);
assert_eq!(path, "");
}
#[test]
fn test_location_with_root() {
let mut location = Location::new("/images", LocationModifier::None);
location.root = Some(PathBuf::from("/var/www/images"));
assert_eq!(location.root, Some(PathBuf::from("/var/www/images")));
assert!(location.is_static());
}
#[test]
fn test_location_with_proxy_pass() {
let mut location = Location::new("/api/v1", LocationModifier::None);
location.proxy_pass = Some("http://api-backend:3000".to_string());
assert_eq!(
location.proxy_pass,
Some("http://api-backend:3000".to_string())
);
assert!(location.is_proxy());
}
#[test]
fn test_location_modifiers_equality() {
assert_eq!(LocationModifier::None, LocationModifier::None);
assert_eq!(LocationModifier::Exact, LocationModifier::Exact);
assert_ne!(LocationModifier::None, LocationModifier::Exact);
assert_ne!(
LocationModifier::Regex,
LocationModifier::RegexCaseInsensitive
);
}
}