apimock_config/workspace/
snapshot.rs1use std::path::PathBuf;
19
20use apimock_routing::RuleSet;
21
22use crate::view::{
23 ConfigFileKind, ConfigFileView, ConfigNodeView, NodeKind, NodeValidation, WorkspaceSnapshot,
24};
25
26use super::Workspace;
27use super::id_index::NodeAddress;
28use super::path_helpers::file_basename;
29use super::validate::respond_node_validation;
30
31impl Workspace {
32 pub fn snapshot(&self) -> WorkspaceSnapshot {
42 let mut files: Vec<ConfigFileView> = Vec::new();
43
44 if let Some(root_nodes) = self.root_file_nodes() {
46 files.push(root_nodes);
47 }
48
49 for (rs_idx, rule_set) in self.config.service.rule_sets.iter().enumerate() {
51 files.push(self.rule_set_file_view(rs_idx, rule_set));
52 }
53
54 if let Some(paths) = self.config.service.middlewares_file_paths.as_ref() {
57 for (mw_idx, mw_path) in paths.iter().enumerate() {
58 let abs = self.resolve_relative(mw_path);
59 let id = self
60 .ids
61 .id_for(NodeAddress::Middleware { middleware: mw_idx })
62 .expect("middleware id seeded at load");
63 let node = ConfigNodeView {
64 id,
65 source_file: abs.clone(),
66 toml_path: format!("service.middlewares[{}]", mw_idx),
67 display_name: mw_path.clone(),
68 kind: NodeKind::Script,
69 validation: NodeValidation::ok(),
70 };
71 files.push(ConfigFileView {
72 path: abs.clone(),
73 display_name: file_basename(&abs),
74 kind: ConfigFileKind::Middleware,
75 nodes: vec![node],
76 });
77 }
78 }
79
80 let fallback_dir = self.config.service.fallback_respond_dir.as_str();
85 let fallback_abs = self.resolve_relative(fallback_dir);
86 let filter = self
87 .config
88 .file_tree_view
89 .as_ref()
90 .map(|c| c.to_filter())
91 .unwrap_or_default();
92 let file_tree =
93 apimock_routing::view::build::build_file_tree_with(&fallback_abs, &filter);
94
95 let script_routes: Vec<apimock_routing::view::ScriptRouteView> = self
96 .config
97 .service
98 .middlewares_file_paths
99 .as_ref()
100 .map(|paths| {
101 paths
102 .iter()
103 .enumerate()
104 .map(|(idx, p)| apimock_routing::view::build::build_script_route_view(idx, p))
105 .collect()
106 })
107 .unwrap_or_default();
108
109 let routes = apimock_routing::view::build::build_route_catalog(
110 &self.config.service.rule_sets,
111 Some(fallback_dir),
112 file_tree,
113 script_routes,
114 );
115
116 WorkspaceSnapshot {
117 files,
118 routes,
119 diagnostics: self.diagnostics.clone(),
120 }
121 }
122 fn root_file_nodes(&self) -> Option<ConfigFileView> {
124 let mut nodes = Vec::new();
125
126 if let Some(root_id) = self.ids.id_for(NodeAddress::Root) {
127 nodes.push(ConfigNodeView {
128 id: root_id,
129 source_file: self.root_path.clone(),
130 toml_path: String::new(),
131 display_name: "apimock.toml".to_owned(),
132 kind: NodeKind::RootSetting,
133 validation: NodeValidation::ok(),
134 });
135 }
136
137 if let Some(fb_id) = self.ids.id_for(NodeAddress::FallbackRespondDir) {
138 nodes.push(ConfigNodeView {
139 id: fb_id,
140 source_file: self.root_path.clone(),
141 toml_path: "service.fallback_respond_dir".to_owned(),
142 display_name: self.config.service.fallback_respond_dir.clone(),
143 kind: NodeKind::FileNode,
144 validation: NodeValidation::ok(),
145 });
146 }
147
148 Some(ConfigFileView {
149 path: self.root_path.clone(),
150 display_name: file_basename(&self.root_path),
151 kind: ConfigFileKind::Root,
152 nodes,
153 })
154 }
155
156 fn rule_set_file_view(&self, rs_idx: usize, rule_set: &RuleSet) -> ConfigFileView {
157 let file_path = PathBuf::from(rule_set.file_path.as_str());
158 let mut nodes: Vec<ConfigNodeView> = Vec::new();
159
160 if let Some(rs_id) = self
162 .ids
163 .id_for(NodeAddress::RuleSet { rule_set: rs_idx })
164 {
165 nodes.push(ConfigNodeView {
166 id: rs_id,
167 source_file: file_path.clone(),
168 toml_path: String::new(),
169 display_name: file_basename(&file_path),
170 kind: NodeKind::RuleSet,
171 validation: NodeValidation::ok(),
172 });
173 }
174
175 for (rule_idx, rule) in rule_set.rules.iter().enumerate() {
177 if let Some(rule_id) = self.ids.id_for(NodeAddress::Rule {
178 rule_set: rs_idx,
179 rule: rule_idx,
180 }) {
181 let url_path_label = rule
182 .when
183 .request
184 .url_path
185 .as_ref()
186 .map(|u| u.value.as_str())
187 .unwrap_or_default();
188 let display = if url_path_label.is_empty() {
189 format!("Rule #{}", rule_idx + 1)
190 } else {
191 url_path_label.to_owned()
192 };
193 nodes.push(ConfigNodeView {
194 id: rule_id,
195 source_file: file_path.clone(),
196 toml_path: format!("rules[{}]", rule_idx),
197 display_name: display,
198 kind: NodeKind::Rule,
199 validation: NodeValidation::ok(),
200 });
201 }
202
203 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
204 rule_set: rs_idx,
205 rule: rule_idx,
206 }) {
207 nodes.push(ConfigNodeView {
208 id: resp_id,
209 source_file: file_path.clone(),
210 toml_path: format!("rules[{}].respond", rule_idx),
211 display_name: summarise_respond(&rule.respond),
212 kind: NodeKind::Respond,
213 validation: respond_node_validation(&rule.respond, rule_set, rule_idx, rs_idx),
214 });
215 }
216 }
217
218 ConfigFileView {
219 path: file_path.clone(),
220 display_name: file_basename(&file_path),
221 kind: ConfigFileKind::RuleSet,
222 nodes,
223 }
224 }
225}
226
227fn summarise_respond(respond: &apimock_routing::Respond) -> String {
228 if let Some(p) = respond.file_path.as_ref() {
229 return format!("file: {}", p);
230 }
231 if let Some(t) = respond.text.as_ref() {
232 const LIMIT: usize = 40;
233 if t.chars().count() > LIMIT {
234 let truncated: String = t.chars().take(LIMIT).collect();
235 return format!("text: {}…", truncated);
236 }
237 return format!("text: {}", t);
238 }
239 if let Some(s) = respond.status.as_ref() {
240 return format!("status: {}", s);
241 }
242 "(empty)".to_owned()
243}