use std::collections::hash_map::DefaultHasher;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MapNodeId(pub u64);
impl MapNodeId {
pub fn new(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MapEdgeId(pub u64);
impl MapEdgeId {
pub fn new(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum MapError {
#[error("Node not found: {0:?}")]
NodeNotFound(MapNodeId),
#[error("Edge not found: {0:?}")]
EdgeNotFound(MapEdgeId),
#[error("Node ID conflict: {0:?}")]
NodeConflict(MapNodeId),
#[error("Edge ID conflict: {0:?}")]
EdgeConflict(MapEdgeId),
#[error("Invalid state transition: {from:?} -> {to:?}")]
InvalidStateTransition {
node_id: MapNodeId,
from: MapNodeState,
to: MapNodeState,
},
#[error("Root node already exists: {0:?}")]
RootAlreadyExists(MapNodeId),
#[error("Parent node not found: {0:?}")]
ParentNotFound(MapNodeId),
#[error("Node already closed: {0:?}")]
AlreadyClosed(MapNodeId),
}
pub type MapResult<T> = Result<T, MapError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddResult<T> {
Added(T),
AlreadyExists(T),
}
impl<T> AddResult<T> {
pub fn is_added(&self) -> bool {
matches!(self, Self::Added(_))
}
pub fn is_already_exists(&self) -> bool {
matches!(self, Self::AlreadyExists(_))
}
pub fn into_inner(self) -> T {
match self {
Self::Added(v) | Self::AlreadyExists(v) => v,
}
}
pub fn as_inner(&self) -> &T {
match self {
Self::Added(v) | Self::AlreadyExists(v) => v,
}
}
}
pub trait ExplorationMap {
type Node: Debug + Clone;
type Edge: Debug + Clone;
type Update;
type Result;
fn apply(&mut self, update: Self::Update) -> Self::Result;
fn apply_batch(&mut self, updates: Vec<Self::Update>) -> Vec<Self::Result> {
updates.into_iter().map(|u| self.apply(u)).collect()
}
fn get(&self, id: MapNodeId) -> Option<&Self::Node>;
fn get_mut(&mut self, id: MapNodeId) -> Option<&mut Self::Node>;
fn node_count(&self) -> usize;
fn frontiers(&self) -> Vec<MapNodeId>;
}
pub trait GraphExplorationMap: ExplorationMap {
fn get_edge(&self, id: MapEdgeId) -> Option<&Self::Edge>;
fn outgoing_edges(&self, node: MapNodeId) -> Vec<MapEdgeId>;
fn incoming_edge(&self, node: MapNodeId) -> Option<MapEdgeId>;
fn edge_count(&self) -> usize;
fn root(&self) -> Option<MapNodeId>;
}
pub trait HierarchicalMap: ExplorationMap {
fn parent(&self, node: MapNodeId) -> Option<MapNodeId>;
fn children(&self, node: MapNodeId) -> Vec<MapNodeId>;
fn depth(&self, node: MapNodeId) -> u32;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MapApplyResult<N> {
NodeCreated { node_id: MapNodeId, data: N },
NodeUpdated { node_id: MapNodeId },
NodeCompleted { node_id: MapNodeId },
NodeDeadEnd { node_id: MapNodeId },
Failed {
node_id: MapNodeId,
can_retry: bool,
reason: String,
},
Ignored,
}
pub trait MapState: Clone + std::fmt::Debug {
fn is_expandable(&self) -> bool;
fn is_closed(&self) -> bool;
fn closed() -> Self;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum MapNodeState {
#[default]
Open,
Closed,
}
impl MapState for MapNodeState {
fn is_expandable(&self) -> bool {
matches!(self, Self::Open)
}
fn is_closed(&self) -> bool {
matches!(self, Self::Closed)
}
fn closed() -> Self {
Self::Closed
}
}
impl MapNodeState {
pub fn is_open(&self) -> bool {
matches!(self, Self::Open)
}
pub fn is_closed(&self) -> bool {
matches!(self, Self::Closed)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapNode<N, S: MapState> {
pub id: MapNodeId,
pub data: N,
pub state: S,
pub depth: u32,
pub parent_edge: Option<MapEdgeId>,
}
impl<N, S: MapState> MapNode<N, S> {
pub fn new(id: MapNodeId, data: N, state: S) -> Self {
Self {
id,
data,
state,
depth: 0,
parent_edge: None,
}
}
pub fn with_state(mut self, state: S) -> Self {
self.state = state;
self
}
pub fn with_depth(mut self, depth: u32) -> Self {
self.depth = depth;
self
}
pub fn with_parent(mut self, edge: MapEdgeId) -> Self {
self.parent_edge = Some(edge);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapEdge<T> {
pub id: MapEdgeId,
pub from: MapNodeId,
pub to: Option<MapNodeId>,
pub data: T,
pub attempts: u32,
pub succeeded: bool,
}
impl<T> MapEdge<T> {
pub fn new(id: MapEdgeId, from: MapNodeId, data: T) -> Self {
Self {
id,
from,
to: None,
data,
attempts: 0,
succeeded: false,
}
}
pub fn mark_success(&mut self, to: MapNodeId) {
self.succeeded = true;
self.to = Some(to);
self.attempts += 1;
}
pub fn mark_failure(&mut self) {
self.succeeded = false;
self.attempts += 1;
}
}
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct GraphMap<N, E, S: MapState> {
nodes: HashMap<MapNodeId, MapNode<N, S>>,
edges: HashMap<MapEdgeId, MapEdge<E>>,
expandables: HashSet<MapNodeId>,
root: Option<MapNodeId>,
next_node_id: u64,
next_edge_id: u64,
index: HashMap<u64, MapNodeId>,
}
impl<N, E, S: MapState> Default for GraphMap<N, E, S> {
fn default() -> Self {
Self::new()
}
}
impl<N, E, S: MapState> GraphMap<N, E, S> {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
edges: HashMap::new(),
expandables: HashSet::new(),
root: None,
next_node_id: 0,
next_edge_id: 0,
index: HashMap::new(),
}
}
fn compute_hash<K: Hash>(key: &K) -> u64 {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
hasher.finish()
}
pub fn index_node<K, F>(&mut self, node_id: MapNodeId, key_fn: F)
where
K: Hash,
F: FnOnce(&N) -> K,
{
if let Some(node) = self.nodes.get(&node_id) {
let key = key_fn(&node.data);
let hash = Self::compute_hash(&key);
self.index.insert(hash, node_id);
}
}
pub fn get_by_key<K: Hash>(&self, key: &K) -> Option<MapNodeId> {
let hash = Self::compute_hash(key);
self.index.get(&hash).copied()
}
pub fn contains_key<K: Hash>(&self, key: &K) -> bool {
self.get_by_key(key).is_some()
}
pub fn clear_index(&mut self) {
self.index.clear();
}
pub fn rebuild_index<K, F>(&mut self, key_fn: F)
where
K: Hash,
F: Fn(&N) -> K,
{
self.index.clear();
for (&node_id, node) in &self.nodes {
let key = key_fn(&node.data);
let hash = Self::compute_hash(&key);
self.index.insert(hash, node_id);
}
}
pub fn find_node<F>(&self, predicate: F) -> Option<MapNodeId>
where
F: Fn(&MapNode<N, S>) -> bool,
{
self.nodes.values().find(|n| predicate(n)).map(|n| n.id)
}
pub fn find_nodes<F>(&self, predicate: F) -> Vec<MapNodeId>
where
F: Fn(&MapNode<N, S>) -> bool,
{
self.nodes
.values()
.filter(|n| predicate(n))
.map(|n| n.id)
.collect()
}
pub fn find_by_data(&self, data: &N) -> Option<MapNodeId>
where
N: PartialEq,
{
self.nodes.values().find(|n| &n.data == data).map(|n| n.id)
}
pub fn find_by_state(&self, state: &S) -> Vec<MapNodeId>
where
S: PartialEq,
{
self.nodes
.values()
.filter(|n| &n.state == state)
.map(|n| n.id)
.collect()
}
pub fn find_nodes_by_state<F>(&self, predicate: F) -> Vec<MapNodeId>
where
F: Fn(&S) -> bool,
{
self.nodes
.values()
.filter(|n| predicate(&n.state))
.map(|n| n.id)
.collect()
}
pub fn create_root_if_absent<K, F>(
&mut self,
data: N,
state: S,
key_fn: F,
) -> AddResult<MapNodeId>
where
K: Hash,
F: FnOnce(&N) -> K,
{
let key = key_fn(&data);
let hash = Self::compute_hash(&key);
if let Some(&existing) = self.index.get(&hash) {
return AddResult::AlreadyExists(existing);
}
let id = self.create_root(data, state);
self.index.insert(hash, id);
AddResult::Added(id)
}
pub fn add_child_if_absent<K, F>(
&mut self,
parent: MapNodeId,
edge_data: E,
node_data: N,
node_state: S,
key_fn: F,
) -> MapResult<AddResult<MapNodeId>>
where
K: Hash,
F: FnOnce(&N) -> K,
{
let key = key_fn(&node_data);
let hash = Self::compute_hash(&key);
if let Some(&existing) = self.index.get(&hash) {
return Ok(AddResult::AlreadyExists(existing));
}
let parent_depth = self
.nodes
.get(&parent)
.map(|n| n.depth)
.ok_or(MapError::ParentNotFound(parent))?;
let edge_id = self.add_edge(parent, edge_data);
let id = self.add_node(node_data, node_state, edge_id, parent_depth + 1);
if let Some(edge) = self.edges.get_mut(&edge_id) {
edge.to = Some(id);
}
self.index.insert(hash, id);
Ok(AddResult::Added(id))
}
pub fn create_root(&mut self, data: N, state: S) -> MapNodeId {
let id = self.allocate_node_id();
let node = MapNode::new(id, data, state.clone());
self.nodes.insert(id, node);
if state.is_expandable() {
self.expandables.insert(id);
}
self.root = Some(id);
id
}
fn add_node(&mut self, data: N, state: S, parent_edge: MapEdgeId, depth: u32) -> MapNodeId {
let id = self.allocate_node_id();
let node = MapNode::new(id, data, state.clone())
.with_parent(parent_edge)
.with_depth(depth);
self.nodes.insert(id, node);
if state.is_expandable() {
self.expandables.insert(id);
}
id
}
fn add_edge(&mut self, from: MapNodeId, data: E) -> MapEdgeId {
let id = self.allocate_edge_id();
let edge = MapEdge::new(id, from, data);
self.edges.insert(id, edge);
id
}
fn allocate_node_id(&mut self) -> MapNodeId {
let id = MapNodeId(self.next_node_id);
self.next_node_id += 1;
id
}
fn allocate_edge_id(&mut self) -> MapEdgeId {
let id = MapEdgeId(self.next_edge_id);
self.next_edge_id += 1;
id
}
pub fn set_state(&mut self, id: MapNodeId, state: S) {
if let Some(node) = self.nodes.get_mut(&id) {
let was_expandable = node.state.is_expandable();
node.state = state;
let is_expandable = node.state.is_expandable();
if was_expandable && !is_expandable {
self.expandables.remove(&id);
} else if !was_expandable && is_expandable {
self.expandables.insert(id);
}
}
}
pub fn add_nodes(&mut self, nodes: Vec<(N, S)>) -> Vec<MapNodeId> {
nodes
.into_iter()
.map(|(data, state)| {
if self.root.is_none() {
self.create_root(data, state)
} else {
let id = self.allocate_node_id();
let is_expandable = state.is_expandable();
let node = MapNode::new(id, data, state);
self.nodes.insert(id, node);
if is_expandable {
self.expandables.insert(id);
}
id
}
})
.collect()
}
pub fn set_states(&mut self, ids: &[MapNodeId], state: S) -> Vec<MapNodeId> {
let mut changed = Vec::new();
for &id in ids {
if self.nodes.contains_key(&id) {
self.set_state(id, state.clone());
changed.push(id);
}
}
changed
}
pub fn close_nodes(&mut self, ids: &[MapNodeId]) -> Vec<MapNodeId> {
self.set_states(ids, S::closed())
}
pub fn cascade_down(&mut self, id: MapNodeId) -> Vec<MapNodeId> {
let mut closed = Vec::new();
self.cascade_down_recursive(id, &mut closed);
closed
}
fn cascade_down_recursive(&mut self, id: MapNodeId, closed: &mut Vec<MapNodeId>) {
if !self.nodes.contains_key(&id) {
return;
}
let children: Vec<MapNodeId> = self.children_internal(id);
for child in children {
self.cascade_down_recursive(child, closed);
}
self.set_state(id, S::closed());
closed.push(id);
}
pub fn children(&self, node: MapNodeId) -> Vec<MapNodeId> {
self.children_internal(node)
}
fn children_internal(&self, node: MapNodeId) -> Vec<MapNodeId> {
self.edges
.values()
.filter(|e| e.from == node && e.to.is_some())
.filter_map(|e| e.to)
.collect()
}
pub fn cascade_up_if_all_closed(&mut self, id: MapNodeId) -> Vec<MapNodeId> {
let mut closed = Vec::new();
self.cascade_up_recursive(id, &mut closed);
closed
}
fn cascade_up_recursive(&mut self, id: MapNodeId, closed: &mut Vec<MapNodeId>) {
let parent = self.parent_internal(id);
if let Some(parent_id) = parent {
let all_children_closed = self.children_internal(parent_id).iter().all(|&child| {
self.nodes
.get(&child)
.map(|n| n.state.is_closed())
.unwrap_or(true)
});
if all_children_closed {
self.set_state(parent_id, S::closed());
closed.push(parent_id);
self.cascade_up_recursive(parent_id, closed);
}
}
}
pub fn parent(&self, node: MapNodeId) -> Option<MapNodeId> {
self.parent_internal(node)
}
fn parent_internal(&self, node: MapNodeId) -> Option<MapNodeId> {
self.nodes
.get(&node)
.and_then(|n| n.parent_edge)
.and_then(|edge_id| self.edges.get(&edge_id))
.map(|e| e.from)
}
pub fn close_with_cascade_up(&mut self, id: MapNodeId) -> Vec<MapNodeId> {
let mut closed = Vec::new();
if self.nodes.contains_key(&id) {
self.set_state(id, S::closed());
closed.push(id);
closed.extend(self.cascade_up_if_all_closed(id));
}
closed
}
pub fn get_nodes(&self, ids: &[MapNodeId]) -> Vec<&MapNode<N, S>> {
ids.iter().filter_map(|id| self.nodes.get(id)).collect()
}
pub fn get_node_data(&self, ids: &[MapNodeId]) -> Vec<&N> {
ids.iter()
.filter_map(|id| self.nodes.get(id).map(|n| &n.data))
.collect()
}
pub fn expandables(&self) -> impl Iterator<Item = MapNodeId> + '_ {
self.expandables.iter().copied()
}
pub fn expandables_count(&self) -> usize {
self.expandables.len()
}
pub fn closed_nodes(&self) -> Vec<MapNodeId> {
self.nodes
.values()
.filter(|n| n.state.is_closed())
.map(|n| n.id)
.collect()
}
pub fn working_nodes(&self) -> Vec<MapNodeId> {
self.nodes
.values()
.filter(|n| !n.state.is_expandable() && !n.state.is_closed())
.map(|n| n.id)
.collect()
}
}
#[derive(Debug, Clone)]
pub enum GraphMapUpdate<N, E, S: MapState> {
AddChild {
parent: MapNodeId,
edge_data: E,
node_data: N,
node_state: S,
dedup_key: String,
},
SetState {
node_id: MapNodeId,
state: S,
},
Noop,
}
impl<N, E, S> ExplorationMap for GraphMap<N, E, S>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
{
type Node = MapNode<N, S>;
type Edge = MapEdge<E>;
type Update = GraphMapUpdate<N, E, S>;
type Result = MapApplyResult<N>;
fn apply(&mut self, update: Self::Update) -> Self::Result {
match update {
GraphMapUpdate::AddChild {
parent,
edge_data,
node_data,
node_state,
dedup_key,
} => {
if self.contains_key(&dedup_key) {
return MapApplyResult::Ignored;
}
let parent_depth = match self.nodes.get(&parent) {
Some(node) => node.depth,
None => {
return MapApplyResult::Failed {
node_id: parent,
can_retry: false,
reason: "Parent node not found".to_string(),
}
}
};
let edge_id = self.add_edge(parent, edge_data);
let new_node_id =
self.add_node(node_data.clone(), node_state, edge_id, parent_depth + 1);
if let Some(edge) = self.edges.get_mut(&edge_id) {
edge.mark_success(new_node_id);
}
self.index_node(new_node_id, |_| dedup_key);
MapApplyResult::NodeCreated {
node_id: new_node_id,
data: node_data,
}
}
GraphMapUpdate::SetState { node_id, state } => {
self.set_state(node_id, state);
MapApplyResult::NodeUpdated { node_id }
}
GraphMapUpdate::Noop => MapApplyResult::Ignored,
}
}
fn get(&self, id: MapNodeId) -> Option<&Self::Node> {
self.nodes.get(&id)
}
fn get_mut(&mut self, id: MapNodeId) -> Option<&mut Self::Node> {
self.nodes.get_mut(&id)
}
fn node_count(&self) -> usize {
self.nodes.len()
}
fn frontiers(&self) -> Vec<MapNodeId> {
self.expandables.iter().copied().collect()
}
}
impl<N, E, S> GraphExplorationMap for GraphMap<N, E, S>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
{
fn get_edge(&self, id: MapEdgeId) -> Option<&Self::Edge> {
self.edges.get(&id)
}
fn outgoing_edges(&self, node: MapNodeId) -> Vec<MapEdgeId> {
self.edges
.values()
.filter(|e| e.from == node)
.map(|e| e.id)
.collect()
}
fn incoming_edge(&self, node: MapNodeId) -> Option<MapEdgeId> {
self.nodes.get(&node).and_then(|n| n.parent_edge)
}
fn edge_count(&self) -> usize {
self.edges.len()
}
fn root(&self) -> Option<MapNodeId> {
self.root
}
}
impl<N, E, S> HierarchicalMap for GraphMap<N, E, S>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
{
fn parent(&self, node: MapNodeId) -> Option<MapNodeId> {
self.nodes
.get(&node)
.and_then(|n| n.parent_edge)
.and_then(|edge_id| self.edges.get(&edge_id))
.map(|e| e.from)
}
fn children(&self, node: MapNodeId) -> Vec<MapNodeId> {
self.edges
.values()
.filter(|e| e.from == node && e.to.is_some())
.filter_map(|e| e.to)
.collect()
}
fn depth(&self, node: MapNodeId) -> u32 {
self.nodes.get(&node).map(|n| n.depth).unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_map_node_state() {
assert!(MapNodeState::Open.is_open());
assert!(!MapNodeState::Open.is_closed());
assert!(!MapNodeState::Closed.is_open());
assert!(MapNodeState::Closed.is_closed());
}
#[test]
fn test_map_node_builder() {
let node = MapNode::new(MapNodeId(1), "task-1", MapNodeState::Open).with_depth(2);
assert_eq!(node.id, MapNodeId(1));
assert_eq!(node.data, "task-1");
assert_eq!(node.state, MapNodeState::Open);
assert_eq!(node.depth, 2);
assert!(node.parent_edge.is_none()); }
#[test]
fn test_map_node_with_parent() {
let node = MapNode::new(MapNodeId(2), "child", MapNodeState::Open)
.with_parent(MapEdgeId(0))
.with_depth(1);
assert_eq!(node.depth, 1);
assert_eq!(node.parent_edge, Some(MapEdgeId(0)));
}
#[test]
fn test_map_edge() {
let mut edge = MapEdge::new(MapEdgeId(1), MapNodeId(0), "action-1");
assert!(!edge.succeeded);
assert_eq!(edge.attempts, 0);
edge.mark_success(MapNodeId(1));
assert!(edge.succeeded);
assert_eq!(edge.attempts, 1);
assert_eq!(edge.to, Some(MapNodeId(1)));
}
#[test]
fn test_graph_map_create_root() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
assert_eq!(map.node_count(), 1);
assert_eq!(map.frontiers().len(), 1);
assert_eq!(map.root(), Some(root));
let node = map.get(root).unwrap();
assert_eq!(node.data, "root");
assert_eq!(node.state, MapNodeState::Open);
assert_eq!(node.depth, 0);
assert!(node.parent_edge.is_none()); }
#[test]
fn test_graph_map_create_root_closed() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Closed);
assert_eq!(map.node_count(), 1);
assert!(map.frontiers().is_empty()); assert_eq!(map.root(), Some(root));
}
#[test]
fn test_graph_map_apply_add_child() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let update = GraphMapUpdate::AddChild {
parent: root,
edge_data: "action-1".to_string(),
node_data: "child-1".to_string(),
node_state: MapNodeState::Open,
dedup_key: "child-1".to_string(),
};
let result = map.apply(update);
match result {
MapApplyResult::NodeCreated { node_id, data } => {
assert_eq!(data, "child-1");
let node = map.get(node_id).unwrap();
assert_eq!(node.depth, 1); assert!(node.parent_edge.is_some()); assert_eq!(node.state, MapNodeState::Open);
}
_ => panic!("Expected NodeCreated, got {:?}", result),
}
assert_eq!(map.node_count(), 2); assert_eq!(map.edge_count(), 1);
assert_eq!(map.frontiers().len(), 2);
let root_node = map.get(root).unwrap();
assert_eq!(root_node.state, MapNodeState::Open);
}
#[test]
fn test_graph_map_add_child_parent_not_found() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let update = GraphMapUpdate::AddChild {
parent: MapNodeId(999), edge_data: "action".to_string(),
node_data: "child".to_string(),
node_state: MapNodeState::Open,
dedup_key: "child".to_string(),
};
let result = map.apply(update);
match result {
MapApplyResult::Failed {
node_id, reason, ..
} => {
assert_eq!(node_id, MapNodeId(999));
assert!(reason.contains("not found"));
}
_ => panic!("Expected Failed, got {:?}", result),
}
assert_eq!(map.node_count(), 0);
assert_eq!(map.edge_count(), 0);
}
#[test]
fn test_graph_map_add_child_duplicate_key() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let update1 = GraphMapUpdate::AddChild {
parent: root,
edge_data: "action-1".to_string(),
node_data: "child".to_string(),
node_state: MapNodeState::Open,
dedup_key: "unique-key".to_string(),
};
let result1 = map.apply(update1);
assert!(matches!(result1, MapApplyResult::NodeCreated { .. }));
let update2 = GraphMapUpdate::AddChild {
parent: root,
edge_data: "action-2".to_string(),
node_data: "child-different-data".to_string(), node_state: MapNodeState::Open,
dedup_key: "unique-key".to_string(), };
let result2 = map.apply(update2);
assert!(matches!(result2, MapApplyResult::Ignored));
assert_eq!(map.node_count(), 2);
assert_eq!(map.edge_count(), 1);
}
#[test]
fn test_graph_map_apply_set_state() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let update = GraphMapUpdate::AddChild {
parent: root,
edge_data: "action-1".to_string(),
node_data: "child".to_string(),
node_state: MapNodeState::Open,
dedup_key: "child".to_string(),
};
let child = match map.apply(update) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
_ => panic!("Expected NodeCreated"),
};
assert_eq!(map.frontiers().len(), 2);
let update = GraphMapUpdate::SetState {
node_id: child,
state: MapNodeState::Closed,
};
let result = map.apply(update);
assert!(matches!(result, MapApplyResult::NodeUpdated { .. }));
let child_node = map.get(child).unwrap();
assert_eq!(child_node.state, MapNodeState::Closed);
assert_eq!(map.frontiers().len(), 1);
assert!(map.frontiers().contains(&root));
assert!(!map.frontiers().contains(&child));
}
#[test]
fn test_graph_map_set_state_reopen() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Closed);
assert!(map.frontiers().is_empty());
let update = GraphMapUpdate::SetState {
node_id: root,
state: MapNodeState::Open,
};
map.apply(update);
assert_eq!(map.frontiers().len(), 1);
assert!(map.frontiers().contains(&root));
}
#[test]
fn test_graph_map_hierarchical() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let node1 = match map.apply(GraphMapUpdate::AddChild {
parent: root,
edge_data: "edge-1".to_string(),
node_data: "node-1".to_string(),
node_state: MapNodeState::Open,
dedup_key: "node-1".to_string(),
}) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
r => panic!("Expected NodeCreated, got {:?}", r),
};
let node2 = match map.apply(GraphMapUpdate::AddChild {
parent: node1,
edge_data: "edge-2".to_string(),
node_data: "node-2".to_string(),
node_state: MapNodeState::Open,
dedup_key: "node-2".to_string(),
}) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
r => panic!("Expected NodeCreated, got {:?}", r),
};
assert_eq!(map.parent(node1), Some(root));
assert_eq!(map.parent(node2), Some(node1));
assert_eq!(map.parent(root), None);
assert_eq!(map.children(root), vec![node1]);
assert_eq!(map.children(node1), vec![node2]);
assert!(map.children(node2).is_empty());
assert_eq!(map.depth(root), 0);
assert_eq!(map.depth(node1), 1);
assert_eq!(map.depth(node2), 2);
}
#[test]
fn test_find_node() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let target_id = match map.apply(GraphMapUpdate::AddChild {
parent: root,
edge_data: "edge".to_string(),
node_data: "target".to_string(),
node_state: MapNodeState::Open,
dedup_key: "target".to_string(),
}) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
r => panic!("Expected NodeCreated, got {:?}", r),
};
let found = map.find_node(|n| n.data == "target");
assert_eq!(found, Some(target_id));
let not_found = map.find_node(|n| n.data == "nonexistent");
assert!(not_found.is_none());
}
#[test]
fn test_find_nodes() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let id1 = match map.apply(GraphMapUpdate::AddChild {
parent: root,
edge_data: "edge-1".to_string(),
node_data: "match-1".to_string(),
node_state: MapNodeState::Open,
dedup_key: "match-1".to_string(),
}) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
r => panic!("Expected NodeCreated, got {:?}", r),
};
let id2 = match map.apply(GraphMapUpdate::AddChild {
parent: id1,
edge_data: "edge-2".to_string(),
node_data: "match-2".to_string(),
node_state: MapNodeState::Open,
dedup_key: "match-2".to_string(),
}) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
r => panic!("Expected NodeCreated, got {:?}", r),
};
let found = map.find_nodes(|n| n.data.starts_with("match"));
assert_eq!(found.len(), 2);
assert!(found.contains(&id1));
assert!(found.contains(&id2));
}
#[test]
fn test_find_by_data() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let found = map.find_by_data(&"root".to_string());
assert_eq!(found, Some(root));
let not_found = map.find_by_data(&"nonexistent".to_string());
assert!(not_found.is_none());
}
#[test]
fn test_index_basic() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("file:auth.rs".to_string(), MapNodeState::Open);
map.index_node(root, |data| data.clone());
let found = map.get_by_key(&"file:auth.rs".to_string());
assert_eq!(found, Some(root));
let not_found = map.get_by_key(&"file:other.rs".to_string());
assert!(not_found.is_none());
}
#[test]
fn test_contains_key() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("task-1".to_string(), MapNodeState::Open);
map.index_node(root, |data| data.clone());
assert!(map.contains_key(&"task-1".to_string()));
assert!(!map.contains_key(&"task-2".to_string()));
}
#[test]
fn test_rebuild_index() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map.create_root("root".to_string(), MapNodeState::Open);
let child = match map.apply(GraphMapUpdate::AddChild {
parent: root,
edge_data: "edge".to_string(),
node_data: "child".to_string(),
node_state: MapNodeState::Open,
dedup_key: "child".to_string(),
}) {
MapApplyResult::NodeCreated { node_id, .. } => node_id,
r => panic!("Expected NodeCreated, got {:?}", r),
};
map.rebuild_index(|data| data.clone());
assert_eq!(map.get_by_key(&"root".to_string()), Some(root));
assert_eq!(map.get_by_key(&"child".to_string()), Some(child));
}
#[test]
fn test_create_root_if_absent() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let result1 =
map.create_root_if_absent("task-1".to_string(), MapNodeState::Open, |d| d.clone());
assert!(result1.is_added());
let id1 = result1.into_inner();
let result2 =
map.create_root_if_absent("task-1".to_string(), MapNodeState::Open, |d| d.clone());
assert!(result2.is_already_exists());
assert_eq!(result2.into_inner(), id1);
let result3 =
map.create_root_if_absent("task-2".to_string(), MapNodeState::Open, |d| d.clone());
assert!(result3.is_added());
assert_ne!(result3.into_inner(), id1);
}
#[test]
fn test_add_child_if_absent() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map
.create_root_if_absent("root".to_string(), MapNodeState::Open, |d| d.clone())
.into_inner();
let result1 = map
.add_child_if_absent(
root,
"edge-1".to_string(),
"child-1".to_string(),
MapNodeState::Open,
|d| d.clone(),
)
.unwrap();
assert!(result1.is_added());
let child1 = result1.into_inner();
let result2 = map
.add_child_if_absent(
root,
"edge-2".to_string(),
"child-1".to_string(),
MapNodeState::Open,
|d| d.clone(),
)
.unwrap();
assert!(result2.is_already_exists());
assert_eq!(result2.into_inner(), child1);
let result3 = map
.add_child_if_absent(
root,
"edge-3".to_string(),
"child-2".to_string(),
MapNodeState::Open,
|d| d.clone(),
)
.unwrap();
assert!(result3.is_added());
}
#[test]
fn test_add_child_if_absent_parent_not_found() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let result = map.add_child_if_absent(
MapNodeId(999),
"edge".to_string(),
"child".to_string(),
MapNodeState::Open,
|d| d.clone(),
);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), MapError::ParentNotFound(_)));
}
#[test]
fn test_dedup_prevents_infinite_loop() {
let mut map: GraphMap<String, String, MapNodeState> = GraphMap::new();
let root = map
.create_root_if_absent("search:auth".to_string(), MapNodeState::Open, |d| d.clone())
.into_inner();
let result_a = map.add_child_if_absent(
root,
"grep".to_string(),
"file:auth.rs".to_string(),
MapNodeState::Open,
|d| d.clone(),
);
assert!(result_a.unwrap().is_added());
let result_b = map.add_child_if_absent(
root,
"grep".to_string(),
"file:auth.rs".to_string(),
MapNodeState::Open,
|d| d.clone(),
);
assert!(result_b.unwrap().is_already_exists());
let result_c = map.add_child_if_absent(
root,
"grep".to_string(),
"file:user.rs".to_string(),
MapNodeState::Open,
|d| d.clone(),
);
assert!(result_c.unwrap().is_added());
assert_eq!(map.node_count(), 3);
}
#[test]
fn test_add_result_methods() {
let added: AddResult<i32> = AddResult::Added(42);
assert!(added.is_added());
assert!(!added.is_already_exists());
assert_eq!(*added.as_inner(), 42);
assert_eq!(added.into_inner(), 42);
let exists: AddResult<i32> = AddResult::AlreadyExists(42);
assert!(!exists.is_added());
assert!(exists.is_already_exists());
assert_eq!(*exists.as_inner(), 42);
assert_eq!(exists.into_inner(), 42);
}
}