1use std::collections::HashMap;
67use std::fmt::Debug;
68use std::hash::Hash;
69
70use serde::{Deserialize, Serialize};
71
72use super::map::{AddResult, GraphMap, MapNodeId, MapState};
73
74use crate::actions::{Action, ActionParams};
75
76#[derive(Debug, Clone, Default, Serialize, Deserialize)]
102pub struct ActionNodeData {
103 pub action_name: String,
105 pub target: Option<String>,
107 pub args: HashMap<String, String>,
109 pub discovery: Option<serde_json::Value>,
111}
112
113impl ActionNodeData {
114 pub fn new(action_name: impl Into<String>) -> Self {
116 Self {
117 action_name: action_name.into(),
118 target: None,
119 args: HashMap::new(),
120 discovery: None,
121 }
122 }
123
124 pub fn with_target(mut self, target: impl Into<String>) -> Self {
126 self.target = Some(target.into());
127 self
128 }
129
130 pub fn with_arg(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
132 self.args.insert(key.into(), value.into());
133 self
134 }
135
136 pub fn with_discovery(mut self, discovery: serde_json::Value) -> Self {
138 self.discovery = Some(discovery);
139 self
140 }
141
142 pub fn from_input(input: &dyn MutationInput) -> Self {
144 let discovery = match input.result() {
146 ExplorationResult::Discover(children) => Some(serde_json::Value::Array(
147 children
148 .iter()
149 .map(|s| serde_json::Value::String(s.clone()))
150 .collect(),
151 )),
152 _ => None,
153 };
154 Self {
155 action_name: input.action_name().to_string(),
156 target: input.target().map(|s| s.to_string()),
157 args: HashMap::new(),
158 discovery,
159 }
160 }
161}
162
163impl From<&ActionNodeData> for Action {
165 fn from(data: &ActionNodeData) -> Self {
166 Action {
167 name: data.action_name.clone(),
168 params: ActionParams {
169 target: data.target.clone(),
170 args: data.args.clone(),
171 data: Vec::new(),
172 },
173 }
174 }
175}
176
177impl From<ActionNodeData> for Action {
179 fn from(data: ActionNodeData) -> Self {
180 Action {
181 name: data.action_name,
182 params: ActionParams {
183 target: data.target,
184 args: data.args,
185 data: Vec::new(),
186 },
187 }
188 }
189}
190
191pub trait ActionExtractor {
201 fn action_name(&self) -> &str;
203
204 fn target(&self) -> Option<&str>;
206
207 fn extract(&self) -> (&str, Option<&str>) {
209 (self.action_name(), self.target())
210 }
211}
212
213impl ActionExtractor for ActionNodeData {
214 fn action_name(&self) -> &str {
215 &self.action_name
216 }
217
218 fn target(&self) -> Option<&str> {
219 self.target.as_deref()
220 }
221}
222
223#[derive(Debug, Clone, PartialEq)]
245pub enum ExplorationResult {
246 Discover(Vec<String>),
253
254 Success,
261
262 Fail(Option<String>),
270}
271
272impl ExplorationResult {
273 pub fn is_success(&self) -> bool {
275 matches!(
276 self,
277 ExplorationResult::Discover(_) | ExplorationResult::Success
278 )
279 }
280
281 pub fn is_fail(&self) -> bool {
283 matches!(self, ExplorationResult::Fail(_))
284 }
285
286 pub fn children(&self) -> Option<&[String]> {
288 match self {
289 ExplorationResult::Discover(children) => Some(children),
290 _ => None,
291 }
292 }
293
294 pub fn error(&self) -> Option<&str> {
296 match self {
297 ExplorationResult::Fail(Some(msg)) => Some(msg),
298 _ => None,
299 }
300 }
301}
302
303pub trait MutationInput {
319 fn node_id(&self) -> MapNodeId;
321
322 fn action_name(&self) -> &str;
324
325 fn target(&self) -> Option<&str>;
327
328 fn result(&self) -> &ExplorationResult;
332
333 fn dedup_key(&self) -> String {
337 let target = self.target().unwrap_or("_no_target_");
338 format!("{}:{}", self.action_name(), target)
339 }
340}
341
342#[derive(Debug, Clone)]
351pub enum MapUpdate<N, E, S: MapState> {
352 AddChild {
356 parent: MapNodeId,
358 edge_data: E,
360 node_data: N,
362 node_state: S,
364 dedup_key: String,
366 },
367
368 Close(MapNodeId),
373
374 Noop,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
384pub enum MapUpdateResult {
385 NodeCreated(MapNodeId),
387 NodeClosed(MapNodeId),
389 Ignored,
391 Error(String),
393}
394
395impl<N, E, S> GraphMap<N, E, S>
400where
401 N: Debug + Clone,
402 E: Debug + Clone,
403 S: MapState,
404{
405 pub fn apply_update<K, F>(&mut self, update: MapUpdate<N, E, S>, key_fn: F) -> MapUpdateResult
407 where
408 K: Hash,
409 F: Fn(&str) -> K,
410 {
411 match update {
412 MapUpdate::AddChild {
413 parent,
414 edge_data,
415 node_data,
416 node_state,
417 dedup_key,
418 } => {
419 let key = key_fn(&dedup_key);
420 match self.add_child_if_absent(parent, edge_data, node_data, node_state, |_| key) {
421 Ok(AddResult::Added(id)) => MapUpdateResult::NodeCreated(id),
422 Ok(AddResult::AlreadyExists(_)) => MapUpdateResult::Ignored,
423 Err(e) => MapUpdateResult::Error(e.to_string()),
424 }
425 }
426 MapUpdate::Close(node_id) => {
427 self.close_with_cascade_up(node_id);
428 MapUpdateResult::NodeClosed(node_id)
429 }
430 MapUpdate::Noop => MapUpdateResult::Ignored,
431 }
432 }
433
434 pub fn apply_updates<K, F>(
436 &mut self,
437 updates: Vec<MapUpdate<N, E, S>>,
438 key_fn: F,
439 ) -> Vec<MapUpdateResult>
440 where
441 K: Hash + Clone,
442 F: Fn(&str) -> K,
443 {
444 updates
445 .into_iter()
446 .map(|u| self.apply_update(u, &key_fn))
447 .collect()
448 }
449}
450
451#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::exploration::map::{ExplorationMap, MapNodeState};
459
460 struct TestInput {
462 node_id: MapNodeId,
463 action_name: String,
464 target: Option<String>,
465 result: ExplorationResult,
466 }
467
468 impl MutationInput for TestInput {
469 fn node_id(&self) -> MapNodeId {
470 self.node_id
471 }
472
473 fn action_name(&self) -> &str {
474 &self.action_name
475 }
476
477 fn target(&self) -> Option<&str> {
478 self.target.as_deref()
479 }
480
481 fn result(&self) -> &ExplorationResult {
482 &self.result
483 }
484 }
485
486 fn make_test_input(
487 node_id: u64,
488 action_name: &str,
489 target: Option<&str>,
490 success: bool,
491 ) -> TestInput {
492 let result = if success {
493 ExplorationResult::Success
494 } else {
495 ExplorationResult::Fail(None)
496 };
497 TestInput {
498 node_id: MapNodeId::new(node_id),
499 action_name: action_name.to_string(),
500 target: target.map(|s| s.to_string()),
501 result,
502 }
503 }
504
505 #[test]
506 fn test_mutation_input_dedup_key() {
507 let input = make_test_input(0, "grep", Some("src/auth.rs"), true);
508 assert_eq!(input.dedup_key(), "grep:src/auth.rs");
509 }
510
511 #[test]
512 fn test_mutation_input_dedup_key_no_target() {
513 let input = make_test_input(0, "list", None, true);
514 assert_eq!(input.dedup_key(), "list:_no_target_");
515 }
516
517 #[test]
518 fn test_map_update_add_child() {
519 let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
520 let root = map.create_root("root".to_string(), MapNodeState::Open);
521
522 let update = MapUpdate::AddChild {
523 parent: root,
524 edge_data: "edge-1".to_string(),
525 node_data: "child-1".to_string(),
526 node_state: MapNodeState::Open,
527 dedup_key: "child-1".to_string(),
528 };
529
530 let result = map.apply_update(update, |k| k.to_string());
531 assert!(matches!(result, MapUpdateResult::NodeCreated(_)));
532 assert_eq!(map.node_count(), 2);
533 }
534
535 #[test]
536 fn test_map_update_add_child_dedup() {
537 let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
538 let root = map.create_root("root".to_string(), MapNodeState::Open);
539
540 let update1 = MapUpdate::AddChild {
542 parent: root,
543 edge_data: "edge-1".to_string(),
544 node_data: "child-1".to_string(),
545 node_state: MapNodeState::Open,
546 dedup_key: "same-key".to_string(),
547 };
548 let result1 = map.apply_update(update1, |k| k.to_string());
549 assert!(matches!(result1, MapUpdateResult::NodeCreated(_)));
550
551 let update2 = MapUpdate::AddChild {
553 parent: root,
554 edge_data: "edge-2".to_string(),
555 node_data: "child-2".to_string(),
556 node_state: MapNodeState::Open,
557 dedup_key: "same-key".to_string(),
558 };
559 let result2 = map.apply_update(update2, |k| k.to_string());
560 assert!(matches!(result2, MapUpdateResult::Ignored));
561
562 assert_eq!(map.node_count(), 2); }
564
565 #[test]
566 fn test_map_update_close() {
567 let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
568 let root = map.create_root("root".to_string(), MapNodeState::Open);
569
570 let update = MapUpdate::<String, String, MapNodeState>::Close(root);
571 let result = map.apply_update(update, |k| k.to_string());
572
573 assert!(matches!(result, MapUpdateResult::NodeClosed(_)));
574 assert!(map.frontiers().is_empty());
575 }
576
577 #[test]
578 fn test_map_update_noop() {
579 let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
580 let _root = map.create_root("root".to_string(), MapNodeState::Open);
581
582 let update = MapUpdate::<String, String, MapNodeState>::Noop;
583 let result = map.apply_update(update, |k| k.to_string());
584
585 assert!(matches!(result, MapUpdateResult::Ignored));
586 }
587
588 #[test]
589 fn test_apply_updates_batch() {
590 let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
591 let root = map.create_root("root".to_string(), MapNodeState::Open);
592
593 let updates = vec![
594 MapUpdate::AddChild {
595 parent: root,
596 edge_data: "e1".to_string(),
597 node_data: "c1".to_string(),
598 node_state: MapNodeState::Open,
599 dedup_key: "c1".to_string(),
600 },
601 MapUpdate::AddChild {
602 parent: root,
603 edge_data: "e2".to_string(),
604 node_data: "c2".to_string(),
605 node_state: MapNodeState::Open,
606 dedup_key: "c2".to_string(),
607 },
608 MapUpdate::Noop,
609 ];
610
611 let results = map.apply_updates(updates, |k| k.to_string());
612
613 assert_eq!(results.len(), 3);
614 assert!(matches!(results[0], MapUpdateResult::NodeCreated(_)));
615 assert!(matches!(results[1], MapUpdateResult::NodeCreated(_)));
616 assert!(matches!(results[2], MapUpdateResult::Ignored));
617 assert_eq!(map.node_count(), 3);
618 }
619}