nodedb_graph/
traversal_options.rs1pub const MAX_GRAPH_TRAVERSAL_DEPTH: usize = 64;
16
17use serde::{Deserialize, Serialize};
18
19#[derive(
24 Debug,
25 Clone,
26 PartialEq,
27 Eq,
28 Serialize,
29 Deserialize,
30 zerompk::ToMessagePack,
31 zerompk::FromMessagePack,
32)]
33pub struct GraphTraversalOptions {
34 pub fan_out_soft: u16,
40
41 pub fan_out_hard: u16,
47
48 pub fan_out_partial: bool,
54
55 pub max_visited: usize,
60}
61
62impl Default for GraphTraversalOptions {
63 fn default() -> Self {
64 Self {
65 fan_out_soft: 12,
66 fan_out_hard: 16,
67 fan_out_partial: false,
68 max_visited: 100_000,
69 }
70 }
71}
72
73impl GraphTraversalOptions {
74 pub fn new() -> Self {
76 Self::default()
77 }
78}
79
80#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct GraphResponseMeta {
86 pub shards_reached: u16,
88
89 pub shards_skipped: u16,
91
92 pub truncated: bool,
94
95 pub fan_out_warning: Option<String>,
100
101 pub approximate: bool,
105}
106
107impl GraphResponseMeta {
108 pub fn is_clean(&self) -> bool {
115 self.fan_out_warning.is_none() && !self.truncated && !self.approximate
116 }
117
118 pub fn with_warning(shards_reached: u16, shards_skipped: u16, fan_out_hard: u16) -> Self {
123 Self {
124 shards_reached,
125 shards_skipped,
126 truncated: false,
127 fan_out_warning: Some(format!("{}/{}", shards_reached, fan_out_hard)),
128 approximate: true,
129 }
130 }
131
132 pub fn with_truncation(shards_reached: u16, shards_skipped: u16) -> Self {
136 Self {
137 shards_reached,
138 shards_skipped,
139 truncated: true,
140 fan_out_warning: None,
141 approximate: true,
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use sonic_rs;
150
151 #[test]
152 fn default_options_have_expected_values() {
153 let opts = GraphTraversalOptions::default();
154 assert_eq!(opts.fan_out_soft, 12);
155 assert_eq!(opts.fan_out_hard, 16);
156 assert!(!opts.fan_out_partial);
157 assert_eq!(opts.max_visited, 100_000);
158 }
159
160 #[test]
161 fn new_returns_defaults() {
162 let opts = GraphTraversalOptions::new();
163 assert_eq!(opts, GraphTraversalOptions::default());
164 }
165
166 #[test]
167 fn default_meta_is_clean() {
168 let meta = GraphResponseMeta::default();
169 assert!(meta.is_clean());
170 assert_eq!(meta.shards_reached, 0);
171 assert_eq!(meta.shards_skipped, 0);
172 assert!(!meta.truncated);
173 assert!(meta.fan_out_warning.is_none());
174 assert!(!meta.approximate);
175 }
176
177 #[test]
178 fn with_warning_generates_correct_string() {
179 let meta = GraphResponseMeta::with_warning(12, 4, 16);
180 assert_eq!(meta.shards_reached, 12);
181 assert_eq!(meta.shards_skipped, 4);
182 assert!(!meta.truncated);
183 assert_eq!(meta.fan_out_warning, Some("12/16".to_string()));
184 assert!(meta.approximate);
185 }
186
187 #[test]
188 fn with_truncation_sets_flags() {
189 let meta = GraphResponseMeta::with_truncation(10, 6);
190 assert_eq!(meta.shards_reached, 10);
191 assert_eq!(meta.shards_skipped, 6);
192 assert!(meta.truncated);
193 assert!(meta.fan_out_warning.is_none());
194 assert!(meta.approximate);
195 }
196
197 #[test]
198 fn with_warning_is_not_clean() {
199 let meta = GraphResponseMeta::with_warning(12, 4, 16);
200 assert!(!meta.is_clean());
201 }
202
203 #[test]
204 fn with_truncation_is_not_clean() {
205 let meta = GraphResponseMeta::with_truncation(10, 6);
206 assert!(!meta.is_clean());
207 }
208
209 #[test]
210 fn serialization_roundtrip() {
211 let opts = GraphTraversalOptions {
212 fan_out_soft: 8,
213 fan_out_hard: 12,
214 fan_out_partial: true,
215 max_visited: 50_000,
216 };
217 let json = sonic_rs::to_string(&opts).unwrap();
218 let deserialized: GraphTraversalOptions = sonic_rs::from_str(&json).unwrap();
219 assert_eq!(opts.fan_out_soft, deserialized.fan_out_soft);
220 assert_eq!(opts.fan_out_hard, deserialized.fan_out_hard);
221 assert_eq!(opts.fan_out_partial, deserialized.fan_out_partial);
222 assert_eq!(opts.max_visited, deserialized.max_visited);
223 }
224
225 #[test]
226 fn meta_serialization_roundtrip() {
227 let meta = GraphResponseMeta::with_warning(15, 1, 16);
228 let json = sonic_rs::to_string(&meta).unwrap();
229 let deserialized: GraphResponseMeta = sonic_rs::from_str(&json).unwrap();
230 assert_eq!(meta.shards_reached, deserialized.shards_reached);
231 assert_eq!(meta.shards_skipped, deserialized.shards_skipped);
232 assert_eq!(meta.truncated, deserialized.truncated);
233 assert_eq!(meta.fan_out_warning, deserialized.fan_out_warning);
234 assert_eq!(meta.approximate, deserialized.approximate);
235 }
236}