use serde::Deserialize;
use std::{fs, path::Path};
mod default_respond;
mod guard;
mod prefix;
pub mod rule;
use crate::core::{
config::service_config::strategy::Strategy,
error::{AppError, AppResult},
server::parsed_request::ParsedRequest,
util::http::normalize_url_path,
};
use default_respond::DefaultRespond;
use guard::Guard;
use prefix::Prefix;
use rule::{Rule, respond::Respond};
#[derive(Clone, Deserialize, Debug)]
pub struct RuleSet {
pub prefix: Option<Prefix>,
pub default: Option<DefaultRespond>,
pub guard: Option<Guard>,
pub rules: Vec<Rule>,
#[serde(skip)]
pub file_path: String,
}
impl RuleSet {
pub fn new(
rule_set_file_path: &str,
current_dir_to_config_dir_relative_path: &str,
rule_set_idx: usize,
) -> AppResult<Self> {
let path = Path::new(rule_set_file_path);
let toml_string = fs::read_to_string(rule_set_file_path).map_err(|e| {
AppError::RuleSetRead {
path: path.to_path_buf(),
source: e,
}
})?;
let mut ret: Self = toml::from_str(&toml_string).map_err(|e| AppError::RuleSetParse {
path: path.to_path_buf(),
canonical: path.canonicalize().ok(),
source: e,
})?;
let mut prefix = ret.prefix.clone().unwrap_or_default();
prefix.url_path_prefix = prefix
.url_path_prefix
.as_deref()
.map(|p| normalize_url_path(p, None));
let respond_dir_prefix = prefix.respond_dir_prefix.as_deref().unwrap_or(".");
let respond_dir_prefix =
Path::new(current_dir_to_config_dir_relative_path).join(respond_dir_prefix);
let respond_dir_prefix = respond_dir_prefix.to_str().ok_or_else(|| {
AppError::RuleSetRead {
path: path.to_path_buf(),
source: std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"respond_dir path contains non-UTF-8 bytes: {}",
respond_dir_prefix.to_string_lossy()
),
),
}
})?;
prefix.respond_dir_prefix = Some(respond_dir_prefix.to_owned());
ret.prefix = Some(prefix);
ret.rules = ret
.rules
.iter()
.enumerate()
.map(|(rule_idx, rule)| rule.compute_derived_fields(&ret, rule_idx, rule_set_idx))
.collect();
ret.file_path = rule_set_file_path.to_owned();
Ok(ret)
}
pub fn find_matched(
&self,
parsed_request: &ParsedRequest,
strategy: Option<&Strategy>,
rule_set_idx: usize,
) -> Option<Respond> {
let _ = match self.prefix.as_ref() {
Some(prefix) if prefix.url_path_prefix.is_some() => {
if !parsed_request
.url_path
.starts_with(prefix.url_path_prefix.as_ref().unwrap())
{
return None;
}
}
_ => (),
};
for (rule_idx, rule) in self.rules.iter().enumerate() {
let is_match = rule.when.is_match(parsed_request, rule_idx, rule_set_idx);
if is_match {
match strategy {
Some(&Strategy::FirstMatch) | None => return Some(rule.respond.to_owned()),
}
}
}
None
}
pub fn validate(&self) -> bool {
true
}
pub fn dir_prefix(&self) -> String {
if let Some(dir_prefix) = self.prefix.clone().unwrap_or_default().respond_dir_prefix {
dir_prefix
} else {
String::new()
}
}
}
impl std::fmt::Display for RuleSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = self.prefix.as_ref() {
let _ = write!(f, "{}", x);
}
if let Some(x) = self.guard.as_ref() {
let _ = write!(f, "{}", x);
}
if let Some(x) = self.default.as_ref() {
let _ = write!(f, "{}", x);
}
for rule in self.rules.iter() {
let _ = write!(f, "{}", rule);
}
Ok(())
}
}