1use std::collections::{HashSet, VecDeque};
4
5use serde::Serialize;
6
7use crate::error::Result;
8use crate::id::RecordId;
9use crate::model::RecordKind;
10use crate::relation::{Relation, RelationKind};
11use crate::storage::Store;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Direction {
15 Outgoing,
16 Incoming,
17 Both,
18}
19
20#[derive(Clone, Debug, PartialEq, Serialize)]
22pub struct Edge {
23 pub source: RecordId,
24 pub relation: Relation,
25}
26
27#[derive(Clone, Debug)]
28pub struct TraversalOptions {
29 pub direction: Direction,
30 pub kinds: Vec<RelationKind>,
32 pub max_depth: usize,
33 pub max_nodes: usize,
35 pub include_retracted: bool,
36}
37
38impl Default for TraversalOptions {
39 fn default() -> Self {
40 Self { direction: Direction::Both, kinds: Vec::new(), max_depth: 2, max_nodes: 500, include_retracted: false }
41 }
42}
43
44#[derive(Clone, Debug, Default, Serialize)]
45pub struct Traversal {
46 pub nodes: Vec<(RecordId, usize)>,
48 pub edges: Vec<Edge>,
49 pub unresolved: Vec<RecordId>,
52 pub truncated: bool,
53}
54
55fn wanted(rel: &Relation, opts: &TraversalOptions) -> bool {
56 (opts.include_retracted || !rel.retracted) && (opts.kinds.is_empty() || opts.kinds.contains(&rel.kind))
57}
58
59pub fn outgoing<S: Store>(store: &S, id: &RecordId, kinds: &[RelationKind]) -> Result<Vec<Edge>> {
61 let Some(record) = store.get(id)? else { return Ok(Vec::new()) };
62 Ok(record
63 .active_relations()
64 .filter(|r| kinds.is_empty() || kinds.contains(&r.kind))
65 .map(|r| Edge { source: id.clone(), relation: r.clone() })
66 .collect())
67}
68
69pub fn incoming<S: Store>(store: &S, id: &RecordId, kinds: &[RelationKind], from: &[RecordKind]) -> Result<Vec<Edge>> {
71 Ok(store
72 .incoming(id)?
73 .into_iter()
74 .filter(|e| !e.relation.retracted)
75 .filter(|e| kinds.is_empty() || kinds.contains(&e.relation.kind))
76 .filter(|e| from.is_empty() || from.contains(&e.source_kind))
77 .map(|e| Edge { source: e.source, relation: e.relation })
78 .collect())
79}
80
81pub fn traverse<S: Store>(store: &S, start: &RecordId, opts: &TraversalOptions) -> Result<Traversal> {
83 let mut out = Traversal::default();
84 let mut seen: HashSet<RecordId> = HashSet::from([start.clone()]);
85 let mut edge_seen = HashSet::new();
86 let mut queue = VecDeque::from([(start.clone(), 0usize)]);
87 while let Some((id, depth)) = queue.pop_front() {
88 let Some(record) = store.get(&id)? else {
89 out.unresolved.push(id);
90 continue;
91 };
92 out.nodes.push((id.clone(), depth));
93 if depth >= opts.max_depth {
94 continue;
95 }
96 let mut next = Vec::new();
97 if opts.direction != Direction::Incoming {
98 for rel in record.relations.iter().filter(|r| wanted(r, opts)) {
99 next.push((Edge { source: id.clone(), relation: rel.clone() }, rel.target.clone()));
100 }
101 }
102 if opts.direction != Direction::Outgoing {
103 for e in store.incoming(&id)? {
104 if wanted(&e.relation, opts) {
105 let src = e.source.clone();
106 next.push((Edge { source: e.source, relation: e.relation }, src));
107 }
108 }
109 }
110 for (edge, neighbour) in next {
111 if edge_seen.insert((edge.source.clone(), edge.relation.id)) {
112 out.edges.push(edge);
113 }
114 if seen.contains(&neighbour) {
115 continue;
116 }
117 if seen.len() >= opts.max_nodes {
118 out.truncated = true;
119 continue;
120 }
121 seen.insert(neighbour.clone());
122 queue.push_back((neighbour, depth + 1));
123 }
124 }
125 Ok(out)
126}