Skip to main content

fraiseql_wire/operators/where_operator/
mod.rs

1//! WHERE clause operators
2//!
3//! Type-safe operator definitions for WHERE clause generation.
4//! Supports 25+ operators across 5 categories with both JSONB and direct column sources.
5
6use super::field::{Field, Value};
7
8/// WHERE clause operators
9///
10/// Supports type-safe, audit-friendly WHERE clause construction
11/// without raw SQL strings.
12///
13/// # Categories
14///
15/// - **Comparison**: Eq, Neq, Gt, Gte, Lt, Lte
16/// - **Array**: In, Nin, Contains, `ArrayContains`, `ArrayContainedBy`, `ArrayOverlaps`
17/// - **Array Length**: `LenEq`, `LenGt`, `LenGte`, `LenLt`, `LenLte`
18/// - **String**: Icontains, Startswith, Istartswith, Endswith, Iendswith, Like, Ilike
19/// - **Null**: `IsNull`
20/// - **Vector Distance**: `L2Distance`, `CosineDistance`, `InnerProduct`, `L1Distance`, `HammingDistance`, `JaccardDistance`
21/// - **Full-Text Search**: Matches, `PlainQuery`, `PhraseQuery`, `WebsearchQuery`
22/// - **Network**: `IsIPv4`, `IsIPv6`, `IsPrivate`, `IsLoopback`, `InSubnet`, `ContainsSubnet`, `ContainsIP`, `IPRangeOverlap`
23/// - **JSONB**: `StrictlyContains`
24/// - **`LTree`**: `AncestorOf`, `DescendantOf`, `MatchesLquery`, `MatchesLtxtquery`, `MatchesAnyLquery`,
25///   `DepthEq`, `DepthNeq`, `DepthGt`, `DepthGte`, `DepthLt`, `DepthLte`, Lca
26#[derive(Debug, Clone)]
27#[non_exhaustive]
28pub enum WhereOperator {
29    // ============ Comparison Operators ============
30    /// Equal: `field = value`
31    Eq(Field, Value),
32
33    /// Not equal: `field != value` or `field <> value`
34    Neq(Field, Value),
35
36    /// Greater than: `field > value`
37    Gt(Field, Value),
38
39    /// Greater than or equal: `field >= value`
40    Gte(Field, Value),
41
42    /// Less than: `field < value`
43    Lt(Field, Value),
44
45    /// Less than or equal: `field <= value`
46    Lte(Field, Value),
47
48    // ============ Array Operators ============
49    /// Array contains value: `field IN (...)`
50    In(Field, Vec<Value>),
51
52    /// Array does not contain value: `field NOT IN (...)`
53    Nin(Field, Vec<Value>),
54
55    /// String contains substring: `field LIKE '%substring%'`
56    ///
57    /// # Warning — LIKE wildcard injection
58    ///
59    /// The substring is embedded between `%` anchors. Characters `%` (any sequence) and
60    /// `_` (any single character) in the substring itself are **also** treated as LIKE
61    /// wildcards, causing broader matches than intended. Escape user-supplied values
62    /// (replace `%` → `\%` and `_` → `\_`) before constructing this variant.
63    Contains(Field, String),
64
65    /// Array contains element: PostgreSQL array operator `@>`
66    /// Generated SQL: `field @> array[value]`
67    ArrayContains(Field, Value),
68
69    /// Array is contained by: PostgreSQL array operator `<@`
70    /// Generated SQL: `field <@ array[value]`
71    ArrayContainedBy(Field, Value),
72
73    /// Arrays overlap: PostgreSQL array operator `&&`
74    /// Generated SQL: `field && array[value]`
75    ArrayOverlaps(Field, Vec<Value>),
76
77    // ============ Array Length Operators ============
78    /// Array length equals: `array_length(field, 1) = value`
79    LenEq(Field, usize),
80
81    /// Array length greater than: `array_length(field, 1) > value`
82    LenGt(Field, usize),
83
84    /// Array length greater than or equal: `array_length(field, 1) >= value`
85    LenGte(Field, usize),
86
87    /// Array length less than: `array_length(field, 1) < value`
88    LenLt(Field, usize),
89
90    /// Array length less than or equal: `array_length(field, 1) <= value`
91    LenLte(Field, usize),
92
93    // ============ String Operators ============
94    /// Case-insensitive contains: `field ILIKE '%substring%'`
95    ///
96    /// # Warning — LIKE wildcard injection
97    ///
98    /// Same escaping requirements as [`WhereOperator::Contains`]. ILIKE applies the same
99    /// wildcard semantics, so `%` and `_` in the substring expand unexpectedly.
100    Icontains(Field, String),
101
102    /// Starts with: `field LIKE 'prefix%'`
103    ///
104    /// # Warning — LIKE wildcard injection
105    ///
106    /// The prefix string is passed verbatim before the trailing `%`. Characters `%` and `_`
107    /// in the prefix are treated as wildcards; escape user-supplied values before use.
108    Startswith(Field, String),
109
110    /// Starts with (case-insensitive): `field ILIKE 'prefix%'`
111    ///
112    /// # Warning — LIKE wildcard injection
113    ///
114    /// Same escaping requirements as [`WhereOperator::Startswith`].
115    Istartswith(Field, String),
116
117    /// Ends with: `field LIKE '%suffix'`
118    ///
119    /// # Warning — LIKE wildcard injection
120    ///
121    /// The suffix string is passed verbatim after the leading `%`. Characters `%` and `_`
122    /// in the suffix are treated as wildcards; escape user-supplied values before use.
123    Endswith(Field, String),
124
125    /// Ends with (case-insensitive): `field ILIKE '%suffix'`
126    ///
127    /// # Warning — LIKE wildcard injection
128    ///
129    /// Same escaping requirements as [`WhereOperator::Endswith`].
130    Iendswith(Field, String),
131
132    /// LIKE pattern matching: `field LIKE pattern`
133    ///
134    /// # Warning — LIKE wildcard injection
135    ///
136    /// The pattern string is passed to the database as-is. Characters `%` (any
137    /// sequence) and `_` (any single character) are SQL LIKE wildcards. If the
138    /// pattern originates from user input, callers **must** escape these
139    /// characters (e.g. replace `%` → `\%` and `_` → `\_`) before constructing
140    /// this variant, and append the appropriate `ESCAPE '\'` clause.
141    Like(Field, String),
142
143    /// Case-insensitive LIKE: `field ILIKE pattern`
144    ///
145    /// # Warning — LIKE wildcard injection
146    ///
147    /// Same escaping requirements as [`WhereOperator::Like`]. The `%` and `_`
148    /// wildcards apply to ILIKE patterns as well.
149    Ilike(Field, String),
150
151    // ============ Null Operator ============
152    /// IS NULL: `field IS NULL` or `field IS NOT NULL`
153    ///
154    /// When the boolean is true, generates `IS NULL`
155    /// When false, generates `IS NOT NULL`
156    IsNull(Field, bool),
157
158    // ============ Vector Distance Operators (pgvector) ============
159    /// L2 (Euclidean) distance: `l2_distance(field, vector) < threshold`
160    ///
161    /// Requires pgvector extension.
162    L2Distance {
163        /// The vector field to compare against
164        field: Field,
165        /// The embedding vector for distance calculation
166        vector: Vec<f32>,
167        /// Distance threshold for comparison
168        threshold: f32,
169    },
170
171    /// Cosine distance: `cosine_distance(field, vector) < threshold`
172    ///
173    /// Requires pgvector extension.
174    CosineDistance {
175        /// The vector field to compare against
176        field: Field,
177        /// The embedding vector for distance calculation
178        vector: Vec<f32>,
179        /// Distance threshold for comparison
180        threshold: f32,
181    },
182
183    /// Inner product: `inner_product(field, vector) > threshold`
184    ///
185    /// Requires pgvector extension.
186    InnerProduct {
187        /// The vector field to compare against
188        field: Field,
189        /// The embedding vector for distance calculation
190        vector: Vec<f32>,
191        /// Distance threshold for comparison
192        threshold: f32,
193    },
194
195    /// L1 (Manhattan) distance: `l1_distance(field, vector) < threshold`
196    ///
197    /// Requires pgvector extension.
198    L1Distance {
199        /// The vector field to compare against
200        field: Field,
201        /// The embedding vector for distance calculation
202        vector: Vec<f32>,
203        /// Distance threshold for comparison
204        threshold: f32,
205    },
206
207    /// Hamming distance: `hamming_distance(field, vector) < threshold`
208    ///
209    /// Requires pgvector extension. Works with bit vectors.
210    HammingDistance {
211        /// The vector field to compare against
212        field: Field,
213        /// The embedding vector for distance calculation
214        vector: Vec<f32>,
215        /// Distance threshold for comparison
216        threshold: f32,
217    },
218
219    /// Jaccard distance: `jaccard_distance(field, set) < threshold`
220    ///
221    /// Works with text arrays, measures set overlap.
222    JaccardDistance {
223        /// The field to compare against
224        field: Field,
225        /// The set of values for comparison
226        set: Vec<String>,
227        /// Distance threshold for comparison
228        threshold: f32,
229    },
230
231    // ============ Full-Text Search Operators ============
232    /// Full-text search with language: `field @@ plainto_tsquery(language, query)`
233    ///
234    /// If language is None, defaults to 'english'
235    Matches {
236        /// The text field to search
237        field: Field,
238        /// The search query
239        query: String,
240        /// Optional language for text search (default: english)
241        language: Option<String>,
242    },
243
244    /// Plain text query: `field @@ plainto_tsquery(query)`
245    ///
246    /// Uses no language specification
247    PlainQuery {
248        /// The text field to search
249        field: Field,
250        /// The search query
251        query: String,
252    },
253
254    /// Phrase query with language: `field @@ phraseto_tsquery(language, query)`
255    ///
256    /// If language is None, defaults to 'english'
257    PhraseQuery {
258        /// The text field to search
259        field: Field,
260        /// The search query
261        query: String,
262        /// Optional language for text search (default: english)
263        language: Option<String>,
264    },
265
266    /// Web search query with language: `field @@ websearch_to_tsquery(language, query)`
267    ///
268    /// If language is None, defaults to 'english'
269    WebsearchQuery {
270        /// The text field to search
271        field: Field,
272        /// The search query
273        query: String,
274        /// Optional language for text search (default: english)
275        language: Option<String>,
276    },
277
278    // ============ Network/INET Operators ============
279    /// Check if IP is `IPv4`: `family(field) = 4`
280    IsIPv4(Field),
281
282    /// Check if IP is `IPv6`: `family(field) = 6`
283    IsIPv6(Field),
284
285    /// Check if IP is private (RFC1918/ULA): matches private ranges.
286    ///
287    /// When `value` is `true`, generates a positive containment check.
288    /// When `value` is `false`, generates the negated check (equivalent to "is public").
289    IsPrivate {
290        /// The IP field to check
291        field: Field,
292        /// `true` = is private, `false` = is public (NOT private)
293        value: bool,
294    },
295
296    /// Check if IP is loopback: `IPv4` 127.0.0.0/8 or `IPv6` `::1/128`.
297    ///
298    /// When `value` is `false`, generates the negated check.
299    IsLoopback {
300        /// The IP field to check
301        field: Field,
302        /// `true` = is loopback, `false` = is NOT loopback
303        value: bool,
304    },
305
306    /// Check if IP is multicast: `IPv4` 224.0.0.0/4 or `IPv6` ff00::/8 (RFC 3171, RFC 4291).
307    ///
308    /// When `value` is `false`, generates the negated check.
309    IsMulticast {
310        /// The IP field to check
311        field: Field,
312        /// `true` = is multicast, `false` = is NOT multicast
313        value: bool,
314    },
315
316    /// Check if IP is link-local: `IPv4` 169.254.0.0/16 or `IPv6` fe80::/10 (RFC 3927, RFC 4291).
317    ///
318    /// When `value` is `false`, generates the negated check.
319    IsLinkLocal {
320        /// The IP field to check
321        field: Field,
322        /// `true` = is link-local, `false` = is NOT link-local
323        value: bool,
324    },
325
326    /// Check if IP is in a documentation range (RFC 5737, RFC 3849):
327    /// `IPv4` 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24; `IPv6` 2001:db8::/32.
328    ///
329    /// When `value` is `false`, generates the negated check.
330    IsDocumentation {
331        /// The IP field to check
332        field: Field,
333        /// `true` = is documentation, `false` = is NOT documentation
334        value: bool,
335    },
336
337    /// Check if IP is in carrier-grade NAT range (RFC 6598): 100.64.0.0/10 (IPv4 only).
338    ///
339    /// When `value` is `false`, generates the negated check.
340    IsCarrierGrade {
341        /// The IP field to check
342        field: Field,
343        /// `true` = is carrier-grade, `false` = is NOT carrier-grade
344        value: bool,
345    },
346
347    /// IP is in subnet: `field << subnet`
348    ///
349    /// The subnet should be in CIDR notation (e.g., "192.168.0.0/24")
350    InSubnet {
351        /// The IP field to check
352        field: Field,
353        /// The CIDR subnet (e.g., "192.168.0.0/24")
354        subnet: String,
355    },
356
357    /// Network contains subnet: `field >> subnet`
358    ///
359    /// The subnet should be in CIDR notation
360    ContainsSubnet {
361        /// The network field to check
362        field: Field,
363        /// The CIDR subnet to check for containment
364        subnet: String,
365    },
366
367    /// Network/range contains IP: `field >> ip`
368    ///
369    /// The IP should be a single address (e.g., "192.168.1.1")
370    ContainsIP {
371        /// The network field to check
372        field: Field,
373        /// The IP address to check for containment
374        ip: String,
375    },
376
377    /// IP ranges overlap: `field && range`
378    ///
379    /// The range should be in CIDR notation
380    IPRangeOverlap {
381        /// The IP range field to check
382        field: Field,
383        /// The IP range to check for overlap
384        range: String,
385    },
386
387    // ============ JSONB Operators ============
388    /// JSONB strictly contains: `field @> value`
389    ///
390    /// Checks if the JSONB field strictly contains the given value
391    StrictlyContains(Field, Value),
392
393    // ============ LTree Operators (Hierarchical) ============
394    /// Ancestor of: `field @> path`
395    ///
396    /// Checks if the ltree field is an ancestor of the given path
397    AncestorOf {
398        /// The ltree field to check
399        field: Field,
400        /// The path to check ancestry against
401        path: String,
402    },
403
404    /// Descendant of: `field <@ path`
405    ///
406    /// Checks if the ltree field is a descendant of the given path
407    DescendantOf {
408        /// The ltree field to check
409        field: Field,
410        /// The path to check descendancy against
411        path: String,
412    },
413
414    /// Matches lquery: `field ~ lquery`
415    ///
416    /// Checks if the ltree field matches the given lquery pattern
417    MatchesLquery {
418        /// The ltree field to check
419        field: Field,
420        /// The lquery pattern to match against
421        pattern: String,
422    },
423
424    /// Matches ltxtquery: `field @ ltxtquery`
425    ///
426    /// Checks if the ltree field matches the given ltxtquery pattern (Boolean query syntax)
427    MatchesLtxtquery {
428        /// The ltree field to check
429        field: Field,
430        /// The ltxtquery pattern to match against (e.g., "Science & !Deprecated")
431        query: String,
432    },
433
434    /// Matches any lquery: `field ? array[lqueries]`
435    ///
436    /// Checks if the ltree field matches any of the given lquery patterns
437    MatchesAnyLquery {
438        /// The ltree field to check
439        field: Field,
440        /// Array of lquery patterns to match against
441        patterns: Vec<String>,
442    },
443
444    /// `LTree` depth equals: `nlevel(field) = depth`
445    DepthEq {
446        /// The ltree field to check
447        field: Field,
448        /// The depth value to compare
449        depth: usize,
450    },
451
452    /// `LTree` depth not equals: `nlevel(field) != depth`
453    DepthNeq {
454        /// The ltree field to check
455        field: Field,
456        /// The depth value to compare
457        depth: usize,
458    },
459
460    /// `LTree` depth greater than: `nlevel(field) > depth`
461    DepthGt {
462        /// The ltree field to check
463        field: Field,
464        /// The depth value to compare
465        depth: usize,
466    },
467
468    /// `LTree` depth greater than or equal: `nlevel(field) >= depth`
469    DepthGte {
470        /// The ltree field to check
471        field: Field,
472        /// The depth value to compare
473        depth: usize,
474    },
475
476    /// `LTree` depth less than: `nlevel(field) < depth`
477    DepthLt {
478        /// The ltree field to check
479        field: Field,
480        /// The depth value to compare
481        depth: usize,
482    },
483
484    /// `LTree` depth less than or equal: `nlevel(field) <= depth`
485    DepthLte {
486        /// The ltree field to check
487        field: Field,
488        /// The depth value to compare
489        depth: usize,
490    },
491
492    /// Lowest common ancestor: `lca(field, paths)`
493    ///
494    /// Checks if the ltree field equals the lowest common ancestor of the given paths
495    Lca {
496        /// The ltree field to check
497        field: Field,
498        /// The paths to find LCA of
499        paths: Vec<String>,
500    },
501
502    // ============ LTree ID-Based Operators ============
503    /// Descendant of entity by ID: `path <@ (SELECT path FROM t WHERE id = $1)`
504    ///
505    /// Resolves the ltree path from the entity's UUID before performing the
506    /// descendant comparison. The UUID value is the entity ID to resolve.
507    DescendantOfId {
508        /// The ltree field (or relationship field) to filter
509        field: Field,
510        /// The UUID of the entity whose path to resolve
511        id: String,
512    },
513
514    /// Ancestor of entity by ID: `path @> (SELECT path FROM t WHERE id = $1)`
515    ///
516    /// Resolves the ltree path from the entity's UUID before performing the
517    /// ancestor comparison. The UUID value is the entity ID to resolve.
518    AncestorOfId {
519        /// The ltree field (or relationship field) to filter
520        field: Field,
521        /// The UUID of the entity whose path to resolve
522        id: String,
523    },
524}
525
526impl WhereOperator {
527    /// Get a human-readable name for this operator
528    #[must_use]
529    pub const fn name(&self) -> &'static str {
530        match self {
531            WhereOperator::Eq(_, _) => "Eq",
532            WhereOperator::Neq(_, _) => "Neq",
533            WhereOperator::Gt(_, _) => "Gt",
534            WhereOperator::Gte(_, _) => "Gte",
535            WhereOperator::Lt(_, _) => "Lt",
536            WhereOperator::Lte(_, _) => "Lte",
537            WhereOperator::In(_, _) => "In",
538            WhereOperator::Nin(_, _) => "Nin",
539            WhereOperator::Contains(_, _) => "Contains",
540            WhereOperator::ArrayContains(_, _) => "ArrayContains",
541            WhereOperator::ArrayContainedBy(_, _) => "ArrayContainedBy",
542            WhereOperator::ArrayOverlaps(_, _) => "ArrayOverlaps",
543            WhereOperator::LenEq(_, _) => "LenEq",
544            WhereOperator::LenGt(_, _) => "LenGt",
545            WhereOperator::LenGte(_, _) => "LenGte",
546            WhereOperator::LenLt(_, _) => "LenLt",
547            WhereOperator::LenLte(_, _) => "LenLte",
548            WhereOperator::Icontains(_, _) => "Icontains",
549            WhereOperator::Startswith(_, _) => "Startswith",
550            WhereOperator::Istartswith(_, _) => "Istartswith",
551            WhereOperator::Endswith(_, _) => "Endswith",
552            WhereOperator::Iendswith(_, _) => "Iendswith",
553            WhereOperator::Like(_, _) => "Like",
554            WhereOperator::Ilike(_, _) => "Ilike",
555            WhereOperator::IsNull(_, _) => "IsNull",
556            WhereOperator::L2Distance { .. } => "L2Distance",
557            WhereOperator::CosineDistance { .. } => "CosineDistance",
558            WhereOperator::InnerProduct { .. } => "InnerProduct",
559            WhereOperator::L1Distance { .. } => "L1Distance",
560            WhereOperator::HammingDistance { .. } => "HammingDistance",
561            WhereOperator::JaccardDistance { .. } => "JaccardDistance",
562            WhereOperator::Matches { .. } => "Matches",
563            WhereOperator::PlainQuery { .. } => "PlainQuery",
564            WhereOperator::PhraseQuery { .. } => "PhraseQuery",
565            WhereOperator::WebsearchQuery { .. } => "WebsearchQuery",
566            WhereOperator::IsIPv4(_) => "IsIPv4",
567            WhereOperator::IsIPv6(_) => "IsIPv6",
568            WhereOperator::IsPrivate { .. } => "IsPrivate",
569            WhereOperator::IsLoopback { .. } => "IsLoopback",
570            WhereOperator::IsMulticast { .. } => "IsMulticast",
571            WhereOperator::IsLinkLocal { .. } => "IsLinkLocal",
572            WhereOperator::IsDocumentation { .. } => "IsDocumentation",
573            WhereOperator::IsCarrierGrade { .. } => "IsCarrierGrade",
574            WhereOperator::InSubnet { .. } => "InSubnet",
575            WhereOperator::ContainsSubnet { .. } => "ContainsSubnet",
576            WhereOperator::ContainsIP { .. } => "ContainsIP",
577            WhereOperator::IPRangeOverlap { .. } => "IPRangeOverlap",
578            WhereOperator::StrictlyContains(_, _) => "StrictlyContains",
579            WhereOperator::AncestorOf { .. } => "AncestorOf",
580            WhereOperator::DescendantOf { .. } => "DescendantOf",
581            WhereOperator::MatchesLquery { .. } => "MatchesLquery",
582            WhereOperator::MatchesLtxtquery { .. } => "MatchesLtxtquery",
583            WhereOperator::MatchesAnyLquery { .. } => "MatchesAnyLquery",
584            WhereOperator::DepthEq { .. } => "DepthEq",
585            WhereOperator::DepthNeq { .. } => "DepthNeq",
586            WhereOperator::DepthGt { .. } => "DepthGt",
587            WhereOperator::DepthGte { .. } => "DepthGte",
588            WhereOperator::DepthLt { .. } => "DepthLt",
589            WhereOperator::DepthLte { .. } => "DepthLte",
590            WhereOperator::Lca { .. } => "Lca",
591            WhereOperator::DescendantOfId { .. } => "DescendantOfId",
592            WhereOperator::AncestorOfId { .. } => "AncestorOfId",
593        }
594    }
595
596    /// Validate operator for basic correctness
597    ///
598    /// # Errors
599    ///
600    /// Returns an error string if the field name is invalid or, for vector distance
601    /// operators, the threshold is not a finite number.
602    pub fn validate(&self) -> Result<(), String> {
603        match self {
604            WhereOperator::Eq(f, _)
605            | WhereOperator::Neq(f, _)
606            | WhereOperator::Gt(f, _)
607            | WhereOperator::Gte(f, _)
608            | WhereOperator::Lt(f, _)
609            | WhereOperator::Lte(f, _)
610            | WhereOperator::In(f, _)
611            | WhereOperator::Nin(f, _)
612            | WhereOperator::Contains(f, _)
613            | WhereOperator::ArrayContains(f, _)
614            | WhereOperator::ArrayContainedBy(f, _)
615            | WhereOperator::ArrayOverlaps(f, _)
616            | WhereOperator::LenEq(f, _)
617            | WhereOperator::LenGt(f, _)
618            | WhereOperator::LenGte(f, _)
619            | WhereOperator::LenLt(f, _)
620            | WhereOperator::LenLte(f, _)
621            | WhereOperator::Icontains(f, _)
622            | WhereOperator::Startswith(f, _)
623            | WhereOperator::Istartswith(f, _)
624            | WhereOperator::Endswith(f, _)
625            | WhereOperator::Iendswith(f, _)
626            | WhereOperator::Like(f, _)
627            | WhereOperator::Ilike(f, _)
628            | WhereOperator::IsNull(f, _)
629            | WhereOperator::StrictlyContains(f, _) => f.validate(),
630
631            WhereOperator::L2Distance {
632                field, threshold, ..
633            }
634            | WhereOperator::CosineDistance {
635                field, threshold, ..
636            }
637            | WhereOperator::InnerProduct {
638                field, threshold, ..
639            }
640            | WhereOperator::L1Distance {
641                field, threshold, ..
642            }
643            | WhereOperator::HammingDistance {
644                field, threshold, ..
645            }
646            | WhereOperator::JaccardDistance {
647                field, threshold, ..
648            } => {
649                if !threshold.is_finite() {
650                    return Err(format!(
651                        "Vector distance threshold must be a finite number, got {}",
652                        threshold
653                    ));
654                }
655                field.validate()
656            }
657
658            WhereOperator::Matches { field, .. }
659            | WhereOperator::PlainQuery { field, .. }
660            | WhereOperator::PhraseQuery { field, .. }
661            | WhereOperator::WebsearchQuery { field, .. }
662            | WhereOperator::IsIPv4(field)
663            | WhereOperator::IsIPv6(field)
664            | WhereOperator::IsPrivate { field, .. }
665            | WhereOperator::IsLoopback { field, .. }
666            | WhereOperator::IsMulticast { field, .. }
667            | WhereOperator::IsLinkLocal { field, .. }
668            | WhereOperator::IsDocumentation { field, .. }
669            | WhereOperator::IsCarrierGrade { field, .. }
670            | WhereOperator::InSubnet { field, .. }
671            | WhereOperator::ContainsSubnet { field, .. }
672            | WhereOperator::ContainsIP { field, .. }
673            | WhereOperator::IPRangeOverlap { field, .. }
674            | WhereOperator::AncestorOf { field, .. }
675            | WhereOperator::DescendantOf { field, .. }
676            | WhereOperator::MatchesLquery { field, .. }
677            | WhereOperator::MatchesLtxtquery { field, .. }
678            | WhereOperator::MatchesAnyLquery { field, .. }
679            | WhereOperator::DepthEq { field, .. }
680            | WhereOperator::DepthNeq { field, .. }
681            | WhereOperator::DepthGt { field, .. }
682            | WhereOperator::DepthGte { field, .. }
683            | WhereOperator::DepthLt { field, .. }
684            | WhereOperator::DepthLte { field, .. }
685            | WhereOperator::Lca { field, .. }
686            | WhereOperator::DescendantOfId { field, .. }
687            | WhereOperator::AncestorOfId { field, .. } => field.validate(),
688        }
689    }
690}
691
692#[cfg(test)]
693mod tests;