apimock_routing/
rule_set.rs1use serde::Deserialize;
2
3use std::{fs, path::Path};
4
5mod default_respond;
6mod guard;
7mod prefix;
8pub mod rule;
9
10use crate::{
11 error::{RoutingError, RoutingResult},
12 parsed_request::ParsedRequest,
13 strategy::Strategy,
14 util::http::normalize_url_path,
15};
16use default_respond::DefaultRespond;
17use guard::Guard;
18use prefix::Prefix;
19use rule::{Rule, respond::Respond};
20
21#[derive(Clone, Deserialize, Debug)]
32pub struct RuleSet {
33 pub prefix: Option<Prefix>,
34 pub default: Option<DefaultRespond>,
35 pub guard: Option<Guard>,
36 pub rules: Vec<Rule>,
37 #[serde(skip)]
38 pub file_path: String,
39}
40
41impl RuleSet {
42 pub fn new(
52 rule_set_file_path: &str,
53 current_dir_to_config_dir_relative_path: &str,
54 rule_set_idx: usize,
55 ) -> RoutingResult<Self> {
56 let path = Path::new(rule_set_file_path);
57 let toml_string = fs::read_to_string(rule_set_file_path).map_err(|e| {
58 RoutingError::RuleSetRead {
59 path: path.to_path_buf(),
60 source: e,
61 }
62 })?;
63
64 let mut ret: Self = toml::from_str(&toml_string).map_err(|e| RoutingError::RuleSetParse {
65 path: path.to_path_buf(),
66 canonical: path.canonicalize().ok(),
67 source: e,
68 })?;
69
70 let mut prefix = ret.prefix.clone().unwrap_or_default();
72
73 prefix.url_path_prefix = prefix
76 .url_path_prefix
77 .as_deref()
78 .map(|p| normalize_url_path(p, None));
79
80 let respond_dir_prefix = prefix.respond_dir_prefix.as_deref().unwrap_or(".");
84
85 let respond_dir_prefix =
86 Path::new(current_dir_to_config_dir_relative_path).join(respond_dir_prefix);
87 let respond_dir_prefix = respond_dir_prefix.to_str().ok_or_else(|| {
88 RoutingError::RuleSetRead {
89 path: path.to_path_buf(),
90 source: std::io::Error::new(
95 std::io::ErrorKind::InvalidData,
96 format!(
97 "respond_dir path contains non-UTF-8 bytes: {}",
98 respond_dir_prefix.to_string_lossy()
99 ),
100 ),
101 }
102 })?;
103
104 prefix.respond_dir_prefix = Some(respond_dir_prefix.to_owned());
105 ret.prefix = Some(prefix);
106
107 ret.rules = ret
111 .rules
112 .iter()
113 .enumerate()
114 .map(|(rule_idx, rule)| rule.compute_derived_fields(&ret, rule_idx, rule_set_idx))
115 .collect();
116
117 ret.file_path = rule_set_file_path.to_owned();
119
120 Ok(ret)
121 }
122
123 pub fn find_matched(
125 &self,
126 parsed_request: &ParsedRequest,
127 strategy: Option<&Strategy>,
128 rule_set_idx: usize,
129 ) -> Option<Respond> {
130 let _ = match self.prefix.as_ref() {
131 Some(prefix) if prefix.url_path_prefix.is_some() => {
132 if !parsed_request
133 .url_path
134 .starts_with(prefix.url_path_prefix.as_ref().unwrap())
135 {
136 return None;
137 }
138 }
139 _ => (),
140 };
141
142 for (rule_idx, rule) in self.rules.iter().enumerate() {
143 let is_match = rule.when.is_match(parsed_request, rule_idx, rule_set_idx);
144 if is_match {
145 match strategy {
147 Some(&Strategy::FirstMatch) | None => return Some(rule.respond.to_owned()),
148 }
149 }
150 }
151
152 None
153 }
154
155 pub fn validate(&self) -> bool {
157 true
158 }
159
160 pub fn dir_prefix(&self) -> String {
162 if let Some(dir_prefix) = self.prefix.clone().unwrap_or_default().respond_dir_prefix {
163 dir_prefix
164 } else {
165 String::new()
166 }
167 }
168}
169
170impl std::fmt::Display for RuleSet {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 if let Some(x) = self.prefix.as_ref() {
173 let _ = write!(f, "{}", x);
174 }
175 if let Some(x) = self.guard.as_ref() {
176 let _ = write!(f, "{}", x);
177 }
178 if let Some(x) = self.default.as_ref() {
179 let _ = write!(f, "{}", x);
180 }
181 for rule in self.rules.iter() {
182 let _ = write!(f, "{}", rule);
183 }
184 Ok(())
185 }
186}