Skip to main content

assert_struct/
lib.rs

1//! # assert-struct: Ergonomic Structural Assertions
2//!
3//! `assert-struct` is a procedural macro that enables clean, readable assertions for complex
4//! data structures without verbose field-by-field comparisons. When assertions fail, it provides
5//! clear, actionable error messages showing exactly what went wrong, including field paths and
6//! expected vs actual values.
7//!
8//! This comprehensive guide teaches you how to use `assert-struct` effectively in your tests.
9//! After reading this documentation, you'll be familiar with all capabilities and able to
10//! leverage the full power of structural assertions.
11//!
12//! # Table of Contents
13//!
14//! - [Quick Start](#quick-start)
15//! - [Core Concepts](#core-concepts)
16//!   - [Basic Assertions](#basic-assertions)
17//!   - [Partial Matching](#partial-matching)
18//!   - [Nested Structures](#nested-structures)
19//! - [Pattern Types](#pattern-types)
20//!   - [Comparison Operators](#comparison-operators)
21//!   - [Equality Operators](#equality-operators)
22//!   - [Range Patterns](#range-patterns)
23//!   - [Regex Patterns](#regex-patterns)
24//!   - [Method Call Patterns](#method-call-patterns)
25//! - [Data Types](#data-types)
26//!   - [Collections (Vec/Slice)](#collections-vecslice)
27//!   - [Set Patterns](#set-patterns)
28//!   - [Maps (HashMap/BTreeMap)](#maps-hashmapbtreemap)
29//!   - [Tuples](#tuples)
30//!   - [Enums (Option/Result/Custom)](#enums-optionresultcustom)
31//!   - [Smart Pointers](#smart-pointers)
32//! - [Error Messages](#error-messages)
33//! - [Advanced Usage](#advanced-usage)
34//!   - [Dynamic Value Types](#dynamic-value-types)
35//!
36//! # Quick Start
37//!
38//! Add to your `Cargo.toml`:
39//!
40//! ```toml
41//! [dev-dependencies]
42//! assert-struct = "0.2"
43//! ```
44//!
45//! Basic example:
46//!
47//! ```rust
48//! use assert_struct::assert_struct;
49//!
50//! #[derive(Debug)]
51//! struct User {
52//!     name: String,
53//!     age: u32,
54//!     email: String,
55//! }
56//!
57//! let user = User {
58//!     name: "Alice".to_string(),
59//!     age: 30,
60//!     email: "alice@example.com".to_string(),
61//! };
62//!
63//! // Only check the fields you care about
64//! assert_struct!(user, User {
65//!     name: "Alice",
66//!     age: 30,
67//!     ..  // Ignore email
68//! });
69//! ```
70//!
71//! # Core Concepts
72//!
73//! ## Basic Assertions
74//!
75//! The simplest use case is asserting all fields of a struct:
76//!
77//! ```rust
78//! # use assert_struct::assert_struct;
79//! # #[derive(Debug)]
80//! # struct Point { x: i32, y: i32 }
81//! let point = Point { x: 10, y: 20 };
82//!
83//! assert_struct!(point, Point {
84//!     x: 10,
85//!     y: 20,
86//! });
87//! ```
88//!
89//! String fields work naturally with string literals:
90//!
91//! ```rust
92//! # use assert_struct::assert_struct;
93//! # #[derive(Debug)]
94//! # struct Message { text: String, urgent: bool }
95//! let msg = Message {
96//!     text: "Hello world".to_string(),
97//!     urgent: false,
98//! };
99//!
100//! assert_struct!(msg, Message {
101//!     text: "Hello world",  // No .to_string() needed!
102//!     urgent: false,
103//! });
104//! ```
105//!
106//! ## Partial Matching
107//!
108//! Use `..` to ignore fields you don't want to check:
109//!
110//! ```rust
111//! # use assert_struct::assert_struct;
112//! # #[derive(Debug)]
113//! # struct User { id: u64, name: String, email: String, created_at: String }
114//! # let user = User {
115//! #     id: 1,
116//! #     name: "Alice".to_string(),
117//! #     email: "alice@example.com".to_string(),
118//! #     created_at: "2024-01-01".to_string(),
119//! # };
120//! // Only verify name and email, ignore id and created_at
121//! assert_struct!(user, User {
122//!     name: "Alice",
123//!     email: "alice@example.com",
124//!     ..
125//! });
126//! ```
127//!
128//! ## Nested Structures
129//!
130//! Assert on deeply nested data without repetitive field access:
131//!
132//! ```rust
133//! # use assert_struct::assert_struct;
134//! # #[derive(Debug)]
135//! # struct Order { customer: Customer, total: f64 }
136//! # #[derive(Debug)]
137//! # struct Customer { name: String, address: Address }
138//! # #[derive(Debug)]
139//! # struct Address { city: String, country: String }
140//! # let order = Order {
141//! #     customer: Customer {
142//! #         name: "Bob".to_string(),
143//! #         address: Address { city: "Paris".to_string(), country: "France".to_string() }
144//! #     },
145//! #     total: 99.99
146//! # };
147//! assert_struct!(order, Order {
148//!     customer: Customer {
149//!         name: "Bob",
150//!         address: Address {
151//!             city: "Paris",
152//!             country: "France",
153//!         },
154//!     },
155//!     total: 99.99,
156//! });
157//!
158//! // Or with partial matching
159//! assert_struct!(order, Order {
160//!     customer: Customer {
161//!         name: "Bob",
162//!         address: Address { city: "Paris", .. },
163//!         ..
164//!     },
165//!     ..
166//! });
167//!
168//! // Direct nested field access (no need to nest structs)
169//! assert_struct!(order, Order {
170//!     customer.name: "Bob",
171//!     customer.address.city: "Paris",
172//!     customer.address.country: "France",
173//!     total: > 50.0,
174//!     ..
175//! });
176//! ```
177//!
178//! # Pattern Types
179//!
180//! ## Comparison Operators
181//!
182//! Use comparison operators for numeric assertions:
183//!
184//! ```rust
185//! # use assert_struct::assert_struct;
186//! # #[derive(Debug)]
187//! # struct Metrics { cpu: f64, memory: u64, requests: u32 }
188//! # let metrics = Metrics { cpu: 75.5, memory: 1024, requests: 150 };
189//! assert_struct!(metrics, Metrics {
190//!     cpu: < 80.0,          // Less than 80%
191//!     memory: <= 2048,      // At most 2GB
192//!     requests: > 100,      // More than 100
193//! });
194//! ```
195//!
196//! All comparison operators work: `<`, `<=`, `>`, `>=`
197//!
198//! ## Equality Operators
199//!
200//! Use explicit equality for clarity:
201//!
202//! ```rust
203//! # use assert_struct::assert_struct;
204//! # #[derive(Debug)]
205//! # struct Status { code: i32, active: bool }
206//! # let status = Status { code: 200, active: true };
207//! assert_struct!(status, Status {
208//!     code: == 200,         // Explicit equality
209//!     active: != false,     // Not equal to false
210//! });
211//! ```
212//!
213//! ## Range Patterns
214//!
215//! Use ranges for boundary checks:
216//!
217//! ```rust
218//! # use assert_struct::assert_struct;
219//! # #[derive(Debug)]
220//! # struct Person { age: u32, score: f64 }
221//! # let person = Person { age: 25, score: 87.5 };
222//! assert_struct!(person, Person {
223//!     age: 18..=65,         // Working age range
224//!     score: 0.0..100.0,    // Valid score range
225//! });
226//! ```
227//!
228//! ## Regex Patterns
229//!
230//! Match string patterns with regular expressions (requires `regex` feature, enabled by default):
231//!
232//! ```rust
233//! # #[cfg(feature = "regex")]
234//! # {
235//! # use assert_struct::assert_struct;
236//! # #[derive(Debug)]
237//! # struct Account { username: String, email: String }
238//! # let account = Account {
239//! #     username: "alice_doe".to_string(),
240//! #     email: "alice@company.com".to_string(),
241//! # };
242//! assert_struct!(account, Account {
243//!     username: =~ r"^[a-z_]+$",        // Lowercase and underscores
244//!     email: =~ r"@company\.com$",      // Company email domain
245//! });
246//! # }
247//! ```
248//!
249//! ## Method Call Patterns
250//!
251//! Call methods on fields and assert on their results:
252//!
253//! ```rust
254//! # use assert_struct::assert_struct;
255//! # use std::collections::HashMap;
256//! # #[derive(Debug)]
257//! # struct Data {
258//! #     content: String,
259//! #     items: Vec<i32>,
260//! #     metadata: Option<String>,
261//! #     cache: HashMap<String, i32>,
262//! # }
263//! # let mut map = HashMap::new();
264//! # map.insert("key1".to_string(), 42);
265//! # let data = Data {
266//! #     content: "hello world".to_string(),
267//! #     items: vec![1, 2, 3, 4, 5],
268//! #     metadata: Some("cached".to_string()),
269//! #     cache: map,
270//! # };
271//! assert_struct!(data, Data {
272//!     content.len(): 11,                    // String length
273//!     items.len(): >= 5,                    // Vector size check
274//!     metadata.is_some(): true,             // Option state
275//!     cache.contains_key("key1"): true,     // HashMap lookup
276//!     ..
277//! });
278//! ```
279//!
280//! Method calls work with arguments too:
281//!
282//! ```rust
283//! # use assert_struct::assert_struct;
284//! # #[derive(Debug)]
285//! # struct Text { content: String, other: String }
286//! # let text = Text { content: "hello world".to_string(), other: "test".to_string() };
287//! assert_struct!(text, Text {
288//!     content.starts_with("hello"): true,
289//!     ..
290//! });
291//!
292//! assert_struct!(text, Text {
293//!     content.contains("world"): true,
294//!     ..
295//! });
296//! ```
297//!
298//! # Data Types
299//!
300//! ## Collections (Vec/Slice)
301//!
302//! Element-wise pattern matching for vectors:
303//!
304//! ```rust
305//! # use assert_struct::assert_struct;
306//! # #[derive(Debug)]
307//! # struct Data { values: Vec<i32>, names: Vec<String> }
308//! # let data = Data {
309//! #     values: vec![5, 15, 25],
310//! #     names: vec!["alice".to_string(), "bob".to_string()],
311//! # };
312//! // Exact matching
313//! assert_struct!(data, Data {
314//!     values: [5, 15, 25],
315//!     names: ["alice", "bob"],  // String literals work in slices too!
316//! });
317//!
318//! // Pattern matching for each element
319//! assert_struct!(data, Data {
320//!     values: [> 0, < 20, >= 25],    // Different pattern per element
321//!     names: ["alice", "bob"],
322//! });
323//! ```
324//!
325//! Partial slice matching:
326//!
327//! ```rust
328//! # use assert_struct::assert_struct;
329//! # #[derive(Debug)]
330//! # struct Data { items: Vec<i32> }
331//! # let data = Data { items: vec![1, 2, 3, 4, 5] };
332//! assert_struct!(data, Data {
333//!     items: [1, 2, ..],      // First two elements, ignore rest
334//! });
335//!
336//! assert_struct!(data, Data {
337//!     items: [.., 4, 5],      // Last two elements
338//! });
339//!
340//! assert_struct!(data, Data {
341//!     items: [1, .., 5],      // First and last elements
342//! });
343//! ```
344//!
345//! ## Set Patterns
346//!
347//! Use `#(...)` to assert that a collection contains elements matching the given patterns,
348//! **in any order**. Each pattern must match a distinct element; unlike slice patterns `[...]`,
349//! position does not matter.
350//!
351//! ```rust
352//! # use assert_struct::assert_struct;
353//! # #[derive(Debug)]
354//! # struct Data { items: Vec<i32>, tags: Vec<String> }
355//! # let data = Data {
356//! #     items: vec![3, 1, 2],
357//! #     tags: vec!["rust".to_string(), "async".to_string()],
358//! # };
359//! // Exact match - every element must be matched, in any order
360//! assert_struct!(data, Data {
361//!     items: #(1, 2, 3),
362//!     tags: #("rust", "async"),  // String literals work too
363//! });
364//!
365//! // Partial match - use .. to allow extra unmatched elements
366//! assert_struct!(data, Data {
367//!     items: #(> 0, ..),         // At least one element matching > 0
368//!     tags: #("rust", ..),       // Contains "rust", possibly more
369//! });
370//! ```
371//!
372//! Struct patterns work inside `#(...)` too, which is useful when asserting that certain
373//! items are present in a collection regardless of their order:
374//!
375//! ```rust
376//! # use assert_struct::assert_struct;
377//! # #[derive(Debug)]
378//! # struct Event { kind: String, value: i32 }
379//! # let events = vec![
380//! #     Event { kind: "click".to_string(), value: 10 },
381//! #     Event { kind: "hover".to_string(), value: 20 },
382//! #     Event { kind: "scroll".to_string(), value: 30 },
383//! # ];
384//! // Assert that click and hover events are present, ignoring order and other events
385//! assert_struct!(events, #(
386//!     _ { kind: "click", value: > 0 },
387//!     _ { kind: "hover" },
388//!     ..
389//! ));
390//! ```
391//!
392//! Other pattern types also work inside `#(...)`:
393//!
394//! ```rust
395//! # use assert_struct::assert_struct;
396//! # #[derive(Debug)]
397//! # struct Report { scores: Vec<i32>, results: Vec<Option<i32>> }
398//! # let report = Report {
399//! #     scores: vec![95, 42, 77],
400//! #     results: vec![Some(5), None, Some(10)],
401//! # };
402//! assert_struct!(report, Report {
403//!     scores: #(> 90, < 50, 18..=100),    // Comparisons and ranges
404//!     results: #(None, Some(> 0), ..),     // Enum patterns
405//! });
406//! ```
407//!
408//! Empty and wildcard set patterns:
409//!
410//! ```rust
411//! # use assert_struct::assert_struct;
412//! # #[derive(Debug)]
413//! # struct Data { empty: Vec<i32>, any: Vec<i32> }
414//! # let data = Data { empty: vec![], any: vec![1, 2, 3] };
415//! assert_struct!(data, Data {
416//!     empty: #(),     // Exactly empty collection
417//!     any: #(..),     // Any collection (wildcard - any contents or length)
418//! });
419//! ```
420//!
421//!
422//! ## Maps (HashMap/BTreeMap)
423//!
424//! Pattern matching for map-like structures using duck typing (works with any type that has `len()` and `get()` methods):
425//!
426//! ```rust
427//! # use assert_struct::assert_struct;
428//! # use std::collections::HashMap;
429//! # #[derive(Debug)]
430//! # struct Config {
431//! #     settings: HashMap<String, String>,
432//! #     flags: HashMap<String, bool>,
433//! # }
434//! # let mut settings = HashMap::new();
435//! # settings.insert("theme".to_string(), "dark".to_string());
436//! # settings.insert("language".to_string(), "en".to_string());
437//! # let mut flags = HashMap::new();
438//! # flags.insert("debug".to_string(), true);
439//! # flags.insert("verbose".to_string(), false);
440//! # let config = Config { settings, flags };
441//! // Exact matching (checks map length)
442//! assert_struct!(config, Config {
443//!     settings: #{ "theme": "dark", "language": "en" },
444//!     flags: #{ "debug": true, "verbose": false },
445//! });
446//!
447//! // Partial matching (ignores map length)
448//! assert_struct!(config, Config {
449//!     settings: #{ "theme": "dark", .. },     // Only check theme
450//!     flags: #{ "debug": true, .. },          // Only check debug flag
451//! });
452//! ```
453//!
454//! Advanced map patterns with comparisons and nested patterns:
455//!
456//! ```rust
457//! # use assert_struct::assert_struct;
458//! # use std::collections::{HashMap, BTreeMap};
459//! # #[derive(Debug)]
460//! # struct Analytics {
461//! #     metrics: HashMap<String, i32>,
462//! #     metadata: BTreeMap<String, String>,
463//! # }
464//! # let mut metrics = HashMap::new();
465//! # metrics.insert("views".to_string(), 1000);
466//! # metrics.insert("clicks".to_string(), 50);
467//! # metrics.insert("conversions".to_string(), 5);
468//! # let mut metadata = BTreeMap::new();
469//! # metadata.insert("version".to_string(), "v1.2.3".to_string());
470//! # metadata.insert("environment".to_string(), "production".to_string());
471//! # let analytics = Analytics { metrics, metadata };
472//! // Pattern matching with comparison operators
473//! assert_struct!(analytics, Analytics {
474//!     metrics: #{
475//!         "views": > 500,           // More than 500 views
476//!         "clicks": >= 10,          // At least 10 clicks
477//!         "conversions": > 0,       // Some conversions
478//!         ..
479//!     },
480//!     metadata: #{
481//!         "version": =~ r"^v\d+\.\d+\.\d+$",  // Semantic version pattern
482//!         "environment": != "development",     // Not in development
483//!         ..
484//!     },
485//! });
486//! ```
487//!
488//! Empty and wildcard map matching:
489//!
490//! ```rust
491//! # use assert_struct::assert_struct;
492//! # use std::collections::HashMap;
493//! # #[derive(Debug)]
494//! # struct Data {
495//! #     cache: HashMap<String, i32>,
496//! #     config: HashMap<String, String>,
497//! # }
498//! # let mut config = HashMap::new();
499//! # config.insert("key".to_string(), "value".to_string());
500//! # let data = Data { cache: HashMap::new(), config };
501//! assert_struct!(data, Data {
502//!     cache: #{},             // Exactly empty map (len() == 0)
503//!     config: #{ .. },        // Any map content (wildcard)
504//! });
505//! ```
506//!
507//! ## Tuples
508//!
509//! Full support for multi-field tuples:
510//!
511//! ```rust
512//! # use assert_struct::assert_struct;
513//! # #[derive(Debug)]
514//! # struct Data { point: (i32, i32), metadata: (String, u32, bool) }
515//! # let data = Data {
516//! #     point: (15, 25),
517//! #     metadata: ("info".to_string(), 100, true),
518//! # };
519//! // Basic tuple matching
520//! assert_struct!(data, Data {
521//!     point: (15, 25),
522//!     metadata: ("info", 100, true),  // String literals work in tuples!
523//! });
524//!
525//! // Advanced patterns
526//! assert_struct!(data, Data {
527//!     point: (> 10, < 30),           // Comparison operators
528//!     metadata: ("info", >= 50, true),
529//! });
530//! ```
531//!
532//! Tuple method calls:
533//!
534//! ```rust
535//! # use assert_struct::assert_struct;
536//! # #[derive(Debug)]
537//! # struct Data { coords: (String, Vec<i32>) }
538//! # let data = Data {
539//! #     coords: ("location".to_string(), vec![1, 2, 3]),
540//! # };
541//! assert_struct!(data, Data {
542//!     coords: (0.len(): 8, 1.len(): 3),  // Method calls on tuple elements
543//! });
544//! ```
545//!
546//! ## Enums (Option/Result/Custom)
547//!
548//! ### Option Types
549//!
550//! ```rust
551//! # use assert_struct::assert_struct;
552//! # #[derive(Debug)]
553//! # struct User { name: Option<String>, age: Option<u32> }
554//! # let user = User { name: Some("Alice".to_string()), age: Some(30) };
555//! assert_struct!(user, User {
556//!     name: Some("Alice"),
557//!     age: Some(30),
558//! });
559//!
560//! // Advanced patterns inside Option
561//! assert_struct!(user, User {
562//!     name: Some("Alice"),
563//!     age: Some(>= 18),      // Adult check inside Some
564//! });
565//! ```
566//!
567//! ### Result Types
568//!
569//! ```rust
570//! # use assert_struct::assert_struct;
571//! # #[derive(Debug)]
572//! # struct Response { result: Result<String, String> }
573//! # let response = Response { result: Ok("success".to_string()) };
574//! assert_struct!(response, Response {
575//!     result: Ok("success"),
576//! });
577//!
578//! // Pattern matching inside Result
579//! # let response = Response { result: Ok("user123".to_string()) };
580//! assert_struct!(response, Response {
581//!     result: Ok(=~ r"^user\d+$"),  // Regex inside Ok
582//! });
583//! ```
584//!
585//! ### Custom Enums
586//!
587//! ```rust
588//! # use assert_struct::assert_struct;
589//! # #[derive(Debug, PartialEq)]
590//! # enum Status { Active, Pending { since: String } }
591//! # #[derive(Debug)]
592//! # struct Account { status: Status }
593//! # let account = Account { status: Status::Pending { since: "2024-01-01".to_string() } };
594//! // Unit variants
595//! let active_account = Account { status: Status::Active };
596//! assert_struct!(active_account, Account {
597//!     status: Status::Active,
598//! });
599//!
600//! // Struct variants with partial matching
601//! assert_struct!(account, Account {
602//!     status: Status::Pending { since: "2024-01-01" },
603//! });
604//! ```
605//!
606//! ## Smart Pointers
607//!
608//! Dereference smart pointers directly in patterns:
609//!
610//! ```rust
611//! # use assert_struct::assert_struct;
612//! # use std::rc::Rc;
613//! # use std::sync::Arc;
614//! # #[derive(Debug)]
615//! # struct Cache {
616//! #     data: Arc<String>,
617//! #     count: Box<i32>,
618//! #     shared: Rc<bool>,
619//! # }
620//! # let cache = Cache {
621//! #     data: Arc::new("cached".to_string()),
622//! #     count: Box::new(42),
623//! #     shared: Rc::new(true),
624//! # };
625//! assert_struct!(cache, Cache {
626//!     *data: "cached",       // Dereference Arc<String>
627//!     *count: > 40,          // Dereference Box<i32> with comparison
628//!     *shared: true,         // Dereference Rc<bool>
629//! });
630//! ```
631//!
632//! Multiple dereferencing for nested pointers:
633//!
634//! ```rust
635//! # use assert_struct::assert_struct;
636//! # #[derive(Debug)]
637//! # struct Nested { value: Box<Box<i32>> }
638//! # let nested = Nested { value: Box::new(Box::new(42)) };
639//! assert_struct!(nested, Nested {
640//!     **value: 42,           // Double dereference
641//! });
642//! ```
643//!
644//! ## Wildcard Patterns
645//!
646//! Use wildcard patterns (`_`) to avoid importing types while still asserting on their structure:
647//!
648//! ```rust
649//! # use assert_struct::assert_struct;
650//! # mod api {
651//! #     #[derive(Debug)]
652//! #     pub struct Response {
653//! #         pub user: User,
654//! #         pub metadata: Metadata,
655//! #     }
656//! #     #[derive(Debug)]
657//! #     pub struct User {
658//! #         pub id: u32,
659//! #         pub name: String,
660//! #     }
661//! #     #[derive(Debug)]
662//! #     pub struct Metadata {
663//! #         pub timestamp: u64,
664//! #         pub version: String,
665//! #     }
666//! # }
667//! # let response = api::Response {
668//! #     user: api::User { id: 123, name: "Alice".to_string() },
669//! #     metadata: api::Metadata { timestamp: 1234567890, version: "1.0".to_string() }
670//! # };
671//! // No need to import User or Metadata types!
672//! // Wildcard patterns always do partial matching, so .. is never required.
673//! assert_struct!(response, _ {
674//!     user: _ {
675//!         id: 123,
676//!         name: "Alice",
677//!     },
678//!     metadata: _ {
679//!         version: "1.0",
680//!     },
681//! });
682//! ```
683//!
684//! This is particularly useful when testing API responses where you don't want to import all the nested types:
685//!
686//! ```rust
687//! # use assert_struct::assert_struct;
688//! # #[derive(Debug)]
689//! # struct JsonResponse { data: Data }
690//! # #[derive(Debug)]
691//! # struct Data { items: Vec<Item>, total: u32 }
692//! # #[derive(Debug)]
693//! # struct Item { id: u32, value: String }
694//! # let json_response = JsonResponse {
695//! #     data: Data {
696//! #         items: vec![Item { id: 1, value: "test".to_string() }],
697//! #         total: 1
698//! #     }
699//! # };
700//! // Test deeply nested structures without imports
701//! assert_struct!(json_response, _ {
702//!     data: _ {
703//!         items: [_ { id: 1, value: "test" }],
704//!         total: 1,
705//!     },
706//! });
707//! ```
708//!
709//! # Error Messages
710//!
711//! When assertions fail, `assert-struct` provides detailed, actionable error messages:
712//!
713//! ## Basic Mismatch
714//!
715//! ```rust,should_panic
716//! # use assert_struct::assert_struct;
717//! # #[derive(Debug)]
718//! # struct User { name: String, age: u32 }
719//! # let user = User { name: "Alice".to_string(), age: 25 };
720//! assert_struct!(user, User {
721//!     name: "Bob",  // This will fail
722//!     age: 25,
723//! });
724//! // Error output:
725//! // assert_struct! failed:
726//! //
727//! // value mismatch:
728//! //   --> `user.name` (src/lib.rs:456)
729//! //   actual: "Alice"
730//! //   expected: "Bob"
731//! ```
732//!
733//! ## Comparison Failure
734//!
735//! ```rust,should_panic
736//! # use assert_struct::assert_struct;
737//! # #[derive(Debug)]
738//! # struct Stats { score: u32 }
739//! # let stats = Stats { score: 50 };
740//! assert_struct!(stats, Stats {
741//!     score: > 100,  // This will fail
742//! });
743//! // Error output:
744//! // assert_struct! failed:
745//! //
746//! // comparison mismatch:
747//! //   --> `stats.score` (src/lib.rs:469)
748//! //   actual: 50
749//! //   expected: > 100
750//! ```
751//!
752//! ## Nested Field Errors
753//!
754//! Error messages show the exact path to the failing field, even in deeply nested structures.
755//! Method calls are also shown in the field path for clear debugging.
756//!
757//! # Advanced Usage
758//!
759//! ## Mixing Structural and Operator Patterns
760//!
761//! Structural patterns and operator patterns are complementary — within a single
762//! `assert_struct!` call, use whichever is most concise for each field. The goal is
763//! the shortest assertion that expresses what you actually care about.
764//!
765//! Structural patterns (`SomeType { field: value, .. }`) are great for navigating
766//! nested types. But for a specific field, writing out the full structural equivalent
767//! can be far more verbose than a simple `==` or `=~` check — especially when the
768//! field's type implements `PartialEq` or `PartialOrd` with a simpler type.
769//!
770//! For example, consider a `Value` enum (similar to `serde_json::Value`) that can
771//! hold different primitive types and implements `PartialEq` with them:
772//!
773//! ```rust
774//! # use assert_struct::assert_struct;
775//! # #[derive(Debug, PartialEq)]
776//! # enum Value { Bool(bool), Int(i64), Text(String) }
777//! # impl PartialEq<bool> for Value {
778//! #     fn eq(&self, other: &bool) -> bool {
779//! #         match self { Value::Bool(b) => b == other, _ => false }
780//! #     }
781//! # }
782//! # impl PartialEq<i64> for Value {
783//! #     fn eq(&self, other: &i64) -> bool {
784//! #         match self { Value::Int(n) => n == other, _ => false }
785//! #     }
786//! # }
787//! # impl PartialEq<&str> for Value {
788//! #     fn eq(&self, other: &&str) -> bool {
789//! #         match self { Value::Text(s) => s == *other, _ => false }
790//! #     }
791//! # }
792//! # impl PartialOrd<i64> for Value {
793//! #     fn partial_cmp(&self, other: &i64) -> Option<std::cmp::Ordering> {
794//! #         match self { Value::Int(n) => n.partial_cmp(other), _ => None }
795//! #     }
796//! # }
797//! # #[derive(Debug)]
798//! # struct Filter { column: usize, value: Value }
799//! # #[derive(Debug)]
800//! # struct Query { table: String, filter: Option<Filter> }
801//! # let query = Query {
802//! #     table: "users".to_string(),
803//! #     filter: Some(Filter { column: 2, value: Value::Text("alice".to_string()) }),
804//! # };
805//! // Structural matching navigates the outer types; operator pattern handles Value
806//! assert_struct!(query, Query {
807//!     table: "users",
808//!     filter: Some(_ {
809//!         column: 2,
810//!         value: == "alice",  // much shorter than Value::Text("alice".to_string())
811//!     }),
812//! });
813//! ```
814//!
815//! The `== "alice"` works because `Value: PartialEq<&str>`. The same applies to
816//! comparison operators — if `Value: PartialOrd<i64>`, you can write `value: > 0i64`
817//! instead of matching the variant.
818//!
819//! The general rule: if writing the structural pattern for a field requires naming
820//! internal types or constructing wrapper values, check whether an operator pattern
821//! expresses the same intent more directly.
822//!
823//! ## Pattern Composition
824//!
825//! Combine multiple patterns for comprehensive assertions:
826//!
827//! ```rust
828//! # use assert_struct::assert_struct;
829//! # #[derive(Debug)]
830//! # struct Complex {
831//! #     data: Option<Vec<i32>>,
832//! #     metadata: (String, u32),
833//! # }
834//! # let complex = Complex {
835//! #     data: Some(vec![1, 2, 3]),
836//! #     metadata: ("info".to_string(), 42),
837//! # };
838//! assert_struct!(complex, Complex {
839//!     data: Some([> 0, > 1, > 2]),              // Option + Vec + comparisons
840//!     metadata: ("info", > 40),                 // Tuple + string + comparison
841//!     ..
842//! });
843//!
844//! // Verify data length separately
845//! assert_eq!(complex.data.as_ref().unwrap().len(), 3);
846//! ```
847//!
848//! ## Real-World Testing Patterns
849//!
850//! See the [examples directory](../../examples/) for comprehensive real-world examples including:
851//! - API response validation
852//! - Database record testing
853//! - Configuration validation
854//! - Event system testing
855//!
856//! For complete specification details, see the [`assert_struct!`] macro documentation.
857
858// Re-export the procedural macro
859pub use assert_struct_macros::assert_struct;
860
861// Error handling module
862#[doc(hidden)]
863pub mod error;
864
865// Hidden module for macro support functions
866#[doc(hidden)]
867pub mod __macro_support {
868    pub use crate::error::{ComparisonOp, ErrorReport, NodeKind, PatternNode, PlainOutputGuard};
869
870    // Re-export regex types for macro expansion when regex feature is enabled
871    #[cfg(feature = "regex")]
872    pub use regex::Regex;
873
874    /// Helper function to enable type inference for closure parameters in assert_struct patterns
875    #[inline]
876    pub fn check_closure_condition<T, F>(value: T, predicate: F) -> bool
877    where
878        F: FnOnce(T) -> bool,
879    {
880        predicate(value)
881    }
882
883    /// Runtime helper for the set pattern `#(...)`.
884    ///
885    /// Checks that `n_elements` satisfies the length constraint, then uses backtracking
886    /// to find a 1-to-1 assignment of patterns to elements. Each predicate returns `true`
887    /// if the element at the given index matches its pattern.
888    ///
889    /// On failure, pushes exactly one error to `report`.
890    pub fn set_match(
891        n_elements: usize,
892        rest: bool,
893        predicates: &[&dyn Fn(usize) -> bool],
894        report: &mut ErrorReport,
895        node: &'static PatternNode,
896    ) {
897        let n_patterns = predicates.len();
898
899        // Length check
900        let length_ok = if rest {
901            n_elements >= n_patterns
902        } else {
903            n_elements == n_patterns
904        };
905
906        if !length_ok {
907            let expected_str = if rest {
908                format!("at least {} element(s)", n_patterns)
909            } else {
910                format!("{} element(s)", n_patterns)
911            };
912            report.push(
913                node,
914                format!("{} element(s)", n_elements),
915                Some(expected_str),
916            );
917            return;
918        }
919
920        // Backtracking assignment
921        let mut matched = vec![false; n_elements];
922        if !set_backtrack(predicates, &mut matched, 0) {
923            report.push(node, format!("{} element(s)", n_elements), None);
924        }
925    }
926
927    fn set_backtrack(
928        predicates: &[&dyn Fn(usize) -> bool],
929        matched: &mut [bool],
930        pattern_idx: usize,
931    ) -> bool {
932        if pattern_idx == predicates.len() {
933            return true;
934        }
935        for i in 0..matched.len() {
936            if !matched[i] && predicates[pattern_idx](i) {
937                matched[i] = true;
938                if set_backtrack(predicates, matched, pattern_idx + 1) {
939                    return true;
940                }
941                matched[i] = false;
942            }
943        }
944        false
945    }
946}
947
948/// A trait for pattern matching, similar to `PartialEq` but for flexible matching.
949///
950/// The `Like` trait enables custom pattern matching logic beyond simple equality.
951/// It's primarily used with the `=~` operator in `assert_struct!` macro to support
952/// regex patterns, custom matching logic, and other pattern-based comparisons.
953///
954/// # Examples
955///
956/// ## Basic String Pattern Matching
957///
958/// ```
959/// # #[cfg(feature = "regex")]
960/// # {
961/// use assert_struct::Like;
962///
963/// // Using Like trait directly
964/// let text = "hello@example.com";
965/// assert!(text.like(&r".*@example\.com"));
966/// # }
967/// ```
968///
969/// ## Custom Implementation
970///
971/// ```
972/// use assert_struct::Like;
973///
974/// struct EmailAddress(String);
975///
976/// struct DomainPattern {
977///     domain: String,
978/// }
979///
980/// impl Like<DomainPattern> for EmailAddress {
981///     fn like(&self, pattern: &DomainPattern) -> bool {
982///         self.0.ends_with(&format!("@{}", pattern.domain))
983///     }
984/// }
985///
986/// let email = EmailAddress("user@example.com".to_string());
987/// let pattern = DomainPattern { domain: "example.com".to_string() };
988/// assert!(email.like(&pattern));
989/// ```
990pub trait Like<Rhs = Self> {
991    /// Returns `true` if `self` matches the pattern `other`.
992    ///
993    /// # Examples
994    ///
995    /// ```
996    /// # #[cfg(feature = "regex")]
997    /// # {
998    /// use assert_struct::Like;
999    ///
1000    /// let s = "test123";
1001    /// assert!(s.like(&r"\w+\d+"));
1002    /// # }
1003    /// ```
1004    fn like(&self, other: &Rhs) -> bool;
1005}
1006
1007// String/&str implementations for regex pattern matching
1008#[cfg(feature = "regex")]
1009mod like_impls {
1010    use super::Like;
1011
1012    /// Implementation of Like for String with &str patterns (interpreted as regex)
1013    impl Like<&str> for String {
1014        fn like(&self, pattern: &&str) -> bool {
1015            regex::Regex::new(pattern)
1016                .map(|re| re.is_match(self))
1017                .unwrap_or(false)
1018        }
1019    }
1020
1021    /// Implementation of Like for String with String patterns (interpreted as regex)
1022    impl Like<String> for String {
1023        fn like(&self, pattern: &String) -> bool {
1024            self.like(&pattern.as_str())
1025        }
1026    }
1027
1028    /// Implementation of Like for &str with &str patterns (interpreted as regex)
1029    impl Like<&str> for &str {
1030        fn like(&self, pattern: &&str) -> bool {
1031            regex::Regex::new(pattern)
1032                .map(|re| re.is_match(self))
1033                .unwrap_or(false)
1034        }
1035    }
1036
1037    /// Implementation of Like for &str with String patterns (interpreted as regex)
1038    impl Like<String> for &str {
1039        fn like(&self, pattern: &String) -> bool {
1040            self.like(&pattern.as_str())
1041        }
1042    }
1043
1044    /// Implementation of Like for String with pre-compiled Regex
1045    impl Like<regex::Regex> for String {
1046        fn like(&self, pattern: &regex::Regex) -> bool {
1047            pattern.is_match(self)
1048        }
1049    }
1050
1051    /// Implementation of Like for &str with pre-compiled Regex
1052    impl Like<regex::Regex> for &str {
1053        fn like(&self, pattern: &regex::Regex) -> bool {
1054            pattern.is_match(self)
1055        }
1056    }
1057}