use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GraphTraversalOptions {
pub fan_out_soft: u16,
pub fan_out_hard: u16,
pub fan_out_partial: bool,
pub max_visited: usize,
}
impl Default for GraphTraversalOptions {
fn default() -> Self {
Self {
fan_out_soft: 12,
fan_out_hard: 16,
fan_out_partial: false,
max_visited: 100_000,
}
}
}
impl GraphTraversalOptions {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GraphResponseMeta {
pub shards_reached: u16,
pub shards_skipped: u16,
pub truncated: bool,
pub fan_out_warning: Option<String>,
pub approximate: bool,
}
impl GraphResponseMeta {
pub fn is_clean(&self) -> bool {
self.fan_out_warning.is_none() && !self.truncated && !self.approximate
}
pub fn with_warning(shards_reached: u16, shards_skipped: u16, fan_out_hard: u16) -> Self {
Self {
shards_reached,
shards_skipped,
truncated: false,
fan_out_warning: Some(format!("{}/{}", shards_reached, fan_out_hard)),
approximate: true,
}
}
pub fn with_truncation(shards_reached: u16, shards_skipped: u16) -> Self {
Self {
shards_reached,
shards_skipped,
truncated: true,
fan_out_warning: None,
approximate: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_options_have_expected_values() {
let opts = GraphTraversalOptions::default();
assert_eq!(opts.fan_out_soft, 12);
assert_eq!(opts.fan_out_hard, 16);
assert!(!opts.fan_out_partial);
assert_eq!(opts.max_visited, 100_000);
}
#[test]
fn new_returns_defaults() {
let opts = GraphTraversalOptions::new();
assert_eq!(opts, GraphTraversalOptions::default());
}
#[test]
fn default_meta_is_clean() {
let meta = GraphResponseMeta::default();
assert!(meta.is_clean());
assert_eq!(meta.shards_reached, 0);
assert_eq!(meta.shards_skipped, 0);
assert!(!meta.truncated);
assert!(meta.fan_out_warning.is_none());
assert!(!meta.approximate);
}
#[test]
fn with_warning_generates_correct_string() {
let meta = GraphResponseMeta::with_warning(12, 4, 16);
assert_eq!(meta.shards_reached, 12);
assert_eq!(meta.shards_skipped, 4);
assert!(!meta.truncated);
assert_eq!(meta.fan_out_warning, Some("12/16".to_string()));
assert!(meta.approximate);
}
#[test]
fn with_truncation_sets_flags() {
let meta = GraphResponseMeta::with_truncation(10, 6);
assert_eq!(meta.shards_reached, 10);
assert_eq!(meta.shards_skipped, 6);
assert!(meta.truncated);
assert!(meta.fan_out_warning.is_none());
assert!(meta.approximate);
}
#[test]
fn with_warning_is_not_clean() {
let meta = GraphResponseMeta::with_warning(12, 4, 16);
assert!(!meta.is_clean());
}
#[test]
fn with_truncation_is_not_clean() {
let meta = GraphResponseMeta::with_truncation(10, 6);
assert!(!meta.is_clean());
}
#[test]
fn serialization_roundtrip() {
let opts = GraphTraversalOptions {
fan_out_soft: 8,
fan_out_hard: 12,
fan_out_partial: true,
max_visited: 50_000,
};
let json = serde_json::to_string(&opts).unwrap();
let deserialized: GraphTraversalOptions = serde_json::from_str(&json).unwrap();
assert_eq!(opts.fan_out_soft, deserialized.fan_out_soft);
assert_eq!(opts.fan_out_hard, deserialized.fan_out_hard);
assert_eq!(opts.fan_out_partial, deserialized.fan_out_partial);
assert_eq!(opts.max_visited, deserialized.max_visited);
}
#[test]
fn meta_serialization_roundtrip() {
let meta = GraphResponseMeta::with_warning(15, 1, 16);
let json = serde_json::to_string(&meta).unwrap();
let deserialized: GraphResponseMeta = serde_json::from_str(&json).unwrap();
assert_eq!(meta.shards_reached, deserialized.shards_reached);
assert_eq!(meta.shards_skipped, deserialized.shards_skipped);
assert_eq!(meta.truncated, deserialized.truncated);
assert_eq!(meta.fan_out_warning, deserialized.fan_out_warning);
assert_eq!(meta.approximate, deserialized.approximate);
}
}