Derive Macros
Graph API's derive macros automatically generate code that integrates your custom types with the Graph API framework, making your graph operations type-safe and ergonomic. This section explains the types that are generated and how to use them effectively.
Overview
Graph API provides two primary derive macros:
VertexExt
- For vertex enum typesEdgeExt
- For edge enum types
// Create a new graph with our model
let mut graph = SimpleGraph::<Vertex, Edge>::new();
// Add vertices of different types
let alice = graph.add_vertex(Vertex::Person {
name: "Alice".to_string(),
username: "alice_dev".to_string(),
biography: "Software engineer and graph enthusiast".to_string(),
age: 29,
});
let project = graph.add_vertex(Vertex::Project {
name: "GraphDB".to_string(),
});
// Add edges connecting vertices
graph.add_edge(alice, project, Edge::Created);
// Use type-safe projections for property access
let person_info = graph
.walk()
.vertices(VertexSearch::scan())
.filter_person() // Generated helper method
.map(|v, _| {
// Use type-safe projection
let person = v.project::<Person<_>>().unwrap();
// Return formatted string with person information
format!(
"Person: {} (@{})\n Bio: {}\n Age: {}",
person.name(),
person.username(),
person.biography(),
person.age()
)
})
.collect::<Vec<_>>();
// Print the collected information
for info in person_info {
println!("{}", info);
}
Generated Types for VertexExt
When you apply #[derive(VertexExt)]
to an enum, several useful types are generated:
1. Label Enum (VertexLabel)
A type-safe enum representing the variants of your vertex enum:
// Given this vertex definition
#[derive(VertexExt)]
enum Vertex {
Person { /* fields */ },
Project { /* fields */ },
Comment { /* fields */ },
}
// This is generated
pub enum VertexLabel {
Person,
Project,
Comment,
}
Purpose: Provides type-safe representations of vertex types, used for label-based indexing and filtering.
2. Index selectors
Selectors to make querying easier:
impl Vertex {
// Find all Person vertices
pub fn person() -> VertexSearch { /* ... */ }
// Find Person vertices by indexed properties
pub fn person_by_username(username: &str) -> VertexSearch { /* ... */ }
pub fn person_by_biography(text: &str) -> VertexSearch { /* ... */ }
pub fn person_by_age_range(range: Range<u8>) -> VertexSearch { /* ... */ }
}
Purpose: Enables efficient querying of vertices by label and properties.
3. Projection Types
For each variant with fields, two projection structs are generated:
// For Person variant
pub struct Person<'a, V> {
// Immutable references to fields
name: &'a String,
username: &'a String,
biography: &'a String,
age: &'a u8,
unique_id: &'a Uuid,
}
pub struct PersonMut<'a, V> {
// Mutable references to fields
name: &'a mut String,
username: &'a mut String,
biography: &'a mut String,
age: &'a mut u8,
unique_id: &'a mut Uuid,
}
Purpose: Provides type-safe access to variant fields without manually matching on enum variants.
4. Filter Extension Traits
Extension methods for the walker builder to filter by vertex type:
// On WalkerBuilder
// For all Person vertices
fn filter_person(self) -> Self;
// For Person vertices with custom criteria
fn filter_by_person<F>(self, filter: F) -> Self
where
F: Fn(Person<Vertex>, &Context) -> bool;
Purpose: Enables type-safe filtering of vertices during traversals.
Generated Types for EdgeExt
When you apply #[derive(EdgeExt)]
to an enum, similar types are generated:
1. Label Enum (EdgeLabel)
// Given this edge definition
#[derive(EdgeExt)]
pub enum Edge {
Created,
Follows,
Liked { timestamp: String },
Commented { timestamp: String },
}
// This is generated
pub enum EdgeLabel {
Created,
Follows,
Liked,
Commented,
}
Purpose: Provides type-safe representations of edge types, used for label-based indexing and filtering.
2. Index selectors
Selectors to make querying easier
impl Edge {
// Find all Created edges
pub fn created() -> EdgeSearch { /* ... */ }
// Find all Follows edges
pub fn follows() -> EdgeSearch { /* ... */ }
// Find all Liked edges
pub fn liked() -> EdgeSearch { /* ... */ }
// Find all Commented edges
pub fn commented() -> EdgeSearch { /* ... */ }
}
Purpose: Enables efficient querying of edges by label.
3. Projection Types
For each variant with fields, projection structs are generated:
// For Liked variant
pub struct Liked<'a, E> {
timestamp: &'a String,
}
pub struct LikedMut<'a, E> {
timestamp: &'a mut String,
}
Purpose: Provides type-safe access to edge properties.
4. Filter Extension Traits
Extension methods for the walker builder to filter by edge type:
// On WalkerBuilder
// For all Liked edges
fn filter_liked(self) -> Self;
// For Liked edges with custom criteria
fn filter_by_liked<F>(self, filter: F) -> Self
where
F: Fn(Liked<Edge>, &Context) -> bool;
Purpose: Enables type-safe filtering of edges during traversals.
Using the Generated Types
Vertex Querying
// Find all Person vertices
let people = graph
.walk()
.vertices(Vertex::person())
.collect::<Vec<_>>();
// Find Person vertices with a specific username
let user = graph
.walk()
.vertices(Vertex::person_by_username("bryn123"))
.first()
.unwrap();
// Find Person vertices in an age range
let adults = graph
.walk()
.vertices(Vertex::person_by_age_range(18..65))
.collect::<Vec<_>>();
Edge Traversal
// Find outgoing Created edges from a person
let created_projects = graph
.walk()
.vertices_by_id([person_id])
.edges(Edge::created().outgoing())
.tail() // Follow edges to target vertices
.collect::<Vec<_>>();
Type-Safe Filtering
// Find Person vertices that match specific criteria
let experienced_devs = graph
.walk()
.vertices(Vertex::person())
.filter_by_person(|person, _| {
person.age() > 25 && person.biography().contains("developer")
})
.collect::<Vec<_>>();
Projection for Type Safety
// Working with a vertex reference
if let Some(vertex_ref) = graph.vertex(vertex_id) {
// Project to Person variant
if let Some(person) = vertex_ref.project::<Person<_>>() {
println!("Name: {}, Age: {}", person.name(), person.age());
}
}
Index Attributes
You can apply these attributes to fields to control indexing behavior:
#[index]
- Standard index for exact match queries#[index(range)]
- Range index for range queries#[index(full_text)]
- Full-text index for text search (String fields only)
#[derive(VertexExt)]
enum User {
Profile {
#[index] // Standard index
username: String,
#[index(range)] // Range queries possible
age: u32,
#[index(full_text)] // Text search possible
bio: String,
// Not indexed
email: String,
}
}
Best Practices
-
Selective Indexing: Only add indexes to fields you'll frequently query, as each index increases memory usage.
-
Choose Appropriate Index Types:
- Use
#[index]
for exact match lookups - Use
#[index(range)]
for numeric fields that need range queries - Use
#[index(full_text)]
for text fields that need substring or keyword search
- Use
-
Use Type-Safe Methods:
- Prefer
filter_by_person()
over genericfilter()
with manual pattern matching - Use the generated search methods (
person_by_username()
, etc.) for efficient queries
- Prefer
-
Leverage Projections:
- Use the projection types to safely access variant fields without repetitive pattern matching
- This makes your code more maintainable and less error-prone
-
Consider Query Performance:
- Using an indexed search in the initial
vertices()
step is typically more efficient than scanning and then filtering - The more you can narrow your search using indexes, the better your graph traversal performance
- Using an indexed search in the initial
For more detailed information on the derive macros, see the API Reference.