1pub 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_bool,
35 value_as_integer, value_as_string, value_as_string_list,
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)
91 }
92
93 EditCommand::AddHeaderCondition { rule_id, condition } => {
95 let ids = self.cmd_add_header_condition(rule_id, condition)?;
96 (ids, true)
97 }
98 EditCommand::UpdateHeaderCondition { id, condition } => {
99 let ids = self.cmd_update_header_condition(id, condition)?;
100 (ids, true)
101 }
102 EditCommand::RemoveHeaderCondition { id } => {
103 let ids = self.cmd_remove_header_condition(id)?;
104 (ids, true)
105 }
106 EditCommand::AddBodyCondition { rule_id, condition } => {
107 let ids = self.cmd_add_body_condition(rule_id, condition)?;
108 (ids, true)
109 }
110 EditCommand::UpdateBodyCondition { id, condition } => {
111 let ids = self.cmd_update_body_condition(id, condition)?;
112 (ids, true)
113 }
114 EditCommand::RemoveBodyCondition { id } => {
115 let ids = self.cmd_remove_body_condition(id)?;
116 (ids, true)
117 }
118 };
119
120 let diagnostics = self.collect_diagnostics();
125
126 Ok(ApplyResult {
127 changed_nodes,
128 diagnostics,
129 requires_reload,
130 })
131 }
132
133 fn cmd_add_rule_set(&mut self, path: String) -> Result<Vec<NodeId>, ApplyError> {
136 let relative_dir = self.config_relative_dir().map_err(internal_path_err)?;
139 let joined = Path::new(&relative_dir).join(&path);
140 let path_str = joined.to_str().ok_or_else(|| ApplyError::InvalidPayload {
141 reason: format!(
142 "path contains non-UTF-8 bytes: {}",
143 joined.to_string_lossy()
144 ),
145 })?;
146
147 let next_idx = self.config.service.rule_sets.len();
148 let new_rule_set = RuleSet::new(path_str, relative_dir.as_str(), next_idx)
149 .map_err(|e| ApplyError::InvalidPayload {
150 reason: format!("failed to load rule set `{}`: {}", path, e),
151 })?;
152
153 let file_paths = self
156 .config
157 .service
158 .rule_sets_file_paths
159 .get_or_insert_with(Vec::new);
160 file_paths.push(path.clone());
161
162 let new_len = self.config.service.rule_sets.len() + 1;
163 self.config.service.rule_sets.push(new_rule_set);
164
165 let rs_addr = NodeAddress::RuleSet {
167 rule_set: next_idx,
168 };
169 let rs_id = self.ids.insert(rs_addr);
170 let mut changed = vec![rs_id];
171 let new_rs = &self.config.service.rule_sets[next_idx];
172 for rule_idx in 0..new_rs.rules.len() {
173 let r_id = self.ids.insert(NodeAddress::Rule {
174 rule_set: next_idx,
175 rule: rule_idx,
176 });
177 let resp_id = self.ids.insert(NodeAddress::Respond {
178 rule_set: next_idx,
179 rule: rule_idx,
180 });
181 changed.push(r_id);
182 changed.push(resp_id);
183 }
184 debug_assert_eq!(new_len, self.config.service.rule_sets.len());
187
188 Ok(changed)
189 }
190
191 fn cmd_remove_rule_set(&mut self, id: NodeId) -> Result<Vec<NodeId>, ApplyError> {
192 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
193 let NodeAddress::RuleSet { rule_set: idx } = addr else {
194 return Err(ApplyError::WrongNodeKind {
195 id,
196 reason: "expected a rule set id".to_owned(),
197 });
198 };
199
200 let len = self.config.service.rule_sets.len();
201 if idx >= len {
202 return Err(ApplyError::InvalidPayload {
203 reason: format!("rule set index {} out of range (len={})", idx, len),
204 });
205 }
206
207 let mut changed: Vec<NodeId> = Vec::new();
210 changed.push(id);
212 if let Some(removed_rs) = self.config.service.rule_sets.get(idx) {
213 for rule_idx in 0..removed_rs.rules.len() {
214 if let Some(r_id) = self.ids.id_for(NodeAddress::Rule {
215 rule_set: idx,
216 rule: rule_idx,
217 }) {
218 changed.push(r_id);
219 }
220 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
221 rule_set: idx,
222 rule: rule_idx,
223 }) {
224 changed.push(resp_id);
225 }
226 }
227 }
228
229 self.config.service.rule_sets.remove(idx);
231 if let Some(paths) = self.config.service.rule_sets_file_paths.as_mut() {
232 if idx < paths.len() {
233 paths.remove(idx);
234 }
235 }
236
237 self.shift_rule_sets_down(idx);
242
243 for shifted_idx in idx..self.config.service.rule_sets.len() {
247 if let Some(shifted_id) = self
248 .ids
249 .id_for(NodeAddress::RuleSet {
250 rule_set: shifted_idx,
251 })
252 {
253 if !changed.contains(&shifted_id) {
254 changed.push(shifted_id);
255 }
256 }
257 }
258
259 Ok(changed)
260 }
261
262 fn cmd_add_rule(
263 &mut self,
264 parent: NodeId,
265 rule_payload: crate::view::RulePayload,
266 ) -> Result<Vec<NodeId>, ApplyError> {
267 let addr = self
268 .ids
269 .lookup(parent)
270 .ok_or(ApplyError::UnknownNode { id: parent })?;
271 let NodeAddress::RuleSet { rule_set: rs_idx } = addr else {
272 return Err(ApplyError::WrongNodeKind {
273 id: parent,
274 reason: "expected a rule set id (parent for AddRule must be a rule set)".to_owned(),
275 });
276 };
277
278 let rule_set = self
279 .config
280 .service
281 .rule_sets
282 .get_mut(rs_idx)
283 .ok_or_else(|| ApplyError::InvalidPayload {
284 reason: format!("rule set index {} out of range", rs_idx),
285 })?;
286
287 let new_rule = build_rule_from_payload(rule_payload, rule_set, rs_idx, None)?;
288 let new_rule_idx = rule_set.rules.len();
289 rule_set.rules.push(new_rule);
290
291 let r_id = self.ids.insert(NodeAddress::Rule {
292 rule_set: rs_idx,
293 rule: new_rule_idx,
294 });
295 let resp_id = self.ids.insert(NodeAddress::Respond {
296 rule_set: rs_idx,
297 rule: new_rule_idx,
298 });
299 Ok(vec![parent, r_id, resp_id])
300 }
301
302 fn cmd_update_rule(
303 &mut self,
304 id: NodeId,
305 rule_payload: crate::view::RulePayload,
306 ) -> Result<Vec<NodeId>, ApplyError> {
307 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
308 let NodeAddress::Rule {
309 rule_set: rs_idx,
310 rule: rule_idx,
311 } = addr
312 else {
313 return Err(ApplyError::WrongNodeKind {
314 id,
315 reason: "expected a rule id".to_owned(),
316 });
317 };
318
319 let rule_set = self
320 .config
321 .service
322 .rule_sets
323 .get_mut(rs_idx)
324 .ok_or_else(|| ApplyError::InvalidPayload {
325 reason: format!("rule set index {} out of range", rs_idx),
326 })?;
327
328 let existing = rule_set.rules.get(rule_idx).cloned();
333 let new_rule = build_rule_from_payload(
334 rule_payload,
335 rule_set,
336 rs_idx,
337 existing.as_ref(),
338 )?;
339 *rule_set
340 .rules
341 .get_mut(rule_idx)
342 .ok_or_else(|| ApplyError::InvalidPayload {
343 reason: format!("rule index {} out of range", rule_idx),
344 })? = new_rule;
345
346 let resp_id = self
347 .ids
348 .id_for(NodeAddress::Respond {
349 rule_set: rs_idx,
350 rule: rule_idx,
351 })
352 .unwrap_or_else(NodeId::new);
353 Ok(vec![id, resp_id])
354 }
355
356 fn cmd_delete_rule(&mut self, id: NodeId) -> Result<Vec<NodeId>, ApplyError> {
357 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
358 let NodeAddress::Rule {
359 rule_set: rs_idx,
360 rule: rule_idx,
361 } = addr
362 else {
363 return Err(ApplyError::WrongNodeKind {
364 id,
365 reason: "expected a rule id".to_owned(),
366 });
367 };
368
369 let rule_set = self
370 .config
371 .service
372 .rule_sets
373 .get_mut(rs_idx)
374 .ok_or_else(|| ApplyError::InvalidPayload {
375 reason: format!("rule set index {} out of range", rs_idx),
376 })?;
377
378 if rule_idx >= rule_set.rules.len() {
379 return Err(ApplyError::InvalidPayload {
380 reason: format!("rule index {} out of range", rule_idx),
381 });
382 }
383
384 let mut changed: Vec<NodeId> = Vec::new();
386 changed.push(id);
387 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
388 rule_set: rs_idx,
389 rule: rule_idx,
390 }) {
391 changed.push(resp_id);
392 }
393
394 rule_set.rules.remove(rule_idx);
395 self.shift_rules_down(rs_idx, rule_idx);
396
397 let new_rule_count = self.config.service.rule_sets[rs_idx].rules.len();
399 for shifted_idx in rule_idx..new_rule_count {
400 if let Some(r_id) = self.ids.id_for(NodeAddress::Rule {
401 rule_set: rs_idx,
402 rule: shifted_idx,
403 }) {
404 if !changed.contains(&r_id) {
405 changed.push(r_id);
406 }
407 }
408 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
409 rule_set: rs_idx,
410 rule: shifted_idx,
411 }) {
412 if !changed.contains(&resp_id) {
413 changed.push(resp_id);
414 }
415 }
416 }
417
418 Ok(changed)
419 }
420
421 fn cmd_move_rule(&mut self, id: NodeId, new_index: usize) -> Result<Vec<NodeId>, ApplyError> {
422 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
423 let NodeAddress::Rule {
424 rule_set: rs_idx,
425 rule: old_idx,
426 } = addr
427 else {
428 return Err(ApplyError::WrongNodeKind {
429 id,
430 reason: "expected a rule id".to_owned(),
431 });
432 };
433
434 let rule_set = self
435 .config
436 .service
437 .rule_sets
438 .get_mut(rs_idx)
439 .ok_or_else(|| ApplyError::InvalidPayload {
440 reason: format!("rule set index {} out of range", rs_idx),
441 })?;
442
443 if old_idx >= rule_set.rules.len() || new_index >= rule_set.rules.len() {
444 return Err(ApplyError::InvalidPayload {
445 reason: format!(
446 "move out of bounds: old_idx={}, new_index={}, len={}",
447 old_idx,
448 new_index,
449 rule_set.rules.len()
450 ),
451 });
452 }
453 if old_idx == new_index {
454 return Ok(vec![id]);
455 }
456
457 let rule = rule_set.rules.remove(old_idx);
459 rule_set.rules.insert(new_index, rule);
460
461 self.reorder_rule_ids(rs_idx, old_idx, new_index);
466
467 let lo = old_idx.min(new_index);
470 let hi = old_idx.max(new_index);
471 let mut changed: Vec<NodeId> = Vec::new();
472 for idx in lo..=hi {
473 if let Some(r_id) = self.ids.id_for(NodeAddress::Rule {
474 rule_set: rs_idx,
475 rule: idx,
476 }) {
477 changed.push(r_id);
478 }
479 if let Some(resp_id) = self.ids.id_for(NodeAddress::Respond {
480 rule_set: rs_idx,
481 rule: idx,
482 }) {
483 changed.push(resp_id);
484 }
485 }
486 Ok(changed)
487 }
488
489 fn cmd_update_respond(
490 &mut self,
491 id: NodeId,
492 respond: crate::view::RespondPayload,
493 ) -> Result<Vec<NodeId>, ApplyError> {
494 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
495 let NodeAddress::Respond {
496 rule_set: rs_idx,
497 rule: rule_idx,
498 } = addr
499 else {
500 return Err(ApplyError::WrongNodeKind {
501 id,
502 reason: "expected a respond id".to_owned(),
503 });
504 };
505
506 let rule = self
507 .config
508 .service
509 .rule_sets
510 .get_mut(rs_idx)
511 .and_then(|rs| rs.rules.get_mut(rule_idx))
512 .ok_or_else(|| ApplyError::InvalidPayload {
513 reason: format!(
514 "rule at rule_set={}, rule={} not found",
515 rs_idx, rule_idx
516 ),
517 })?;
518
519 rule.respond = build_respond_from_payload(respond);
520
521 let rule_set = &self.config.service.rule_sets[rs_idx];
524 let derived = rule_set.rules[rule_idx].compute_derived_fields(rule_set, rule_idx, rs_idx);
525 self.config.service.rule_sets[rs_idx].rules[rule_idx] = derived;
526
527 Ok(vec![id])
528 }
529
530 fn cmd_update_root_setting(
531 &mut self,
532 key: crate::view::RootSettingKey,
533 value: EditValue,
534 ) -> Result<Vec<NodeId>, ApplyError> {
535 use crate::view::RootSettingKey::*;
536
537 match key {
538 ListenerIpAddress => {
539 let s = value_as_string(&value)?;
540 let listener = self.config.listener.get_or_insert_with(Default::default);
541 listener.ip_address = s;
542 }
543 ListenerPort => {
544 let n = value_as_integer(&value)?;
545 if !(0..=u16::MAX as i64).contains(&n) {
546 return Err(ApplyError::InvalidPayload {
547 reason: format!("port {} not in 0..=65535", n),
548 });
549 }
550 let listener = self.config.listener.get_or_insert_with(Default::default);
551 listener.port = n as u16;
552 }
553 ServiceFallbackRespondDir => {
554 let s = value_as_string(&value)?;
555 self.config.service.fallback_respond_dir = s;
556 }
557 ServiceStrategy => {
558 let s = value_as_string(&value)?;
559 use apimock_routing::Strategy;
560 let strategy = match s.as_str() {
561 "first_match" => Strategy::FirstMatch,
562 "uniform_random" => Strategy::UniformRandom { seed: None },
563 "weighted_random" => Strategy::WeightedRandom { seed: None },
564 "priority" => Strategy::Priority {
565 tiebreaker: apimock_routing::strategy::PriorityTiebreaker::FirstMatch,
566 },
567 "round_robin" => Strategy::RoundRobin,
568 other => {
569 return Err(ApplyError::InvalidPayload {
570 reason: format!("unknown strategy: `{}`", other),
571 });
572 }
573 };
574 self.config.service.strategy = Some(strategy);
575 }
576
577 TlsEnabled => {
579 let enabled = value_as_bool(&value)?;
580 if !enabled {
581 if let Some(listener) = self.config.listener.as_mut() {
583 listener.tls = None;
584 }
585 }
586 }
591 TlsCertFile => {
592 let s = value_as_string(&value)?;
593 let listener = self.config.listener.get_or_insert_with(Default::default);
594 let tls = listener.tls.get_or_insert_with(|| {
595 crate::config::listener_config::tls_config::TlsConfig {
596 cert: String::new(),
597 key: String::new(),
598 port: None,
599 }
600 });
601 tls.cert = s;
602 }
603 TlsKeyFile => {
604 let s = value_as_string(&value)?;
605 let listener = self.config.listener.get_or_insert_with(Default::default);
606 let tls = listener.tls.get_or_insert_with(|| {
607 crate::config::listener_config::tls_config::TlsConfig {
608 cert: String::new(),
609 key: String::new(),
610 port: None,
611 }
612 });
613 tls.key = s;
614 }
615
616 LogLevel => {
618 let s = value_as_string(&value)?;
619 let valid_levels = ["trace", "debug", "info", "warn", "error"];
620 if !valid_levels.contains(&s.as_str()) {
621 return Err(ApplyError::InvalidPayload {
622 reason: format!(
623 "invalid log level `{}` — valid: trace, debug, info, warn, error",
624 s
625 ),
626 });
627 }
628 let _ = s; }
634 LogFile => {
635 let s = value_as_string(&value)?;
636 let _ = s; }
638 LogFormat => {
639 let s = value_as_string(&value)?;
640 let valid_formats = ["text", "json"];
641 if !valid_formats.contains(&s.as_str()) {
642 return Err(ApplyError::InvalidPayload {
643 reason: format!(
644 "invalid log format `{}` — valid: text, json",
645 s
646 ),
647 });
648 }
649 let _ = s; }
651
652 FileTreeShowHidden => {
654 let b = value_as_bool(&value)?;
655 self.config
656 .file_tree_view
657 .get_or_insert_with(Default::default)
658 .show_hidden = b;
659 }
660 FileTreeBuiltinExcludes => {
661 let b = value_as_bool(&value)?;
662 self.config
663 .file_tree_view
664 .get_or_insert_with(Default::default)
665 .builtin_excludes = b;
666 }
667 FileTreeExtraExcludes => {
668 let list = value_as_string_list(&value)?;
669 self.config
670 .file_tree_view
671 .get_or_insert_with(Default::default)
672 .extra_excludes = list;
673 }
674 FileTreeInclude => {
675 let list = value_as_string_list(&value)?;
676 self.config
677 .file_tree_view
678 .get_or_insert_with(Default::default)
679 .include = list;
680 }
681 }
682
683 let id = self
684 .ids
685 .id_for(NodeAddress::Root)
686 .expect("root id seeded at load");
687 Ok(vec![id])
688 }
689
690 fn cmd_add_header_condition(
693 &mut self,
694 rule_id: crate::view::NodeId,
695 payload: crate::view::HeaderConditionPayload,
696 ) -> Result<Vec<crate::view::NodeId>, ApplyError> {
697 use apimock_routing::rule_set::rule::when::condition_statement::ConditionStatement;
698
699 let (rs_idx, rule_idx) = self.find_rule_indices(rule_id)?;
700 let op = payload::header_op_to_routing_pub(payload.op);
701 let value = payload.value.unwrap_or_default();
702 let stmt = ConditionStatement { op: Some(op), value };
703 let name = payload.name.to_lowercase();
704
705 let rule = &mut self.config.service.rule_sets[rs_idx].rules[rule_idx];
707 let headers = rule.when.request.headers.get_or_insert_with(|| {
708 apimock_routing::rule_set::rule::when::request::headers::Headers(
709 indexmap::IndexMap::new(),
710 )
711 });
712 headers.0.insert(name.clone(), stmt);
713
714 let cond_id = self.ids.insert(NodeAddress::HeaderCondition {
715 rule_set: rs_idx, rule: rule_idx, header_name: name,
716 });
717 let rule_id_out = self
718 .ids
719 .id_for(NodeAddress::Rule { rule_set: rs_idx, rule: rule_idx })
720 .unwrap_or(rule_id);
721 Ok(vec![rule_id_out, cond_id])
722 }
723
724 fn cmd_update_header_condition(
725 &mut self,
726 id: crate::view::NodeId,
727 payload: crate::view::HeaderConditionPayload,
728 ) -> Result<Vec<crate::view::NodeId>, ApplyError> {
729 use apimock_routing::rule_set::rule::when::condition_statement::ConditionStatement;
730
731 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
732 let (rs_idx, rule_idx, old_name) = match addr {
733 NodeAddress::HeaderCondition { rule_set, rule, header_name } => {
734 (rule_set, rule, header_name)
735 }
736 _ => return Err(ApplyError::InvalidPayload {
737 reason: "id does not refer to a header condition".to_owned(),
738 }),
739 };
740
741 let op = payload::header_op_to_routing_pub(payload.op);
742 let value = payload.value.unwrap_or_default();
743 let new_name = payload.name.to_lowercase();
744 let stmt = ConditionStatement { op: Some(op), value };
745
746 let rule = &mut self.config.service.rule_sets[rs_idx].rules[rule_idx];
747 let headers = rule.when.request.headers.get_or_insert_with(|| {
748 apimock_routing::rule_set::rule::when::request::headers::Headers(
749 indexmap::IndexMap::new(),
750 )
751 });
752
753 headers.0.shift_remove(&old_name);
755 headers.0.insert(new_name.clone(), stmt);
756
757 let new_id = self.ids.insert(NodeAddress::HeaderCondition {
759 rule_set: rs_idx, rule: rule_idx, header_name: new_name,
760 });
761 Ok(vec![new_id])
762 }
763
764 fn cmd_remove_header_condition(
765 &mut self,
766 id: crate::view::NodeId,
767 ) -> Result<Vec<crate::view::NodeId>, ApplyError> {
768 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
769 let (rs_idx, rule_idx, name) = match addr {
770 NodeAddress::HeaderCondition { rule_set, rule, header_name } => {
771 (rule_set, rule, header_name)
772 }
773 _ => return Err(ApplyError::InvalidPayload {
774 reason: "id does not refer to a header condition".to_owned(),
775 }),
776 };
777
778 let rule = &mut self.config.service.rule_sets[rs_idx].rules[rule_idx];
779 if let Some(headers) = rule.when.request.headers.as_mut() {
780 headers.0.shift_remove(&name);
781 if headers.0.is_empty() {
782 rule.when.request.headers = None;
783 }
784 }
785
786 let rule_id = self
787 .ids
788 .id_for(NodeAddress::Rule { rule_set: rs_idx, rule: rule_idx })
789 .unwrap_or(id);
790 Ok(vec![rule_id])
791 }
792
793 fn cmd_add_body_condition(
794 &mut self,
795 rule_id: crate::view::NodeId,
796 payload: crate::view::BodyConditionPayload,
797 ) -> Result<Vec<crate::view::NodeId>, ApplyError> {
798 use apimock_routing::rule_set::rule::when::request::body::{
799 Body, BodyConditionStatement, body_kind::BodyKind,
800 };
801
802 let (rs_idx, rule_idx) = self.find_rule_indices(rule_id)?;
803 let op = payload::body_op_to_routing_pub(payload.op);
804 let value = payload::json_value_to_string_pub(&payload.value);
805 let stmt = BodyConditionStatement { op: Some(op), value };
806 let path = payload.path.clone();
807
808 let rule = &mut self.config.service.rule_sets[rs_idx].rules[rule_idx];
809 if rule.when.request.body.is_none() {
810 rule.when.request.body = Some(Body(std::collections::HashMap::new()));
811 }
812 let body_map = rule.when.request.body.as_mut().unwrap();
813 body_map
814 .0
815 .entry(BodyKind::Json)
816 .or_insert_with(indexmap::IndexMap::new)
817 .insert(path.clone(), stmt);
818
819 let cond_id = self.ids.insert(NodeAddress::BodyCondition {
820 rule_set: rs_idx, rule: rule_idx, path,
821 });
822 let rule_id_out = self
823 .ids
824 .id_for(NodeAddress::Rule { rule_set: rs_idx, rule: rule_idx })
825 .unwrap_or(rule_id);
826 Ok(vec![rule_id_out, cond_id])
827 }
828
829 fn cmd_update_body_condition(
830 &mut self,
831 id: crate::view::NodeId,
832 payload: crate::view::BodyConditionPayload,
833 ) -> Result<Vec<crate::view::NodeId>, ApplyError> {
834 use apimock_routing::rule_set::rule::when::request::body::BodyConditionStatement;
835
836 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
837 let (rs_idx, rule_idx, old_path) = match addr {
838 NodeAddress::BodyCondition { rule_set, rule, path } => (rule_set, rule, path),
839 _ => return Err(ApplyError::InvalidPayload {
840 reason: "id does not refer to a body condition".to_owned(),
841 }),
842 };
843
844 let op = payload::body_op_to_routing_pub(payload.op);
845 let value = payload::json_value_to_string_pub(&payload.value);
846 let new_path = payload.path.clone();
847 let stmt = BodyConditionStatement { op: Some(op), value };
848
849 use apimock_routing::rule_set::rule::when::request::body::body_kind::BodyKind;
850 let rule = &mut self.config.service.rule_sets[rs_idx].rules[rule_idx];
851 if let Some(body) = rule.when.request.body.as_mut() {
852 if let Some(json_map) = body.0.get_mut(&BodyKind::Json) {
853 json_map.shift_remove(&old_path);
854 json_map.insert(new_path.clone(), stmt);
855 }
856 }
857
858 let new_id = self.ids.insert(NodeAddress::BodyCondition {
859 rule_set: rs_idx, rule: rule_idx, path: new_path,
860 });
861 Ok(vec![new_id])
862 }
863
864 fn cmd_remove_body_condition(
865 &mut self,
866 id: crate::view::NodeId,
867 ) -> Result<Vec<crate::view::NodeId>, ApplyError> {
868 use apimock_routing::rule_set::rule::when::request::body::body_kind::BodyKind;
869
870 let addr = self.ids.lookup(id).ok_or(ApplyError::UnknownNode { id })?;
871 let (rs_idx, rule_idx, path) = match addr {
872 NodeAddress::BodyCondition { rule_set, rule, path } => (rule_set, rule, path),
873 _ => return Err(ApplyError::InvalidPayload {
874 reason: "id does not refer to a body condition".to_owned(),
875 }),
876 };
877
878 let rule = &mut self.config.service.rule_sets[rs_idx].rules[rule_idx];
879 if let Some(body) = rule.when.request.body.as_mut() {
880 if let Some(json_map) = body.0.get_mut(&BodyKind::Json) {
881 json_map.shift_remove(&path);
882 }
883 }
884
885 let rule_id = self
886 .ids
887 .id_for(NodeAddress::Rule { rule_set: rs_idx, rule: rule_idx })
888 .unwrap_or(id);
889 Ok(vec![rule_id])
890 }
891
892 fn find_rule_indices(
894 &self,
895 rule_id: crate::view::NodeId,
896 ) -> Result<(usize, usize), ApplyError> {
897 match self.ids.lookup(rule_id) {
898 Some(NodeAddress::Rule { rule_set, rule }) => Ok((rule_set, rule)),
899 _ => Err(ApplyError::UnknownNode { id: rule_id }),
900 }
901 }
902}