use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use crate::{
Config,
error::{ConfigError, WorkspaceError},
view::Diagnostic,
};
mod diff;
mod edit;
mod id_index;
mod path_helpers;
mod save;
mod snapshot;
mod validate;
#[cfg(test)]
mod tests;
use id_index::{IdIndex, NodeAddress};
use path_helpers::resolve_root;
pub struct Workspace {
pub(super) root_path: PathBuf,
pub(super) config: Config,
pub(super) ids: IdIndex,
pub(super) diagnostics: Vec<Diagnostic>,
pub(super) baseline_files: HashMap<PathBuf, String>,
pub(super) file_metas: HashMap<PathBuf, FileMeta>,
}
#[derive(Clone, Debug)]
pub(super) struct FileMeta {
pub modified: std::time::SystemTime,
pub len: u64,
}
impl Workspace {
#[allow(clippy::result_large_err)]
pub fn load(root: PathBuf) -> Result<Self, WorkspaceError> {
let resolved = resolve_root(&root)?;
let config_path_string = resolved.to_string_lossy().into_owned();
let config = Config::new(Some(&config_path_string), None).map_err(WorkspaceError::from)?;
let mut baseline_files: HashMap<PathBuf, String> = HashMap::new();
baseline_files.insert(
resolved.clone(),
crate::toml_writer::render_apimock_toml(&config),
);
for rule_set in config.service.rule_sets.iter() {
let path = PathBuf::from(rule_set.file_path.as_str());
baseline_files.insert(path, crate::toml_writer::render_rule_set_toml(rule_set));
}
let mut file_metas: HashMap<PathBuf, FileMeta> = HashMap::new();
for path in baseline_files.keys() {
if let Ok(meta) = std::fs::metadata(path)
&& let Ok(modified) = meta.modified()
{
file_metas.insert(
path.clone(),
FileMeta {
modified,
len: meta.len(),
},
);
}
}
let mut workspace = Self {
root_path: resolved,
config,
ids: IdIndex::default(),
diagnostics: Vec::new(),
baseline_files,
file_metas,
};
workspace.seed_ids();
Ok(workspace)
}
pub fn has_external_changes(&self) -> bool {
for (path, recorded) in &self.file_metas {
if let Ok(meta) = std::fs::metadata(path) {
let changed_size = meta.len() != recorded.len;
let changed_mtime = meta
.modified()
.map(|m| m != recorded.modified)
.unwrap_or(false);
if changed_size || changed_mtime {
return true;
}
}
}
false
}
#[allow(clippy::result_large_err)]
pub fn sync_from_disk(&mut self) -> Result<(), WorkspaceError> {
let fresh = Self::load(self.root_path.clone())?;
*self = fresh;
Ok(())
}
fn seed_ids(&mut self) {
self.ids.insert(NodeAddress::Root);
self.ids.insert(NodeAddress::FallbackRespondDir);
for (rs_idx, rule_set) in self.config.service.rule_sets.iter().enumerate() {
self.ids.insert(NodeAddress::RuleSet { rule_set: rs_idx });
for (rule_idx, _rule) in rule_set.rules.iter().enumerate() {
self.ids.insert(NodeAddress::Rule {
rule_set: rs_idx,
rule: rule_idx,
});
self.ids.insert(NodeAddress::Respond {
rule_set: rs_idx,
rule: rule_idx,
});
}
}
if let Some(paths) = self.config.service.middlewares_file_paths.as_ref() {
for mw_idx in 0..paths.len() {
self.ids
.insert(NodeAddress::Middleware { middleware: mw_idx });
}
}
}
#[allow(clippy::result_large_err)]
pub(super) fn config_relative_dir(&self) -> Result<String, ConfigError> {
self.config.current_dir_to_parent_dir_relative_path()
}
pub(super) fn resolve_relative(&self, rel: &str) -> PathBuf {
match self.config.current_dir_to_parent_dir_relative_path() {
Ok(dir) => Path::new(&dir).join(rel),
Err(_) => PathBuf::from(rel),
}
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn root_path(&self) -> &Path {
&self.root_path
}
pub fn list_directory(&self, path: &Path) -> Vec<apimock_routing::view::FileNodeView> {
let filter = self
.config
.file_tree_view
.as_ref()
.map(|c| c.to_filter())
.unwrap_or_default();
apimock_routing::view::build::list_directory_with(path, &filter)
}
}