apimock_config/workspace/
edit.rs1pub mod id_shift;
22pub mod payload;
23
24use std::path::Path;
25
26use apimock_routing::RuleSet;
27
28use crate::error::ApplyError;
29use crate::view::{ApplyResult, EditCommand, EditValue, NodeId};
30
31use super::Workspace;
32use super::id_index::NodeAddress;
33use payload::{
34 build_rule_from_payload, build_respond_from_payload, internal_path_err, value_as_integer,
35 value_as_string,
36};
37
38impl Workspace {
39 pub fn apply(&mut self, cmd: EditCommand) -> Result<ApplyResult, ApplyError> {
59 let (changed_nodes, requires_reload) = match cmd {
60 EditCommand::AddRuleSet { path } => {
61 let ids = self.cmd_add_rule_set(path)?;
62 (ids, true)
63 }
64 EditCommand::RemoveRuleSet { id } => {
65 let ids = self.cmd_remove_rule_set(id)?;
66 (ids, true)
67 }
68 EditCommand::AddRule { parent, rule } => {
69 let ids = self.cmd_add_rule(parent, rule)?;
70 (ids, true)
71 }
72 EditCommand::UpdateRule { id, rule } => {
73 let ids = self.cmd_update_rule(id, rule)?;
74 (ids, true)
75 }
76 EditCommand::DeleteRule { id } => {
77 let ids = self.cmd_delete_rule(id)?;
78 (ids, true)
79 }
80 EditCommand::MoveRule { id, new_index } => {
81 let ids = self.cmd_move_rule(id, new_index)?;
82 (ids, true)
83 }
84 EditCommand::UpdateRespond { id, respond } => {
85 let ids = self.cmd_update_respond(id, respond)?;
86 (ids, true)
87 }
88 EditCommand::UpdateRootSetting { key, value } => {
89 let ids = self.cmd_update_root_setting(key, value)?;
90 (ids, true)
96 }
97 };
98
99 let diagnostics = self.collect_diagnostics();
104
105 Ok(ApplyResult {
106 changed_nodes,
107 diagnostics,
108 requires_reload,
109 })
110 }
111
112 fn cmd_add_rule_set(&mut self, path: String) -> Result<Vec<NodeId>, ApplyError> {
115 let relative_dir = self.config_relative_dir().map_err(internal_path_err)?;
118 let joined = Path::new(&relative_dir).join(&path);
119 let path_str = joined.to_str().ok_or_else(|| ApplyError::InvalidPayload {
120 reason: format!(
121 "path contains non-UTF-8 bytes: {}",
122 joined.to_string_lossy()
123 ),
124 })?;
125
126 let next_idx = self.config.service.rule_sets.len();
127 let new_rule_set = RuleSet::new(path_str, relative_dir.as_str(), next_idx)
128 .map_err(|e| ApplyError::InvalidPayload {
129 reason: format!("failed to load rule set `{}`: {}", path, e),
130 })?;
131
132 let file_paths = self
135 .config
136 .service
137 .rule_sets_file_paths
138 .get_or_insert_with(Vec::new);
139 file_paths.push(path.clone());
140
141 let new_len = self.config.service.rule_sets.len() + 1;
142 self.config.service.rule_sets.push(new_rule_set);
143
144 let rs_addr = NodeAddress::RuleSet {
146 rule_set: next_idx,
147 };
148 let rs_id = self.ids.insert(rs_addr);
149 let mut changed = vec![rs_id];
150 let new_rs = &self.config.service.rule_sets[next_idx];
151 for rule_idx in 0..new_rs.rules.len() {
152 let r_id = self.ids.insert(NodeAddress::Rule {
153 rule_set: next_idx,
154 rule: rule_idx,
155 });
156 let resp_id = self.ids.insert(NodeAddress::Respond {
157 rule_set: next_idx,
158 rule: rule_idx,
159 });
160 changed.push(r_id);
161 changed.push(resp_id);
162 }
163 debug_assert_eq!(new_len, self.config.service.rule_sets.len());
166
167 Ok(changed)
168 }
169
170 fn cmd_remove_rule_set(&mut self, id: NodeId) -> Result<Vec<NodeId>, ApplyError> {
171 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
172 let NodeAddress::RuleSet { rule_set: idx } = addr else {
173 return Err(ApplyError::WrongNodeKind {
174 id,
175 reason: "expected a rule set id".to_owned(),
176 });
177 };
178
179 let len = self.config.service.rule_sets.len();
180 if idx >= len {
181 return Err(ApplyError::InvalidPayload {
182 reason: format!("rule set index {} out of range (len={})", idx, len),
183 });
184 }
185
186 let mut changed: Vec<NodeId> = Vec::new();
189 changed.push(id);
191 if let Some(removed_rs) = self.config.service.rule_sets.get(idx) {
192 for rule_idx in 0..removed_rs.rules.len() {
193 if let Some(r_id) = self.ids.id_for(NodeAddress::Rule {
194 rule_set: idx,
195 rule: rule_idx,
196 }) {
197 changed.push(r_id);
198 }
199 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
200 rule_set: idx,
201 rule: rule_idx,
202 }) {
203 changed.push(resp_id);
204 }
205 }
206 }
207
208 self.config.service.rule_sets.remove(idx);
210 if let Some(paths) = self.config.service.rule_sets_file_paths.as_mut() {
211 if idx < paths.len() {
212 paths.remove(idx);
213 }
214 }
215
216 self.shift_rule_sets_down(idx);
221
222 for shifted_idx in idx..self.config.service.rule_sets.len() {
226 if let Some(shifted_id) = self
227 .ids
228 .id_for(NodeAddress::RuleSet {
229 rule_set: shifted_idx,
230 })
231 {
232 if !changed.contains(&shifted_id) {
233 changed.push(shifted_id);
234 }
235 }
236 }
237
238 Ok(changed)
239 }
240
241 fn cmd_add_rule(
242 &mut self,
243 parent: NodeId,
244 rule_payload: crate::view::RulePayload,
245 ) -> Result<Vec<NodeId>, ApplyError> {
246 let addr = self
247 .ids
248 .lookup(parent)
249 .ok_or(ApplyError::UnknownNode { id: parent })?;
250 let NodeAddress::RuleSet { rule_set: rs_idx } = addr else {
251 return Err(ApplyError::WrongNodeKind {
252 id: parent,
253 reason: "expected a rule set id (parent for AddRule must be a rule set)".to_owned(),
254 });
255 };
256
257 let rule_set = self
258 .config
259 .service
260 .rule_sets
261 .get_mut(rs_idx)
262 .ok_or_else(|| ApplyError::InvalidPayload {
263 reason: format!("rule set index {} out of range", rs_idx),
264 })?;
265
266 let new_rule = build_rule_from_payload(rule_payload, rule_set, rs_idx)?;
267 let new_rule_idx = rule_set.rules.len();
268 rule_set.rules.push(new_rule);
269
270 let r_id = self.ids.insert(NodeAddress::Rule {
271 rule_set: rs_idx,
272 rule: new_rule_idx,
273 });
274 let resp_id = self.ids.insert(NodeAddress::Respond {
275 rule_set: rs_idx,
276 rule: new_rule_idx,
277 });
278 Ok(vec![parent, r_id, resp_id])
279 }
280
281 fn cmd_update_rule(
282 &mut self,
283 id: NodeId,
284 rule_payload: crate::view::RulePayload,
285 ) -> Result<Vec<NodeId>, ApplyError> {
286 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
287 let NodeAddress::Rule {
288 rule_set: rs_idx,
289 rule: rule_idx,
290 } = addr
291 else {
292 return Err(ApplyError::WrongNodeKind {
293 id,
294 reason: "expected a rule id".to_owned(),
295 });
296 };
297
298 let rule_set = self
299 .config
300 .service
301 .rule_sets
302 .get_mut(rs_idx)
303 .ok_or_else(|| ApplyError::InvalidPayload {
304 reason: format!("rule set index {} out of range", rs_idx),
305 })?;
306
307 let new_rule = build_rule_from_payload(rule_payload, rule_set, rs_idx)?;
308 *rule_set
309 .rules
310 .get_mut(rule_idx)
311 .ok_or_else(|| ApplyError::InvalidPayload {
312 reason: format!("rule index {} out of range", rule_idx),
313 })? = new_rule;
314
315 let resp_id = self
316 .ids
317 .id_for(NodeAddress::Respond {
318 rule_set: rs_idx,
319 rule: rule_idx,
320 })
321 .unwrap_or_else(NodeId::new);
322 Ok(vec![id, resp_id])
323 }
324
325 fn cmd_delete_rule(&mut self, id: NodeId) -> Result<Vec<NodeId>, ApplyError> {
326 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
327 let NodeAddress::Rule {
328 rule_set: rs_idx,
329 rule: rule_idx,
330 } = addr
331 else {
332 return Err(ApplyError::WrongNodeKind {
333 id,
334 reason: "expected a rule id".to_owned(),
335 });
336 };
337
338 let rule_set = self
339 .config
340 .service
341 .rule_sets
342 .get_mut(rs_idx)
343 .ok_or_else(|| ApplyError::InvalidPayload {
344 reason: format!("rule set index {} out of range", rs_idx),
345 })?;
346
347 if rule_idx >= rule_set.rules.len() {
348 return Err(ApplyError::InvalidPayload {
349 reason: format!("rule index {} out of range", rule_idx),
350 });
351 }
352
353 let mut changed: Vec<NodeId> = Vec::new();
355 changed.push(id);
356 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
357 rule_set: rs_idx,
358 rule: rule_idx,
359 }) {
360 changed.push(resp_id);
361 }
362
363 rule_set.rules.remove(rule_idx);
364 self.shift_rules_down(rs_idx, rule_idx);
365
366 let new_rule_count = self.config.service.rule_sets[rs_idx].rules.len();
368 for shifted_idx in rule_idx..new_rule_count {
369 if let Some(r_id) = self.ids.id_for(NodeAddress::Rule {
370 rule_set: rs_idx,
371 rule: shifted_idx,
372 }) {
373 if !changed.contains(&r_id) {
374 changed.push(r_id);
375 }
376 }
377 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
378 rule_set: rs_idx,
379 rule: shifted_idx,
380 }) {
381 if !changed.contains(&resp_id) {
382 changed.push(resp_id);
383 }
384 }
385 }
386
387 Ok(changed)
388 }
389
390 fn cmd_move_rule(&mut self, id: NodeId, new_index: usize) -> Result<Vec<NodeId>, ApplyError> {
391 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
392 let NodeAddress::Rule {
393 rule_set: rs_idx,
394 rule: old_idx,
395 } = addr
396 else {
397 return Err(ApplyError::WrongNodeKind {
398 id,
399 reason: "expected a rule id".to_owned(),
400 });
401 };
402
403 let rule_set = self
404 .config
405 .service
406 .rule_sets
407 .get_mut(rs_idx)
408 .ok_or_else(|| ApplyError::InvalidPayload {
409 reason: format!("rule set index {} out of range", rs_idx),
410 })?;
411
412 if old_idx >= rule_set.rules.len() || new_index >= rule_set.rules.len() {
413 return Err(ApplyError::InvalidPayload {
414 reason: format!(
415 "move out of bounds: old_idx={}, new_index={}, len={}",
416 old_idx,
417 new_index,
418 rule_set.rules.len()
419 ),
420 });
421 }
422 if old_idx == new_index {
423 return Ok(vec![id]);
424 }
425
426 let rule = rule_set.rules.remove(old_idx);
428 rule_set.rules.insert(new_index, rule);
429
430 self.reorder_rule_ids(rs_idx, old_idx, new_index);
435
436 let lo = old_idx.min(new_index);
439 let hi = old_idx.max(new_index);
440 let mut changed: Vec<NodeId> = Vec::new();
441 for idx in lo..=hi {
442 if let Some(r_id) = self.ids.id_for(NodeAddress::Rule {
443 rule_set: rs_idx,
444 rule: idx,
445 }) {
446 changed.push(r_id);
447 }
448 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
449 rule_set: rs_idx,
450 rule: idx,
451 }) {
452 changed.push(resp_id);
453 }
454 }
455 Ok(changed)
456 }
457
458 fn cmd_update_respond(
459 &mut self,
460 id: NodeId,
461 respond: crate::view::RespondPayload,
462 ) -> Result<Vec<NodeId>, ApplyError> {
463 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
464 let NodeAddress::Respond {
465 rule_set: rs_idx,
466 rule: rule_idx,
467 } = addr
468 else {
469 return Err(ApplyError::WrongNodeKind {
470 id,
471 reason: "expected a respond id".to_owned(),
472 });
473 };
474
475 let rule = self
476 .config
477 .service
478 .rule_sets
479 .get_mut(rs_idx)
480 .and_then(|rs| rs.rules.get_mut(rule_idx))
481 .ok_or_else(|| ApplyError::InvalidPayload {
482 reason: format!(
483 "rule at rule_set={}, rule={} not found",
484 rs_idx, rule_idx
485 ),
486 })?;
487
488 rule.respond = build_respond_from_payload(respond);
489
490 let rule_set = &self.config.service.rule_sets[rs_idx];
493 let derived = rule_set.rules[rule_idx].compute_derived_fields(rule_set, rule_idx, rs_idx);
494 self.config.service.rule_sets[rs_idx].rules[rule_idx] = derived;
495
496 Ok(vec![id])
497 }
498
499 fn cmd_update_root_setting(
500 &mut self,
501 key: crate::view::RootSettingKey,
502 value: EditValue,
503 ) -> Result<Vec<NodeId>, ApplyError> {
504 use crate::view::RootSettingKey::*;
505
506 match key {
507 ListenerIpAddress => {
508 let s = value_as_string(&value)?;
509 let listener = self.config.listener.get_or_insert_with(Default::default);
510 listener.ip_address = s;
511 }
512 ListenerPort => {
513 let n = value_as_integer(&value)?;
514 if !(0..=u16::MAX as i64).contains(&n) {
515 return Err(ApplyError::InvalidPayload {
516 reason: format!("port {} not in 0..=65535", n),
517 });
518 }
519 let listener = self.config.listener.get_or_insert_with(Default::default);
520 listener.port = n as u16;
521 }
522 ServiceFallbackRespondDir => {
523 let s = value_as_string(&value)?;
524 self.config.service.fallback_respond_dir = s;
525 }
526 ServiceStrategy => {
527 let s = value_as_string(&value)?;
528 match s.as_str() {
532 "first_match" => {
533 self.config.service.strategy =
534 Some(apimock_routing::Strategy::FirstMatch);
535 }
536 other => {
537 return Err(ApplyError::InvalidPayload {
538 reason: format!("unknown strategy: {}", other),
539 });
540 }
541 }
542 }
543 }
544
545 let id = self
547 .ids
548 .id_for(NodeAddress::Root)
549 .expect("root id seeded at load");
550 Ok(vec![id])
551 }
552}