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: ®ex::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: ®ex::Regex) -> bool {
851 pattern.is_match(self)
852 }
853 }
854}