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;