1use apimock_routing::RuleSet;
16use constant::*;
17use listener_config::ListenerConfig;
18use log_config::LogConfig;
19use serde::Deserialize;
20use service_config::ServiceConfig;
21
22use std::{fs, path::Path};
23
24use crate::{
25 error::{ConfigError, ConfigResult},
26 path_util::current_dir_to_file_parent_dir_relative_path,
27};
28
29pub mod constant;
30pub mod listener_config;
31pub mod log_config;
32pub mod service_config;
33
34#[derive(Clone, Deserialize)]
37pub struct Config {
38 #[serde(skip)]
42 pub file_path: Option<String>,
43
44 pub listener: Option<ListenerConfig>,
45 pub log: Option<LogConfig>,
46 pub service: ServiceConfig,
47}
48
49impl Config {
50 pub fn new(
57 config_file_path: Option<&String>,
58 fallback_respond_dir_path: Option<&String>,
59 ) -> ConfigResult<Self> {
60 let mut ret = Self::init(config_file_path)?;
61
62 ret.set_rule_sets()?;
63
64 ret.compute_fallback_respond_dir(fallback_respond_dir_path)?;
65
66 if !ret.validate() {
67 return Err(ConfigError::Validation);
68 }
69
70 log::info!("{}", ret);
71
72 Ok(ret)
73 }
74
75 fn init(config_file_path: Option<&String>) -> ConfigResult<Self> {
79 let Some(config_file_path) = config_file_path else {
80 return Ok(Config::default());
81 };
82
83 log::info!("[config] {}\n", config_file_path);
84
85 let path = Path::new(config_file_path);
86 let toml_string =
87 fs::read_to_string(config_file_path).map_err(|e| ConfigError::ConfigRead {
88 path: path.to_path_buf(),
89 source: e,
90 })?;
91
92 let mut config: Config =
93 toml::from_str(&toml_string).map_err(|e| ConfigError::ConfigParse {
94 path: path.to_path_buf(),
95 canonical: path.canonicalize().ok(),
96 source: e,
97 })?;
98 config.file_path = Some(config_file_path.to_owned());
99
100 Ok(config)
101 }
102
103 fn set_rule_sets(&mut self) -> ConfigResult<()> {
105 let relative_dir_path = self.current_dir_to_parent_dir_relative_path()?;
106
107 let Some(rule_sets_file_paths) = self.service.rule_sets_file_paths.as_ref() else {
108 return Ok(());
109 };
110
111 let mut rule_sets = Vec::with_capacity(rule_sets_file_paths.len());
112 for (rule_set_idx, rule_set_file_path) in rule_sets_file_paths.iter().enumerate() {
113 let joined = Path::new(relative_dir_path.as_str()).join(rule_set_file_path);
114 let path_str = joined.to_str().ok_or_else(|| ConfigError::ConfigRead {
115 path: joined.clone(),
116 source: std::io::Error::new(
117 std::io::ErrorKind::InvalidData,
118 format!(
119 "rule set #{} path contains non-UTF-8 bytes: {}",
120 rule_set_idx + 1,
121 joined.to_string_lossy(),
122 ),
123 ),
124 })?;
125
126 rule_sets.push(RuleSet::new(
129 path_str,
130 relative_dir_path.as_str(),
131 rule_set_idx,
132 )?);
133 }
134
135 self.service.rule_sets = rule_sets;
136 Ok(())
137 }
138
139 pub fn compute_fallback_respond_dir(
142 &mut self,
143 fallback_respond_dir_path: Option<&String>,
144 ) -> ConfigResult<()> {
145 if let Some(fallback_respond_dir_path) = fallback_respond_dir_path {
146 self.service.fallback_respond_dir = fallback_respond_dir_path.to_owned();
147 return Ok(());
148 }
149
150 if self.service.fallback_respond_dir.as_str() == SERVICE_DEFAULT_FALLBACK_RESPOND_DIR {
151 return Ok(());
152 }
153
154 let relative_path = self.current_dir_to_parent_dir_relative_path()?;
155 let joined =
156 Path::new(relative_path.as_str()).join(self.service.fallback_respond_dir.as_str());
157 let resolved = joined.to_str().ok_or_else(|| ConfigError::PathResolve {
158 path: joined.clone(),
159 source: std::io::Error::new(
160 std::io::ErrorKind::InvalidData,
161 format!(
162 "fallback_respond_dir path contains non-UTF-8 bytes: {}",
163 joined.to_string_lossy(),
164 ),
165 ),
166 })?;
167 self.service.fallback_respond_dir = resolved.to_owned();
168 Ok(())
169 }
170
171 pub fn listener_http_addr(&self) -> Option<String> {
173 let https_is_active = self.listener_https_addr().is_some();
174 if https_is_active {
175 let port_is_single = self
176 .listener
177 .as_ref()
178 .and_then(|l| l.tls.as_ref())
179 .map(|t| t.port.is_none())
180 .unwrap_or(false);
181 if port_is_single {
182 return None;
183 }
184 }
185
186 let listener_default;
187 let listener = match self.listener.as_ref() {
188 Some(l) => l,
189 None => {
190 listener_default = ListenerConfig::default();
191 &listener_default
192 }
193 };
194
195 Some(format!("{}:{}", listener.ip_address, listener.port))
196 }
197
198 pub fn listener_https_addr(&self) -> Option<String> {
200 let listener = self.listener.as_ref()?;
201 let tls = listener.tls.as_ref()?;
202 let port = tls.port.unwrap_or(listener.port);
203 Some(format!("{}:{}", listener.ip_address, port))
204 }
205
206 fn validate(&self) -> bool {
210 if let Some(listener) = self.listener.as_ref() {
211 if !listener.validate() {
212 return false;
213 }
214
215 if self.listener_http_addr().is_none() && self.listener_https_addr().is_none() {
216 log::error!("at least one listener (http or https) is required");
217 return false;
218 }
219 }
220 self.service.validate()
221 }
222
223 pub fn current_dir_to_parent_dir_relative_path(&self) -> ConfigResult<String> {
225 let Some(file_path) = self.file_path.as_ref() else {
226 return Ok(String::from("."));
227 };
228
229 let relative_dir_path =
230 current_dir_to_file_parent_dir_relative_path(file_path.as_str()).map_err(|e| {
231 ConfigError::PathResolve {
232 path: Path::new(file_path).to_path_buf(),
233 source: e,
234 }
235 })?;
236
237 let as_str = relative_dir_path
238 .to_str()
239 .ok_or_else(|| ConfigError::PathResolve {
240 path: relative_dir_path.clone(),
241 source: std::io::Error::new(
242 std::io::ErrorKind::InvalidData,
243 format!(
244 "relative path contains non-UTF-8 bytes: {}",
245 relative_dir_path.to_string_lossy()
246 ),
247 ),
248 })?;
249
250 Ok(as_str.to_owned())
251 }
252}
253
254impl Default for Config {
255 fn default() -> Self {
256 Config {
257 file_path: None,
258 listener: Some(ListenerConfig {
259 ip_address: LISTENER_DEFAULT_IP_ADDRESS.to_owned(),
260 port: LISTENER_DEFAULT_PORT,
261 tls: None,
262 }),
263 log: Some(LogConfig::default()),
264 service: ServiceConfig::default(),
265 }
266 }
267}
268
269impl std::fmt::Display for Config {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 let log = self.log.clone().unwrap_or_default();
272 let _ = write!(f, "{}", log);
273 let _ = writeln!(f, "{}", PRINT_DELIMITER);
274 let _ = write!(f, "{}", self.service);
275 Ok(())
276 }
277}