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