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, server::parsed_request::ParsedRequest,
util::http::normalize_url_path,
};
use default_respond::DefaultRespond;
use guard::Guard;
use prefix::Prefix;
use rule::{respond::Respond, Rule};
#[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,
) -> Self {
let toml_string = fs::read_to_string(rule_set_file_path).expect(&format!(
"failed to read rule set toml `{}`",
rule_set_file_path
));
let deserialized = toml::from_str::<Self>(&toml_string);
let mut ret = match deserialized {
Ok(x) => x,
Err(err) => panic!(
"invalid toml content: {} ({})\n({})",
rule_set_file_path,
Path::new(rule_set_file_path)
.canonicalize()
.unwrap_or_default()
.to_string_lossy(),
err
),
};
let mut prefix = match ret.prefix {
Some(x) => x.clone(),
None => Prefix::default(),
};
prefix.url_path_prefix = match prefix.url_path_prefix {
Some(url_path_prefix) => Some(normalize_url_path(url_path_prefix.as_str(), None)),
None => None,
};
let respond_dir_prefix = match prefix.respond_dir_prefix.as_ref() {
Some(respond_dir_prefix) => respond_dir_prefix.as_str(),
None => ".",
};
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().expect(
format!(
"failed to get path str: {}",
respond_dir_prefix.to_string_lossy()
)
.as_str(),
);
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();
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(())
}
}