use std::path::PathBuf;
use apimock_routing::RuleSet;
use crate::view::{
ConfigFileKind, ConfigFileView, ConfigNodeView, NodeKind, NodeValidation, WorkspaceSnapshot,
};
use super::Workspace;
use super::id_index::NodeAddress;
use super::path_helpers::file_basename;
use super::validate::respond_node_validation;
impl Workspace {
pub fn snapshot(&self) -> WorkspaceSnapshot {
let mut files: Vec<ConfigFileView> = Vec::new();
if let Some(root_nodes) = self.root_file_nodes() {
files.push(root_nodes);
}
for (rs_idx, rule_set) in self.config.service.rule_sets.iter().enumerate() {
files.push(self.rule_set_file_view(rs_idx, rule_set));
}
if let Some(paths) = self.config.service.middlewares_file_paths.as_ref() {
for (mw_idx, mw_path) in paths.iter().enumerate() {
let abs = self.resolve_relative(mw_path);
let id = self
.ids
.id_for(NodeAddress::Middleware { middleware: mw_idx })
.expect("middleware id seeded at load");
let node = ConfigNodeView {
id,
source_file: abs.clone(),
toml_path: format!("service.middlewares[{}]", mw_idx),
display_name: mw_path.clone(),
kind: NodeKind::Script,
validation: NodeValidation::ok(),
};
files.push(ConfigFileView {
path: abs.clone(),
display_name: file_basename(&abs),
kind: ConfigFileKind::Middleware,
nodes: vec![node],
});
}
}
let fallback_dir = self.config.service.fallback_respond_dir.as_str();
let fallback_abs = self.resolve_relative(fallback_dir);
let filter = self
.config
.file_tree_view
.as_ref()
.map(|c| c.to_filter())
.unwrap_or_default();
let file_tree =
apimock_routing::view::build::build_file_tree_with(&fallback_abs, &filter);
let script_routes: Vec<apimock_routing::view::ScriptRouteView> = self
.config
.service
.middlewares_file_paths
.as_ref()
.map(|paths| {
paths
.iter()
.enumerate()
.map(|(idx, p)| apimock_routing::view::build::build_script_route_view(idx, p))
.collect()
})
.unwrap_or_default();
let routes = apimock_routing::view::build::build_route_catalog(
&self.config.service.rule_sets,
Some(fallback_dir),
file_tree,
script_routes,
);
WorkspaceSnapshot {
files,
routes,
diagnostics: self.diagnostics.clone(),
}
}
fn root_file_nodes(&self) -> Option<ConfigFileView> {
let mut nodes = Vec::new();
if let Some(root_id) = self.ids.id_for(NodeAddress::Root) {
nodes.push(ConfigNodeView {
id: root_id,
source_file: self.root_path.clone(),
toml_path: String::new(),
display_name: "apimock.toml".to_owned(),
kind: NodeKind::RootSetting,
validation: NodeValidation::ok(),
});
}
if let Some(fb_id) = self.ids.id_for(NodeAddress::FallbackRespondDir) {
nodes.push(ConfigNodeView {
id: fb_id,
source_file: self.root_path.clone(),
toml_path: "service.fallback_respond_dir".to_owned(),
display_name: self.config.service.fallback_respond_dir.clone(),
kind: NodeKind::FileNode,
validation: NodeValidation::ok(),
});
}
Some(ConfigFileView {
path: self.root_path.clone(),
display_name: file_basename(&self.root_path),
kind: ConfigFileKind::Root,
nodes,
})
}
fn rule_set_file_view(&self, rs_idx: usize, rule_set: &RuleSet) -> ConfigFileView {
let file_path = PathBuf::from(rule_set.file_path.as_str());
let mut nodes: Vec<ConfigNodeView> = Vec::new();
if let Some(rs_id) = self
.ids
.id_for(NodeAddress::RuleSet { rule_set: rs_idx })
{
nodes.push(ConfigNodeView {
id: rs_id,
source_file: file_path.clone(),
toml_path: String::new(),
display_name: file_basename(&file_path),
kind: NodeKind::RuleSet,
validation: NodeValidation::ok(),
});
}
for (rule_idx, rule) in rule_set.rules.iter().enumerate() {
if let Some(rule_id) = self.ids.id_for(NodeAddress::Rule {
rule_set: rs_idx,
rule: rule_idx,
}) {
let url_path_label = rule
.when
.request
.url_path
.as_ref()
.map(|u| u.value.as_str())
.unwrap_or_default();
let display = if url_path_label.is_empty() {
format!("Rule #{}", rule_idx + 1)
} else {
url_path_label.to_owned()
};
nodes.push(ConfigNodeView {
id: rule_id,
source_file: file_path.clone(),
toml_path: format!("rules[{}]", rule_idx),
display_name: display,
kind: NodeKind::Rule,
validation: NodeValidation::ok(),
});
}
if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
rule_set: rs_idx,
rule: rule_idx,
}) {
nodes.push(ConfigNodeView {
id: resp_id,
source_file: file_path.clone(),
toml_path: format!("rules[{}].respond", rule_idx),
display_name: summarise_respond(&rule.respond),
kind: NodeKind::Respond,
validation: respond_node_validation(&rule.respond, rule_set, rule_idx, rs_idx),
});
}
}
ConfigFileView {
path: file_path.clone(),
display_name: file_basename(&file_path),
kind: ConfigFileKind::RuleSet,
nodes,
}
}
}
fn summarise_respond(respond: &apimock_routing::Respond) -> String {
if let Some(p) = respond.file_path.as_ref() {
return format!("file: {}", p);
}
if let Some(t) = respond.text.as_ref() {
const LIMIT: usize = 40;
if t.chars().count() > LIMIT {
let truncated: String = t.chars().take(LIMIT).collect();
return format!("text: {}…", truncated);
}
return format!("text: {}", t);
}
if let Some(s) = respond.status.as_ref() {
return format!("status: {}", s);
}
"(empty)".to_owned()
}