use crate::graph::GraphStorage;
use crate::{ElementProperty, TraverserValue};
pub const DEFAULT_MAX_VALUE_DEPTH: usize = 128;
#[inline]
fn is_container(value: &ElementProperty) -> bool {
matches!(
value,
ElementProperty::Array(_)
| ElementProperty::Object(_)
| ElementProperty::Map(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_)
)
}
pub(crate) fn property_exceeds_depth(value: &ElementProperty, max: usize) -> bool {
if !is_container(value) {
return false;
}
let mut stack: Vec<(&ElementProperty, usize)> = vec![(value, 1)];
while let Some((current, depth)) = stack.pop() {
if depth > max {
return true;
}
let child = depth + 1;
match current {
ElementProperty::Array(items) => {
stack.extend(items.iter().filter(|c| is_container(c)).map(|c| (c, child)));
}
ElementProperty::Object(element)
| ElementProperty::Vertex(element)
| ElementProperty::Edge(element) => {
stack.extend(
element
.values()
.filter(|c| is_container(c))
.map(|c| (c, child)),
);
}
ElementProperty::Map(map) => {
for (key, item) in map.iter() {
for c in [key, item] {
if is_container(c) {
stack.push((c, child));
}
}
}
}
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_) => {}
}
}
false
}
enum Node<'a, S: GraphStorage> {
Stream(&'a TraverserValue<S>),
Property(&'a ElementProperty),
}
pub(crate) fn traverser_value_exceeds_depth<S: GraphStorage>(
value: &TraverserValue<S>,
max: usize,
) -> bool {
match value {
TraverserValue::Computed(property, _) => return property_exceeds_depth(property, max),
TraverserValue::VertexProperty(..)
| TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::EdgeProperty(..)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => return false,
TraverserValue::PathProperty(_)
| TraverserValue::Path(_)
| TraverserValue::Record { .. }
| TraverserValue::Object(_)
| TraverserValue::Entry(_)
| TraverserValue::Array(_) => {}
}
let mut stack: Vec<(Node<'_, S>, usize)> = vec![(Node::Stream(value), 0)];
while let Some((node, depth)) = stack.pop() {
let value = match node {
Node::Property(property) => {
if property_exceeds_depth(property, max.saturating_sub(depth)) {
return true;
}
continue;
}
Node::Stream(value) => value,
};
let child = depth + 1;
match value {
TraverserValue::Computed(property, _) => stack.push((Node::Property(property), depth)),
TraverserValue::VertexProperty(..)
| TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::EdgeProperty(..)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => {}
TraverserValue::PathProperty(entry) => {
if child > max {
return true;
}
stack.push((Node::Property(&entry.1), child));
}
TraverserValue::Path(path) => {
if child > max {
return true;
}
stack.extend(path.steps.iter().map(|s| (Node::Stream(&s.value), child)));
}
TraverserValue::Record { values, .. } => {
if child > max {
return true;
}
stack.extend(values.iter().map(|v| (Node::Stream(v), child)));
}
TraverserValue::Object(map) => {
if child > max {
return true;
}
for (key, item) in map {
stack.push((Node::Stream(key), child));
stack.push((Node::Stream(item), child));
}
}
TraverserValue::Entry(entry) => {
if child > max {
return true;
}
stack.push((Node::Stream(&entry.0), child));
stack.push((Node::Stream(&entry.1), child));
}
TraverserValue::Array(items) => {
if child > max {
return true;
}
stack.extend(items.iter().map(|v| (Node::Stream(v), child)));
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TraversalGraph;
fn nested(depth: usize) -> ElementProperty {
let mut value = ElementProperty::from(1);
for _ in 0..depth {
value = ElementProperty::Array(vec![value]);
}
value
}
#[test]
fn scalars_have_depth_zero() {
assert!(!property_exceeds_depth(&ElementProperty::from(1), 0));
assert!(property_exceeds_depth(&nested(1), 0));
assert!(!property_exceeds_depth(&nested(1), 1));
}
#[test]
fn the_limit_is_inclusive() {
assert!(!property_exceeds_depth(&nested(128), 128));
assert!(property_exceeds_depth(&nested(129), 128));
assert!(property_exceeds_depth(&nested(2_000), 128));
assert!(!property_exceeds_depth(&nested(2_000), 2_000));
}
#[test]
fn stream_values_count_their_own_levels() {
type V = TraverserValue<TraversalGraph>;
let leaf: V = TraverserValue::Computed(nested(2), None);
let wrapped: V = TraverserValue::Array(vec![TraverserValue::Array(vec![leaf])]);
assert!(!traverser_value_exceeds_depth(&wrapped, 4));
assert!(traverser_value_exceeds_depth(&wrapped, 3));
let mut graph = TraversalGraph::default();
let id = graph
.add_vertex(None, vec![], Default::default())
.expect("a vertex");
let vertex: V = TraverserValue::Vertex(id);
assert!(!traverser_value_exceeds_depth(&vertex, 0));
}
}