use crate::cid::{Cid, SerializableCid};
use crate::ipld::Ipld;
use std::collections::{HashMap, HashSet, VecDeque};
pub fn extract_links(ipld: &Ipld) -> Vec<Cid> {
let mut links = Vec::new();
extract_links_recursive(ipld, &mut links, false);
links
}
pub fn collect_all_links(ipld: &Ipld) -> Vec<Cid> {
let mut links = Vec::new();
extract_links_recursive(ipld, &mut links, true);
links
}
pub fn collect_unique_links(ipld: &Ipld) -> HashSet<Cid> {
collect_all_links(ipld).into_iter().collect()
}
fn extract_links_recursive(ipld: &Ipld, links: &mut Vec<Cid>, recursive: bool) {
match ipld {
Ipld::Link(SerializableCid(cid)) => {
links.push(*cid);
}
Ipld::List(items) => {
if recursive {
for item in items {
extract_links_recursive(item, links, true);
}
} else {
for item in items {
if let Ipld::Link(SerializableCid(cid)) = item {
links.push(*cid);
}
}
}
}
Ipld::Map(map) => {
if recursive {
for value in map.values() {
extract_links_recursive(value, links, true);
}
} else {
for value in map.values() {
if let Ipld::Link(SerializableCid(cid)) = value {
links.push(*cid);
}
}
}
}
_ => {}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DagStats {
pub unique_cids: usize,
pub total_links: usize,
pub max_depth: usize,
pub leaf_count: usize,
pub intermediate_count: usize,
}
impl DagStats {
pub fn new() -> Self {
Self::default()
}
pub fn from_ipld(ipld: &Ipld) -> Self {
let all_links = collect_all_links(ipld);
let unique_links: HashSet<_> = all_links.iter().collect();
let depth = calculate_depth(ipld, 0);
let (leaves, intermediates) = count_node_types(ipld);
Self {
unique_cids: unique_links.len(),
total_links: all_links.len(),
max_depth: depth,
leaf_count: leaves,
intermediate_count: intermediates,
}
}
pub fn deduplication_ratio(&self) -> f64 {
if self.total_links == 0 {
return 0.0;
}
let duplicates = self.total_links.saturating_sub(self.unique_cids);
duplicates as f64 / self.total_links as f64
}
}
fn calculate_depth(ipld: &Ipld, current_depth: usize) -> usize {
match ipld {
Ipld::List(items) => {
let max_child_depth = items
.iter()
.map(|item| calculate_depth(item, current_depth + 1))
.max()
.unwrap_or(current_depth);
max_child_depth
}
Ipld::Map(map) => {
let max_child_depth = map
.values()
.map(|value| calculate_depth(value, current_depth + 1))
.max()
.unwrap_or(current_depth);
max_child_depth
}
_ => current_depth,
}
}
fn count_node_types(ipld: &Ipld) -> (usize, usize) {
match ipld {
Ipld::List(items) => {
if items.is_empty() {
return (1, 0); }
let mut leaves = 0;
let mut intermediates = 1; for item in items {
let (l, i) = count_node_types(item);
leaves += l;
intermediates += i;
}
(leaves, intermediates)
}
Ipld::Map(map) => {
if map.is_empty() {
return (1, 0); }
let mut leaves = 0;
let mut intermediates = 1; for value in map.values() {
let (l, i) = count_node_types(value);
leaves += l;
intermediates += i;
}
(leaves, intermediates)
}
_ => (1, 0), }
}
pub fn is_dag(ipld: &Ipld) -> bool {
let mut visited = HashSet::new();
let mut stack = HashSet::new();
has_cycle_dfs(ipld, &mut visited, &mut stack)
}
fn has_cycle_dfs(ipld: &Ipld, visited: &mut HashSet<String>, stack: &mut HashSet<String>) -> bool {
match ipld {
Ipld::Link(SerializableCid(cid)) => {
let cid_str = cid.to_string();
if stack.contains(&cid_str) {
return false; }
if visited.contains(&cid_str) {
return true; }
visited.insert(cid_str.clone());
stack.insert(cid_str.clone());
stack.remove(&cid_str);
true
}
Ipld::List(items) => {
for item in items {
if !has_cycle_dfs(item, visited, stack) {
return false;
}
}
true
}
Ipld::Map(map) => {
for value in map.values() {
if !has_cycle_dfs(value, visited, stack) {
return false;
}
}
true
}
_ => true,
}
}
pub fn find_paths_to_cid(ipld: &Ipld, target_cid: &Cid) -> Vec<Vec<String>> {
let mut paths = Vec::new();
let mut current_path = Vec::new();
find_paths_recursive(ipld, target_cid, &mut current_path, &mut paths);
paths
}
fn find_paths_recursive(
ipld: &Ipld,
target_cid: &Cid,
current_path: &mut Vec<String>,
paths: &mut Vec<Vec<String>>,
) {
match ipld {
Ipld::Link(SerializableCid(cid)) if cid == target_cid => {
paths.push(current_path.clone());
}
Ipld::List(items) => {
for (i, item) in items.iter().enumerate() {
current_path.push(format!("[{}]", i));
find_paths_recursive(item, target_cid, current_path, paths);
current_path.pop();
}
}
Ipld::Map(map) => {
for (key, value) in map {
current_path.push(key.clone());
find_paths_recursive(value, target_cid, current_path, paths);
current_path.pop();
}
}
_ => {}
}
}
pub fn traverse_bfs(root: &Ipld) -> Vec<Ipld> {
let mut result = Vec::new();
let mut queue = VecDeque::new();
queue.push_back(root.clone());
while let Some(node) = queue.pop_front() {
result.push(node.clone());
match &node {
Ipld::List(items) => {
for item in items {
queue.push_back(item.clone());
}
}
Ipld::Map(map) => {
for value in map.values() {
queue.push_back(value.clone());
}
}
_ => {}
}
}
result
}
pub fn traverse_dfs(root: &Ipld) -> Vec<Ipld> {
let mut result = Vec::new();
traverse_dfs_recursive(root, &mut result);
result
}
fn traverse_dfs_recursive(node: &Ipld, result: &mut Vec<Ipld>) {
result.push(node.clone());
match node {
Ipld::List(items) => {
for item in items {
traverse_dfs_recursive(item, result);
}
}
Ipld::Map(map) => {
for value in map.values() {
traverse_dfs_recursive(value, result);
}
}
_ => {}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DagMetrics {
pub avg_branching_factor: f64,
pub max_branching_factor: usize,
pub width: usize,
pub total_nodes: usize,
pub avg_leaf_depth: f64,
}
impl DagMetrics {
pub fn from_ipld(ipld: &Ipld) -> Self {
let mut levels: HashMap<usize, usize> = HashMap::new();
let mut branching_factors = Vec::new();
let mut leaf_depths = Vec::new();
let mut total_nodes = 0;
calculate_metrics(
ipld,
0,
&mut levels,
&mut branching_factors,
&mut leaf_depths,
&mut total_nodes,
);
let width = levels.values().copied().max().unwrap_or(0);
let max_branching_factor = branching_factors.iter().copied().max().unwrap_or(0);
let avg_branching_factor = if branching_factors.is_empty() {
0.0
} else {
branching_factors.iter().sum::<usize>() as f64 / branching_factors.len() as f64
};
let avg_leaf_depth = if leaf_depths.is_empty() {
0.0
} else {
leaf_depths.iter().sum::<usize>() as f64 / leaf_depths.len() as f64
};
Self {
avg_branching_factor,
max_branching_factor,
width,
total_nodes,
avg_leaf_depth,
}
}
}
fn calculate_metrics(
ipld: &Ipld,
depth: usize,
levels: &mut HashMap<usize, usize>,
branching_factors: &mut Vec<usize>,
leaf_depths: &mut Vec<usize>,
total_nodes: &mut usize,
) {
*total_nodes += 1;
*levels.entry(depth).or_insert(0) += 1;
match ipld {
Ipld::List(items) => {
if items.is_empty() {
leaf_depths.push(depth);
} else {
branching_factors.push(items.len());
for item in items {
calculate_metrics(
item,
depth + 1,
levels,
branching_factors,
leaf_depths,
total_nodes,
);
}
}
}
Ipld::Map(map) => {
if map.is_empty() {
leaf_depths.push(depth);
} else {
branching_factors.push(map.len());
for value in map.values() {
calculate_metrics(
value,
depth + 1,
levels,
branching_factors,
leaf_depths,
total_nodes,
);
}
}
}
_ => {
leaf_depths.push(depth);
}
}
}
pub fn topological_sort(ipld: &Ipld) -> Vec<Cid> {
let links = collect_all_links(ipld);
let mut result = Vec::new();
let mut visited = HashSet::new();
for cid in links {
if visited.insert(cid) {
result.push(cid);
}
}
result
}
pub fn subgraph_size(ipld: &Ipld) -> usize {
let mut count = 1;
match ipld {
Ipld::List(items) => {
for item in items {
count += subgraph_size(item);
}
}
Ipld::Map(map) => {
for value in map.values() {
count += subgraph_size(value);
}
}
_ => {}
}
count
}
pub fn dag_fanout_by_level(ipld: &Ipld) -> Vec<usize> {
let mut fanout_by_level = Vec::new();
calculate_fanout(ipld, 0, &mut fanout_by_level);
fanout_by_level
}
fn calculate_fanout(ipld: &Ipld, depth: usize, fanout_by_level: &mut Vec<usize>) {
while fanout_by_level.len() <= depth {
fanout_by_level.push(0);
}
match ipld {
Ipld::List(items) => {
fanout_by_level[depth] += items.len();
for item in items {
calculate_fanout(item, depth + 1, fanout_by_level);
}
}
Ipld::Map(map) => {
fanout_by_level[depth] += map.len();
for value in map.values() {
calculate_fanout(value, depth + 1, fanout_by_level);
}
}
_ => {}
}
}
pub fn count_links_by_depth(ipld: &Ipld) -> Vec<usize> {
let mut counts = Vec::new();
count_links_recursive(ipld, 0, &mut counts);
counts
}
fn count_links_recursive(ipld: &Ipld, depth: usize, counts: &mut Vec<usize>) {
while counts.len() <= depth {
counts.push(0);
}
match ipld {
Ipld::Link(_) => {
counts[depth] += 1;
}
Ipld::List(items) => {
for item in items {
count_links_recursive(item, depth + 1, counts);
}
}
Ipld::Map(map) => {
for value in map.values() {
count_links_recursive(value, depth + 1, counts);
}
}
_ => {}
}
}
pub fn filter_dag<F>(ipld: &Ipld, predicate: &F) -> Option<Ipld>
where
F: Fn(&Ipld) -> bool,
{
if !predicate(ipld) {
return None;
}
match ipld {
Ipld::List(items) => {
let filtered_items: Vec<Ipld> = items
.iter()
.filter_map(|item| filter_dag(item, predicate))
.collect();
Some(Ipld::List(filtered_items))
}
Ipld::Map(map) => {
let filtered_map: std::collections::BTreeMap<String, Ipld> = map
.iter()
.filter_map(|(k, v)| filter_dag(v, predicate).map(|filtered| (k.clone(), filtered)))
.collect();
Some(Ipld::Map(filtered_map))
}
other => Some(other.clone()),
}
}
pub fn map_dag<F>(ipld: &Ipld, transform: &F) -> Ipld
where
F: Fn(&Ipld) -> Ipld,
{
let transformed = match ipld {
Ipld::List(items) => {
let mapped_items: Vec<Ipld> =
items.iter().map(|item| map_dag(item, transform)).collect();
Ipld::List(mapped_items)
}
Ipld::Map(map) => {
let mapped_map: std::collections::BTreeMap<String, Ipld> = map
.iter()
.map(|(k, v)| (k.clone(), map_dag(v, transform)))
.collect();
Ipld::Map(mapped_map)
}
other => other.clone(),
};
transform(&transformed)
}
pub fn dag_diff(dag1: &Ipld, dag2: &Ipld) -> (HashSet<Cid>, HashSet<Cid>, HashSet<Cid>) {
let links1 = collect_unique_links(dag1);
let links2 = collect_unique_links(dag2);
let unique_to_first: HashSet<Cid> = links1.difference(&links2).copied().collect();
let unique_to_second: HashSet<Cid> = links2.difference(&links1).copied().collect();
let common: HashSet<Cid> = links1.intersection(&links2).copied().collect();
(unique_to_first, unique_to_second, common)
}
pub fn find_common_links(dag1: &Ipld, dag2: &Ipld) -> HashSet<Cid> {
let links1 = collect_unique_links(dag1);
let links2 = collect_unique_links(dag2);
links1.intersection(&links2).copied().collect()
}
pub fn prune_dag<F>(ipld: &Ipld, should_keep: &F) -> Option<Ipld>
where
F: Fn(&Ipld) -> bool,
{
if !should_keep(ipld) {
return None;
}
match ipld {
Ipld::List(items) => {
let pruned_items: Vec<Ipld> = items
.iter()
.filter_map(|item| prune_dag(item, should_keep))
.collect();
if pruned_items.is_empty() && !items.is_empty() {
None
} else {
Some(Ipld::List(pruned_items))
}
}
Ipld::Map(map) => {
let pruned_map: std::collections::BTreeMap<String, Ipld> = map
.iter()
.filter_map(|(k, v)| prune_dag(v, should_keep).map(|pruned| (k.clone(), pruned)))
.collect();
if pruned_map.is_empty() && !map.is_empty() {
None
} else {
Some(Ipld::Map(pruned_map))
}
}
other => Some(other.clone()),
}
}
pub fn merge_dags(dag1: &Ipld, dag2: &Ipld) -> Ipld {
match (dag1, dag2) {
(Ipld::Map(map1), Ipld::Map(map2)) => {
let mut merged = map1.clone();
for (k, v) in map2 {
merged.insert(k.clone(), v.clone());
}
Ipld::Map(merged)
}
(Ipld::List(list1), Ipld::List(list2)) => {
let mut merged = list1.clone();
merged.extend(list2.clone());
Ipld::List(merged)
}
(_, dag2) => dag2.clone(),
}
}
pub fn count_nodes(ipld: &Ipld) -> usize {
match ipld {
Ipld::List(items) => 1 + items.iter().map(count_nodes).sum::<usize>(),
Ipld::Map(map) => 1 + map.values().map(count_nodes).sum::<usize>(),
_ => 1,
}
}
pub fn dag_depth(ipld: &Ipld) -> usize {
match ipld {
Ipld::List(items) => {
if items.is_empty() {
1
} else {
1 + items.iter().map(dag_depth).max().unwrap_or(0)
}
}
Ipld::Map(map) => {
if map.is_empty() {
1
} else {
1 + map.values().map(dag_depth).max().unwrap_or(0)
}
}
_ => 1,
}
}
pub fn find_leaves(ipld: &Ipld) -> Vec<Ipld> {
match ipld {
Ipld::List(items) => {
if items.is_empty() {
vec![ipld.clone()]
} else {
items.iter().flat_map(find_leaves).collect()
}
}
Ipld::Map(map) => {
if map.is_empty() {
vec![ipld.clone()]
} else {
map.values().flat_map(find_leaves).collect()
}
}
_ => vec![ipld.clone()],
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cid::CidBuilder;
use std::collections::BTreeMap;
#[test]
fn test_extract_links() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let mut map = BTreeMap::new();
map.insert("link1".to_string(), Ipld::link(cid1));
map.insert("link2".to_string(), Ipld::link(cid2));
map.insert("data".to_string(), Ipld::String("hello".to_string()));
let ipld = Ipld::Map(map);
let links = extract_links(&ipld);
assert_eq!(links.len(), 2);
assert!(links.contains(&cid1));
assert!(links.contains(&cid2));
}
#[test]
fn test_collect_all_links_nested() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let cid3 = CidBuilder::new().build(b"test3").unwrap();
let mut inner = BTreeMap::new();
inner.insert("deep_link".to_string(), Ipld::link(cid3));
let mut outer = BTreeMap::new();
outer.insert("link1".to_string(), Ipld::link(cid1));
outer.insert("link2".to_string(), Ipld::link(cid2));
outer.insert("nested".to_string(), Ipld::Map(inner));
let ipld = Ipld::Map(outer);
let links = collect_all_links(&ipld);
assert_eq!(links.len(), 3);
assert!(links.contains(&cid1));
assert!(links.contains(&cid2));
assert!(links.contains(&cid3));
}
#[test]
fn test_collect_unique_links() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let list = vec![
Ipld::link(cid1),
Ipld::link(cid2),
Ipld::link(cid1), ];
let ipld = Ipld::List(list);
let unique = collect_unique_links(&ipld);
assert_eq!(unique.len(), 2);
}
#[test]
fn test_dag_stats() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let mut map = BTreeMap::new();
map.insert("link1".to_string(), Ipld::link(cid1));
map.insert("link2".to_string(), Ipld::link(cid2));
map.insert("dup_link".to_string(), Ipld::link(cid1));
let ipld = Ipld::Map(map);
let stats = DagStats::from_ipld(&ipld);
assert_eq!(stats.unique_cids, 2);
assert_eq!(stats.total_links, 3);
assert!(stats.deduplication_ratio() > 0.0);
}
#[test]
fn test_dag_stats_nested() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let mut inner = BTreeMap::new();
inner.insert("deep".to_string(), Ipld::link(cid2));
let mut outer = BTreeMap::new();
outer.insert("link".to_string(), Ipld::link(cid1));
outer.insert("nested".to_string(), Ipld::Map(inner));
let ipld = Ipld::Map(outer);
let stats = DagStats::from_ipld(&ipld);
assert_eq!(stats.unique_cids, 2);
assert_eq!(stats.max_depth, 2);
}
#[test]
fn test_is_dag() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let mut map = BTreeMap::new();
map.insert("link".to_string(), Ipld::link(cid1));
let ipld = Ipld::Map(map);
assert!(is_dag(&ipld));
}
#[test]
fn test_find_paths_to_cid() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let mut inner = BTreeMap::new();
inner.insert("target".to_string(), Ipld::link(cid1));
let mut outer = BTreeMap::new();
outer.insert("other".to_string(), Ipld::link(cid2));
outer.insert("nested".to_string(), Ipld::Map(inner));
let ipld = Ipld::Map(outer);
let paths = find_paths_to_cid(&ipld, &cid1);
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec!["nested".to_string(), "target".to_string()]);
}
#[test]
fn test_traverse_bfs() {
let list = vec![
Ipld::Integer(1),
Ipld::Integer(2),
Ipld::List(vec![Ipld::Integer(3), Ipld::Integer(4)]),
];
let ipld = Ipld::List(list);
let nodes = traverse_bfs(&ipld);
assert_eq!(nodes.len(), 6); }
#[test]
fn test_traverse_dfs() {
let list = vec![
Ipld::Integer(1),
Ipld::List(vec![Ipld::Integer(2)]),
Ipld::Integer(3),
];
let ipld = Ipld::List(list);
let nodes = traverse_dfs(&ipld);
assert_eq!(nodes.len(), 5); }
#[test]
fn test_empty_ipld() {
let ipld = Ipld::Null;
let links = extract_links(&ipld);
assert!(links.is_empty());
let stats = DagStats::from_ipld(&ipld);
assert_eq!(stats.unique_cids, 0);
assert_eq!(stats.total_links, 0);
}
#[test]
fn test_deduplication_ratio() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let list = vec![Ipld::link(cid1), Ipld::link(cid2)];
let ipld = Ipld::List(list);
let stats = DagStats::from_ipld(&ipld);
assert_eq!(stats.deduplication_ratio(), 0.0);
let list = vec![
Ipld::link(cid1),
Ipld::link(cid2),
Ipld::link(cid1),
Ipld::link(cid2),
];
let ipld = Ipld::List(list);
let stats = DagStats::from_ipld(&ipld);
assert_eq!(stats.deduplication_ratio(), 0.5);
}
#[test]
fn test_dag_metrics() {
let cid = CidBuilder::new().build(b"test").unwrap();
let mut map = BTreeMap::new();
map.insert("link1".to_string(), Ipld::link(cid));
map.insert("link2".to_string(), Ipld::link(cid));
let ipld = Ipld::Map(map);
let metrics = DagMetrics::from_ipld(&ipld);
assert_eq!(metrics.max_branching_factor, 2);
assert!(metrics.width >= 1);
assert!(metrics.total_nodes > 0);
}
#[test]
fn test_dag_metrics_nested() {
let cid = CidBuilder::new().build(b"test").unwrap();
let mut inner = BTreeMap::new();
inner.insert("deep1".to_string(), Ipld::Integer(1));
inner.insert("deep2".to_string(), Ipld::Integer(2));
inner.insert("deep3".to_string(), Ipld::link(cid));
let mut outer = BTreeMap::new();
outer.insert("nested".to_string(), Ipld::Map(inner));
outer.insert("value".to_string(), Ipld::String("test".to_string()));
let ipld = Ipld::Map(outer);
let metrics = DagMetrics::from_ipld(&ipld);
assert_eq!(metrics.max_branching_factor, 3);
assert!(metrics.avg_branching_factor > 0.0);
assert!(metrics.avg_leaf_depth > 0.0);
}
#[test]
fn test_topological_sort() {
let cid1 = CidBuilder::new().build(b"leaf1").unwrap();
let cid2 = CidBuilder::new().build(b"leaf2").unwrap();
let mut map = BTreeMap::new();
map.insert("child1".to_string(), Ipld::link(cid1));
map.insert("child2".to_string(), Ipld::link(cid2));
let ipld = Ipld::Map(map);
let sorted = topological_sort(&ipld);
assert_eq!(sorted.len(), 2);
assert!(sorted.contains(&cid1));
assert!(sorted.contains(&cid2));
}
#[test]
fn test_topological_sort_with_duplicates() {
let cid1 = CidBuilder::new().build(b"test").unwrap();
let list = vec![Ipld::link(cid1), Ipld::link(cid1), Ipld::link(cid1)];
let ipld = Ipld::List(list);
let sorted = topological_sort(&ipld);
assert_eq!(sorted.len(), 1);
assert_eq!(sorted[0], cid1);
}
#[test]
fn test_subgraph_size() {
let ipld = Ipld::List(vec![
Ipld::Integer(1),
Ipld::Integer(2),
Ipld::List(vec![Ipld::Integer(3)]),
]);
let size = subgraph_size(&ipld);
assert_eq!(size, 5); }
#[test]
fn test_subgraph_size_single_node() {
let ipld = Ipld::Integer(42);
let size = subgraph_size(&ipld);
assert_eq!(size, 1);
}
#[test]
fn test_dag_fanout_by_level() {
let ipld = Ipld::List(vec![
Ipld::Integer(1),
Ipld::List(vec![Ipld::Integer(2), Ipld::Integer(3)]),
]);
let fanout = dag_fanout_by_level(&ipld);
assert!(fanout.len() >= 2);
assert_eq!(fanout[0], 2); }
#[test]
fn test_dag_fanout_empty() {
let ipld = Ipld::Integer(42);
let fanout = dag_fanout_by_level(&ipld);
assert!(fanout.is_empty() || fanout.iter().all(|&f| f == 0));
}
#[test]
fn test_count_links_by_depth() {
let cid = CidBuilder::new().build(b"test").unwrap();
let mut map = BTreeMap::new();
map.insert("link".to_string(), Ipld::link(cid));
let ipld = Ipld::Map(map);
let counts = count_links_by_depth(&ipld);
assert!(!counts.is_empty());
assert_eq!(counts[1], 1);
}
#[test]
fn test_count_links_by_depth_nested() {
let cid1 = CidBuilder::new().build(b"test1").unwrap();
let cid2 = CidBuilder::new().build(b"test2").unwrap();
let mut inner = BTreeMap::new();
inner.insert("deep".to_string(), Ipld::link(cid2));
let mut outer = BTreeMap::new();
outer.insert("shallow".to_string(), Ipld::link(cid1));
outer.insert("nested".to_string(), Ipld::Map(inner));
let ipld = Ipld::Map(outer);
let counts = count_links_by_depth(&ipld);
assert!(counts.len() >= 2);
assert_eq!(counts[1], 1); assert_eq!(counts[2], 1); }
#[test]
fn test_filter_dag() {
let ipld = Ipld::List(vec![
Ipld::Integer(1),
Ipld::Integer(2),
Ipld::String("hello".to_string()),
]);
let filtered = filter_dag(&ipld, &|node| {
matches!(node, Ipld::Integer(_) | Ipld::List(_))
});
assert!(filtered.is_some());
if let Some(Ipld::List(items)) = filtered {
assert_eq!(items.len(), 2); } else {
panic!("Expected filtered list");
}
}
#[test]
fn test_filter_dag_all_filtered() {
let ipld = Ipld::Integer(42);
let filtered = filter_dag(&ipld, &|_| false);
assert!(filtered.is_none());
}
#[test]
fn test_map_dag() {
let ipld = Ipld::Integer(42);
let transformed = map_dag(&ipld, &|node| match node {
Ipld::Integer(n) => Ipld::Integer(n * 2),
other => other.clone(),
});
assert_eq!(transformed, Ipld::Integer(84));
}
#[test]
fn test_map_dag_nested() {
let ipld = Ipld::List(vec![Ipld::Integer(1), Ipld::Integer(2)]);
let transformed = map_dag(&ipld, &|node| match node {
Ipld::Integer(n) => Ipld::Integer(n * 2),
other => other.clone(),
});
if let Ipld::List(items) = transformed {
assert_eq!(items[0], Ipld::Integer(2));
assert_eq!(items[1], Ipld::Integer(4));
} else {
panic!("Expected list");
}
}
#[test]
fn test_map_dag_preserve_structure() {
let mut map = BTreeMap::new();
map.insert("a".to_string(), Ipld::Integer(1));
map.insert("b".to_string(), Ipld::Integer(2));
let ipld = Ipld::Map(map);
let transformed = map_dag(&ipld, &|node| match node {
Ipld::Integer(n) => Ipld::Integer(n + 10),
other => other.clone(),
});
if let Ipld::Map(result_map) = transformed {
assert_eq!(result_map.get("a"), Some(&Ipld::Integer(11)));
assert_eq!(result_map.get("b"), Some(&Ipld::Integer(12)));
} else {
panic!("Expected map");
}
}
}