Expand description
§assert-struct: Ergonomic Structural Assertions
assert-struct
is a procedural macro that enables clean, readable assertions for complex
data structures without verbose field-by-field comparisons. When assertions fail, it provides
clear, actionable error messages showing exactly what went wrong, including field paths and
expected vs actual values.
This comprehensive guide teaches you how to use assert-struct
effectively in your tests.
After reading this documentation, you’ll be familiar with all capabilities and able to
leverage the full power of structural assertions.
§Table of Contents
§Quick Start
Add to your Cargo.toml
:
[dev-dependencies]
assert-struct = "0.2"
Basic example:
use assert_struct::assert_struct;
#[derive(Debug)]
struct User {
name: String,
age: u32,
email: String,
}
let user = User {
name: "Alice".to_string(),
age: 30,
email: "alice@example.com".to_string(),
};
// Only check the fields you care about
assert_struct!(user, User {
name: "Alice",
age: 30,
.. // Ignore email
});
§Core Concepts
§Basic Assertions
The simplest use case is asserting all fields of a struct:
let point = Point { x: 10, y: 20 };
assert_struct!(point, Point {
x: 10,
y: 20,
});
String fields work naturally with string literals:
let msg = Message {
text: "Hello world".to_string(),
urgent: false,
};
assert_struct!(msg, Message {
text: "Hello world", // No .to_string() needed!
urgent: false,
});
§Partial Matching
Use ..
to ignore fields you don’t want to check:
// Only verify name and email, ignore id and created_at
assert_struct!(user, User {
name: "Alice",
email: "alice@example.com",
..
});
§Nested Structures
Assert on deeply nested data without repetitive field access:
assert_struct!(order, Order {
customer: Customer {
name: "Bob",
address: Address {
city: "Paris",
country: "France",
},
},
total: 99.99,
});
// Or with partial matching
assert_struct!(order, Order {
customer: Customer {
name: "Bob",
address: Address { city: "Paris", .. },
..
},
..
});
// Direct nested field access (no need to nest structs)
assert_struct!(order, Order {
customer.name: "Bob",
customer.address.city: "Paris",
customer.address.country: "France",
total: > 50.0,
..
});
§Pattern Types
§Comparison Operators
Use comparison operators for numeric assertions:
assert_struct!(metrics, Metrics {
cpu: < 80.0, // Less than 80%
memory: <= 2048, // At most 2GB
requests: > 100, // More than 100
});
All comparison operators work: <
, <=
, >
, >=
§Equality Operators
Use explicit equality for clarity:
assert_struct!(status, Status {
code: == 200, // Explicit equality
active: != false, // Not equal to false
});
§Range Patterns
Use ranges for boundary checks:
assert_struct!(person, Person {
age: 18..=65, // Working age range
score: 0.0..100.0, // Valid score range
});
§Regex Patterns
Match string patterns with regular expressions (requires regex
feature, enabled by default):
assert_struct!(account, Account {
username: =~ r"^[a-z_]+$", // Lowercase and underscores
email: =~ r"@company\.com$", // Company email domain
});
§Method Call Patterns
Call methods on fields and assert on their results:
assert_struct!(data, Data {
content.len(): 11, // String length
items.len(): >= 5, // Vector size check
metadata.is_some(): true, // Option state
cache.contains_key("key1"): true, // HashMap lookup
..
});
Method calls work with arguments too:
assert_struct!(text, Text {
content.starts_with("hello"): true,
..
});
assert_struct!(text, Text {
content.contains("world"): true,
..
});
§Data Types
§Collections (Vec/Slice)
Element-wise pattern matching for vectors:
// Exact matching
assert_struct!(data, Data {
values: [5, 15, 25],
names: ["alice", "bob"], // String literals work in slices too!
});
// Pattern matching for each element
assert_struct!(data, Data {
values: [> 0, < 20, >= 25], // Different pattern per element
names: ["alice", "bob"],
});
Partial slice matching:
assert_struct!(data, Data {
items: [1, 2, ..], // First two elements, ignore rest
});
assert_struct!(data, Data {
items: [.., 4, 5], // Last two elements
});
assert_struct!(data, Data {
items: [1, .., 5], // First and last elements
});
§Maps (HashMap/BTreeMap)
Pattern matching for map-like structures using duck typing (works with any type that has len()
and get()
methods):
// Exact matching (checks map length)
assert_struct!(config, Config {
settings: #{ "theme": "dark", "language": "en" },
flags: #{ "debug": true, "verbose": false },
});
// Partial matching (ignores map length)
assert_struct!(config, Config {
settings: #{ "theme": "dark", .. }, // Only check theme
flags: #{ "debug": true, .. }, // Only check debug flag
});
Advanced map patterns with comparisons and nested patterns:
// Pattern matching with comparison operators
assert_struct!(analytics, Analytics {
metrics: #{
"views": > 500, // More than 500 views
"clicks": >= 10, // At least 10 clicks
"conversions": > 0, // Some conversions
..
},
metadata: #{
"version": =~ r"^v\d+\.\d+\.\d+$", // Semantic version pattern
"environment": != "development", // Not in development
..
},
});
Empty and wildcard map matching:
assert_struct!(data, Data {
cache: #{}, // Exactly empty map (len() == 0)
config: #{ .. }, // Any map content (wildcard)
});
§Tuples
Full support for multi-field tuples:
// Basic tuple matching
assert_struct!(data, Data {
point: (15, 25),
metadata: ("info", 100, true), // String literals work in tuples!
});
// Advanced patterns
assert_struct!(data, Data {
point: (> 10, < 30), // Comparison operators
metadata: ("info", >= 50, true),
});
Tuple method calls:
assert_struct!(data, Data {
coords: (0.len(): 8, 1.len(): 3), // Method calls on tuple elements
});
§Enums (Option/Result/Custom)
§Option Types
assert_struct!(user, User {
name: Some("Alice"),
age: Some(30),
});
// Advanced patterns inside Option
assert_struct!(user, User {
name: Some("Alice"),
age: Some(>= 18), // Adult check inside Some
});
§Result Types
assert_struct!(response, Response {
result: Ok("success"),
});
// Pattern matching inside Result
assert_struct!(response, Response {
result: Ok(=~ r"^user\d+$"), // Regex inside Ok
});
§Custom Enums
// Unit variants
let active_account = Account { status: Status::Active };
assert_struct!(active_account, Account {
status: Status::Active,
});
// Struct variants with partial matching
assert_struct!(account, Account {
status: Status::Pending { since: "2024-01-01" },
});
§Smart Pointers
Dereference smart pointers directly in patterns:
assert_struct!(cache, Cache {
*data: "cached", // Dereference Arc<String>
*count: > 40, // Dereference Box<i32> with comparison
*shared: true, // Dereference Rc<bool>
});
Multiple dereferencing for nested pointers:
assert_struct!(nested, Nested {
**value: 42, // Double dereference
});
§Wildcard Patterns
Use wildcard patterns (_
) to avoid importing types while still asserting on their structure:
// No need to import User or Metadata types!
assert_struct!(response, _ {
user: _ {
id: 123,
name: "Alice",
..
},
metadata: _ {
version: "1.0",
.. // Ignore other metadata fields
},
..
});
This is particularly useful when testing API responses where you don’t want to import all the nested types:
// Test deeply nested structures without imports
assert_struct!(json_response, _ {
data: _ {
items: [_ { id: 1, value: "test", .. }],
total: 1,
..
},
..
});
§Error Messages
When assertions fail, assert-struct
provides detailed, actionable error messages:
§Basic Mismatch
assert_struct!(user, User {
name: "Bob", // This will fail
age: 25,
});
// Error output:
// assert_struct! failed:
//
// value mismatch:
// --> `user.name` (src/lib.rs:456)
// actual: "Alice"
// expected: "Bob"
§Comparison Failure
assert_struct!(stats, Stats {
score: > 100, // This will fail
});
// Error output:
// assert_struct! failed:
//
// comparison mismatch:
// --> `stats.score` (src/lib.rs:469)
// actual: 50
// expected: > 100
§Nested Field Errors
Error messages show the exact path to the failing field, even in deeply nested structures. Method calls are also shown in the field path for clear debugging.
§Advanced Usage
§Pattern Composition
Combine multiple patterns for comprehensive assertions:
assert_struct!(complex, Complex {
data: Some([> 0, > 1, > 2]), // Option + Vec + comparisons
metadata: ("info", > 40), // Tuple + string + comparison
..
});
// Verify data length separately
assert_eq!(complex.data.as_ref().unwrap().len(), 3);
§Real-World Testing Patterns
See the examples directory for comprehensive real-world examples including:
- API response validation
- Database record testing
- Configuration validation
- Event system testing
For complete specification details, see the assert_struct!
macro documentation.
Macros§
- assert_
struct - Structural assertion macro for testing complex data structures.
Traits§
- Like
- A trait for pattern matching, similar to
PartialEq
but for flexible matching.