toasty_core/stmt/
like.rs

1//! Like trait implementations for semantic pattern matching with assert_struct!
2//!
3//! This module provides essential implementations of the `Like` trait for Toasty expression types,
4//! enabling semantic validation that works across different structural representations
5//! of the same logical data.
6//!
7//! The primary use case is handling polymorphic AST structures where different database
8//! drivers generate different representations for the same semantic content.
9
10use super::{Expr, ExprSet, Value};
11use assert_struct::Like;
12use uuid::Uuid;
13
14/// Helper function to extract Values from an Expr (handles both polymorphic representations)
15fn extract_exprs_from_record(expr: &Expr) -> Option<Vec<Expr>> {
16    match expr {
17        Expr::Value(Value::Record(record)) => {
18            Some(record.fields.iter().cloned().map(Into::into).collect())
19        }
20        Expr::Record(record) => Some(record.fields.clone()),
21        _ => None,
22    }
23}
24
25impl Like<Value> for Expr {
26    fn like(&self, other: &Value) -> bool {
27        match (self, other) {
28            (Expr::Value(value), rhs) => value == rhs,
29            (Expr::Record(lhs), Value::Record(rhs)) => {
30                lhs.len() == rhs.len()
31                    && lhs
32                        .fields
33                        .iter()
34                        .zip(rhs.fields.iter())
35                        .all(|(lhs, rhs)| lhs.like(rhs))
36            }
37            _ => false,
38        }
39    }
40}
41
42/// Like implementation for expressions against Vec<Value> patterns
43impl Like<Vec<Value>> for Expr {
44    fn like(&self, pattern: &Vec<Value>) -> bool {
45        self.like(&&pattern[..])
46    }
47}
48
49/// Like implementation for expressions against arrays of Values
50impl<const N: usize> Like<[Value; N]> for Expr {
51    fn like(&self, pattern: &[Value; N]) -> bool {
52        self.like(&&pattern[..])
53    }
54}
55
56impl Like<&[Value]> for Expr {
57    fn like(&self, other: &&[Value]) -> bool {
58        if let Some(values) = extract_exprs_from_record(self) {
59            values.len() == other.len()
60                && values
61                    .iter()
62                    .zip(*other)
63                    .all(|(value, expected)| value.like(expected))
64        } else {
65            false
66        }
67    }
68}
69
70impl Like<&str> for Value {
71    fn like(&self, pattern: &&str) -> bool {
72        matches!(self, Value::String(value) if value == pattern)
73    }
74}
75
76impl Like<&str> for Expr {
77    fn like(&self, pattern: &&str) -> bool {
78        matches!(self, Expr::Value(value) if value.like(pattern))
79    }
80}
81
82impl Like<&[u8]> for Value {
83    fn like(&self, pattern: &&[u8]) -> bool {
84        matches!(self, Value::Bytes(value) if value == pattern)
85    }
86}
87
88impl Like<&[u8]> for Expr {
89    fn like(&self, pattern: &&[u8]) -> bool {
90        matches!(self, Expr::Value(value) if value.like(pattern))
91    }
92}
93
94/// Convenience implementation for matching Value against String
95impl Like<String> for Value {
96    fn like(&self, pattern: &String) -> bool {
97        matches!(self, Value::String(s) if s == pattern)
98    }
99}
100
101impl Like<&String> for Value {
102    fn like(&self, pattern: &&String) -> bool {
103        self.like(&**pattern)
104    }
105}
106
107/// Like implementation for Expr and String (delegates to PartialEq)
108impl Like<String> for Expr {
109    fn like(&self, pattern: &String) -> bool {
110        self == pattern
111    }
112}
113
114impl Like<&String> for Expr {
115    fn like(&self, pattern: &&String) -> bool {
116        self == *pattern
117    }
118}
119
120impl Like<Uuid> for Value {
121    fn like(&self, other: &Uuid) -> bool {
122        matches!(self, Value::Uuid(value) if value == other)
123    }
124}
125
126impl Like<Uuid> for Expr {
127    fn like(&self, other: &Uuid) -> bool {
128        matches!(self, Expr::Value(value) if value.like(other))
129    }
130}
131
132/// Macro to generate Like implementations for tuple patterns using Alice's pattern
133///
134/// Based on: https://users.rust-lang.org/t/macro-to-impl-trait-for-tuple/79165/2
135/// Takes index-type pairs and generates both Expr and Value implementations.
136macro_rules! impl_like_tuple {
137    ($($idx:tt $t:ident),+) => {
138        #[allow(non_snake_case)]
139        impl<$($t),+> Like<($($t,)+)> for Expr
140        where
141            $(Expr: Like<$t>),+
142        {
143            fn like(&self, pattern: &($($t,)+)) -> bool {
144                if let Some(exprs) = extract_exprs_from_record(self) {
145                    exprs.len() == impl_like_tuple!(@count $($t)+) && $(
146                        exprs[$idx].like(&pattern.$idx)
147                    )&&+
148                } else {
149                    false
150                }
151            }
152        }
153
154        #[allow(non_snake_case)]
155        impl<$($t),+> Like<($($t,)+)> for Value
156        where
157            $(Value: Like<$t>),+
158        {
159            fn like(&self, pattern: &($($t,)+)) -> bool {
160                if let Value::Record(record) = self {
161                    record.fields.len() == impl_like_tuple!(@count $($t)+) && $(
162                        record.fields[$idx].like(&pattern.$idx)
163                    )&&+
164                } else {
165                    false
166                }
167            }
168        }
169    };
170
171    // Helper to count elements
172    (@count $t:ident) => (1);
173    (@count $t:ident $($ts:ident)+) => (1 + impl_like_tuple!(@count $($ts)+));
174}
175
176// Generate implementations for tuples from size 1 to 12
177// Using Alice's clean pattern: index-type pairs
178impl_like_tuple!(0 T1);
179impl_like_tuple!(0 T1, 1 T2);
180impl_like_tuple!(0 T1, 1 T2, 2 T3);
181impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4);
182impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5);
183impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6);
184impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7);
185impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8);
186impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9);
187impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10);
188impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10, 10 T11);
189impl_like_tuple!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10, 10 T11, 11 T12);
190
191/// Support for matching ExprSet against arrays of any length using const generics
192impl<T, const N: usize> Like<[T; N]> for ExprSet
193where
194    Expr: Like<T>,
195{
196    fn like(&self, pattern: &[T; N]) -> bool {
197        match self {
198            ExprSet::Values(values) => {
199                values.rows.len() == N
200                    && values
201                        .rows
202                        .iter()
203                        .zip(pattern)
204                        .all(|(expr, p)| expr.like(p))
205            }
206            _ => false,
207        }
208    }
209}
210
211/// Support for matching ExprSet against Vec patterns
212impl<T> Like<Vec<T>> for ExprSet
213where
214    Expr: Like<T>,
215{
216    fn like(&self, pattern: &Vec<T>) -> bool {
217        match self {
218            ExprSet::Values(values) => {
219                values.rows.len() == pattern.len()
220                    && values
221                        .rows
222                        .iter()
223                        .zip(pattern)
224                        .all(|(expr, p)| expr.like(p))
225            }
226            _ => false,
227        }
228    }
229}
230
231impl<const N: usize> Like<[&str; N]> for Expr {
232    fn like(&self, other: &[&str; N]) -> bool {
233        match self {
234            Expr::Value(Value::Record(v)) if v.len() == other.len() => {
235                v.iter().zip(other.iter()).all(|(lhs, rhs)| lhs.like(rhs))
236            }
237            Expr::Record(v) if v.len() == other.len() => {
238                v.iter().zip(other.iter()).all(|(lhs, rhs)| lhs.like(rhs))
239            }
240            _ => false,
241        }
242    }
243}
244
245impl<const N: usize> Like<&[u8; N]> for Expr {
246    fn like(&self, other: &&[u8; N]) -> bool {
247        self.like(&&other[..])
248    }
249}