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