Context

The push_context step allows you to carry information along a graph traversal, making it possible to access data from previous steps while working with the current element. Context creates a typed value that travels with the traversal without changing its position.

Context step diagram showing a fixed context value being attached to elements

In this diagram:

  • An Input Stream contains elements A and B.
  • The .push_context(|v| v.id()) step is applied. The context function (e.g., |v| v.id()) is evaluated once when the step is encountered (conceptually, when element A is processed).
  • The resulting context value (e.g., "A_ID") is then attached to all subsequent elements (A and B) in the Output Stream.
  • This demonstrates that the context value is determined when the push_context step is applied and remains fixed for the rest of that traversal segment.

Methods for Adding Context

// Adding context to a traversal
walker.push_context(|element, current_context| {
    // Create and return new context value
})

// Adding current vertex/edge as context
walker.push_default_context()

Parameters

  • context_fn: A function that takes:
    • A reference to the current element (vertex or edge)
    • The current context (if any)
    • Returns a new context value to be carried along the traversal

Return Value

Returns a new walker with the context added, preserving the current traversal position.

Accessing Context

Any step that accepts a function with context (like map, filter, etc.) will receive:

  • The current element as the first parameter
  • The context as the second parameter
// Using context in a map step
walker
    .push_context(|v, _| v.id())  // Store vertex ID
    .map(|current, context| {
        // Use the stored vertex ID from context
        format!("Current: {}, Source: {}", current.id(), context)
    })

Examples

Vertex Context

Store information about the source vertex during a traversal:

    // Use push_default_context to make source vertex information available during traversal
    let knows: Vec<_> = graph
        .walk()
        .vertices_by_id(vec![bryn_id, julia_id])
        .push_default_context()
        .edges(EdgeSearch::scan().outgoing())
        .filter_follows()
        .head()
        .map(|target, ctx| {
            if let Vertex::Person { name, .. } = ctx.vertex() {
                format!(
                    "{} follows {}",
                    name,
                    target.project::<Person<_>>().unwrap().name()
                )
            } else {
                "Not a person".to_string()
            }
        })
        .collect::<Vec<_>>();

    // Check the results - should have 2 person descriptions
    assert_eq!(knows.len(), 2);
    println!("Vertex Context Example - Relationships found:");
    for relationship in &knows {
        println!("- {}", relationship);
    }

Edge Context

Track edge types during traversal:

    // Walk the graph starting from the person vertex
    let edge_types = graph
        .walk()
        .vertices_by_id(vec![person_id])
        .edges(EdgeSearch::scan().outgoing())
        .push_context(|edge, _ctx| {
            // Determine edge type based on the edge type
            let edge_type = match edge.weight() {
                Edge::Created => "Created",
                Edge::Follows => "Follows",
                Edge::Liked { .. } => "Liked",
                Edge::Commented { .. } => "Commented",
            };

            // Return the edge type as context
            edge_type
        })
        .map(|_v, c| *c)
        .collect::<Vec<_>>();

    println!("{:?}", edge_types);

Path Tracking

Build a representation of the path taken during traversal:

    // Track the path while traversing
    let paths = graph
        .walk()
        .vertices_by_id(vec![start_id])
        // Start with an empty path containing just the start vertex name
        .push_context(|v, _| {
            vec![match v.weight() {
                Vertex::Person { name, .. } => name.clone(),
                _ => "Unknown".to_string(),
            }]
        })
        // Follow outgoing follows edges
        .edges(EdgeSearch::scan().outgoing())
        .filter_follows()
        .tail()
        // Add each person to the path
        .push_context(|v, ctx| {
            let mut new_path = (**ctx).clone();
            if let Vertex::Person { name, .. } = v.weight() {
                new_path.push(name.clone());
            }
            new_path
        })
        // Collect all paths
        .map(|_, ctx| ctx.join(" -> "))
        .collect::<Vec<_>>();

    // Print all paths
    println!("All paths from start:");
    for path in paths {
        println!("- {}", path);
    }

Type Safety

The context system is fully type-safe:

  • Each context value has a concrete type
  • Context transformation functions must return the correct type
  • Closures that receive context are provided with the correctly typed context

Common Use Cases

  • Path tracking: Store the sequence of vertices or edges traversed
  • Metadata collection: Gather information from different parts of the graph
  • Aggregation: Build up composite results during traversal
  • Decision making: Use information from earlier steps to influence later decisions

Best Practices

  • Keep contexts immutable - create new contexts rather than modifying existing ones
  • Use push_default_context() when you simply need to track the current vertex/edge
  • Chain multiple context operations to build complex data structures
  • Consider type-safety when designing context pipelines