1pub mod numeric {
17 use std::fmt::Display;
18
19 pub const IN_MESSAGE: &str = "value must be in list";
21 pub const NOT_IN_MESSAGE: &str = "value must not be in list";
23 pub const FINITE_MESSAGE: &str = "value must be finite";
25
26 #[must_use]
28 pub fn const_id(prefix: &str) -> String {
29 format!("{prefix}.const")
30 }
31
32 #[must_use]
34 pub fn in_id(prefix: &str) -> String {
35 format!("{prefix}.in")
36 }
37
38 #[must_use]
40 pub fn not_in_id(prefix: &str) -> String {
41 format!("{prefix}.not_in")
42 }
43
44 #[must_use]
46 pub fn finite_id(prefix: &str) -> String {
47 format!("{prefix}.finite")
48 }
49
50 #[must_use]
52 pub fn const_message<T: Display>(want: T) -> String {
53 format!("value must equal {want}")
54 }
55
56 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
65 pub enum RangeKind {
66 GtLt,
68 GtLtExclusive,
70 GtLte,
72 GtLteExclusive,
74 GteLt,
76 GteLtExclusive,
78 GteLte,
80 GteLteExclusive,
82 Gt,
84 Gte,
86 Lt,
88 Lte,
90 }
91
92 impl RangeKind {
93 #[must_use]
101 pub fn violated<T: PartialOrd + Copy>(self, gt: Option<T>, lt: Option<T>, v: T) -> bool {
102 match self {
103 Self::Gt => gt.is_some_and(|g| v <= g),
104 Self::Gte => gt.is_some_and(|g| v < g),
105 Self::Lt => lt.is_some_and(|l| v >= l),
106 Self::Lte => lt.is_some_and(|l| v > l),
107 Self::GtLt => gt.zip(lt).is_some_and(|(g, l)| v <= g || v >= l),
108 Self::GtLtExclusive => gt.zip(lt).is_some_and(|(g, l)| v >= l && v <= g),
109 Self::GtLte => gt.zip(lt).is_some_and(|(g, l)| v <= g || v > l),
110 Self::GtLteExclusive => gt.zip(lt).is_some_and(|(g, l)| v > l && v <= g),
111 Self::GteLt => gt.zip(lt).is_some_and(|(g, l)| v < g || v >= l),
112 Self::GteLtExclusive => gt.zip(lt).is_some_and(|(g, l)| v >= l && v < g),
113 Self::GteLte => gt.zip(lt).is_some_and(|(g, l)| v < g || v > l),
114 Self::GteLteExclusive => gt.zip(lt).is_some_and(|(g, l)| v > l && v < g),
115 }
116 }
117 }
118
119 #[derive(Debug, Clone)]
122 pub struct RangeRule {
123 pub kind: RangeKind,
125 pub rule_id: String,
127 pub rule_path: String,
130 pub message: String,
132 }
133
134 #[must_use]
141 pub fn range_rule<T: PartialOrd + Copy>(
142 prefix: &str,
143 gt: Option<T>,
144 gte: Option<T>,
145 lt: Option<T>,
146 lte: Option<T>,
147 fmt: impl Fn(&T) -> String,
148 ) -> Option<RangeRule> {
149 let (g_label, g_desc, g_bound, g_is_eq) = match (>, >e) {
150 (Some(g), None) => ("gt", "greater than", Some(g), false),
151 (None, Some(g)) => ("gte", "greater than or equal to", Some(g), true),
152 _ => ("", "", None, false),
153 };
154 let (l_label, l_desc, l_bound, l_is_eq) = match (<, <e) {
155 (Some(l), None) => ("lt", "less than", Some(l), false),
156 (None, Some(l)) => ("lte", "less than or equal to", Some(l), true),
157 _ => ("", "", None, false),
158 };
159
160 match (g_bound, l_bound) {
161 (Some(g), Some(l)) => {
162 let inclusive = if g_is_eq && l_is_eq { g <= l } else { g < l };
165 let kind = match (g_is_eq, l_is_eq, inclusive) {
166 (false, false, true) => RangeKind::GtLt,
167 (false, false, false) => RangeKind::GtLtExclusive,
168 (false, true, true) => RangeKind::GtLte,
169 (false, true, false) => RangeKind::GtLteExclusive,
170 (true, false, true) => RangeKind::GteLt,
171 (true, false, false) => RangeKind::GteLtExclusive,
172 (true, true, true) => RangeKind::GteLte,
173 (true, true, false) => RangeKind::GteLteExclusive,
174 };
175 let (suffix, joiner) = if inclusive {
176 ("", "and")
177 } else {
178 ("_exclusive", "or")
179 };
180 Some(RangeRule {
181 kind,
182 rule_id: format!("{prefix}.{g_label}_{l_label}{suffix}"),
183 rule_path: format!("{prefix}.{g_label}"),
184 message: format!(
185 "value must be {g_desc} {} {joiner} {l_desc} {}",
186 fmt(g),
187 fmt(l)
188 ),
189 })
190 }
191 (Some(g), None) => Some(RangeRule {
192 kind: if g_is_eq {
193 RangeKind::Gte
194 } else {
195 RangeKind::Gt
196 },
197 rule_id: format!("{prefix}.{g_label}"),
198 rule_path: format!("{prefix}.{g_label}"),
199 message: format!("value must be {g_desc} {}", fmt(g)),
200 }),
201 (None, Some(l)) => Some(RangeRule {
202 kind: if l_is_eq {
203 RangeKind::Lte
204 } else {
205 RangeKind::Lt
206 },
207 rule_id: format!("{prefix}.{l_label}"),
208 rule_path: format!("{prefix}.{l_label}"),
209 message: format!("value must be {l_desc} {}", fmt(l)),
210 }),
211 (None, None) => None,
212 }
213 }
214
215 #[must_use]
219 pub fn nan_range_rule<T: PartialOrd + Copy>(
220 prefix: &str,
221 gt: Option<T>,
222 gte: Option<T>,
223 lt: Option<T>,
224 lte: Option<T>,
225 ) -> Option<(String, String)> {
226 let rule = range_rule(prefix, gt, gte, lt, lte, |_| String::new())?;
227 Some((rule.rule_id, rule.rule_path))
228 }
229
230 #[cfg(test)]
231 mod tests {
232 use super::{RangeKind, nan_range_rule, range_rule};
233
234 #[test]
235 fn combined_bounds_select_inclusive_and_exclusive_variants() {
236 let incl = range_rule("int32", Some(1), None, Some(10), None, i32::to_string)
237 .expect("bounds set");
238 assert_eq!(incl.kind, RangeKind::GtLt);
239 assert_eq!(incl.rule_id, "int32.gt_lt");
240 assert_eq!(incl.rule_path, "int32.gt");
241 assert_eq!(
242 incl.message,
243 "value must be greater than 1 and less than 10"
244 );
245
246 let excl = range_rule("int32", Some(10), None, Some(1), None, i32::to_string)
247 .expect("bounds set");
248 assert_eq!(excl.kind, RangeKind::GtLtExclusive);
249 assert_eq!(excl.rule_id, "int32.gt_lt_exclusive");
250 assert_eq!(excl.message, "value must be greater than 10 or less than 1");
251 }
252
253 #[test]
254 fn gte_lte_equal_bounds_are_inclusive() {
255 let rule = range_rule("uint32", None, Some(5u32), None, Some(5), u32::to_string)
256 .expect("bounds set");
257 assert_eq!(rule.kind, RangeKind::GteLte);
258 assert_eq!(rule.rule_id, "uint32.gte_lte");
259 assert_eq!(
260 rule.message,
261 "value must be greater than or equal to 5 and less than or equal to 5"
262 );
263 }
264
265 #[test]
266 fn single_bounds_use_their_own_id_as_rule_path() {
267 let rule = range_rule("double", None, None, None, Some(1.5f64), f64::to_string)
268 .expect("bound set");
269 assert_eq!(rule.kind, RangeKind::Lte);
270 assert_eq!(rule.rule_id, "double.lte");
271 assert_eq!(rule.rule_path, "double.lte");
272 assert_eq!(rule.message, "value must be less than or equal to 1.5");
273 }
274
275 #[test]
276 fn float_bounds_format_from_the_wire_scalar() {
277 let rule = range_rule("float", Some(0.1f32), None, None, None, f32::to_string)
280 .expect("bound set");
281 assert_eq!(rule.message, "value must be greater than 0.1");
282 }
283
284 #[test]
285 fn violated_matches_bound_semantics() {
286 assert!(RangeKind::Gt.violated(Some(5), None, 5));
287 assert!(!RangeKind::Gt.violated(Some(5), None, 6));
288 assert!(RangeKind::Gte.violated(Some(5), None, 4));
289 assert!(!RangeKind::Gte.violated(Some(5), None, 5));
290 assert!(RangeKind::Lt.violated(None, Some(5), 5));
291 assert!(RangeKind::Lte.violated(None, Some(5), 6));
292
293 assert!(RangeKind::GtLt.violated(Some(1), Some(10), 1));
295 assert!(RangeKind::GtLt.violated(Some(1), Some(10), 10));
296 assert!(!RangeKind::GtLt.violated(Some(1), Some(10), 5));
297
298 assert!(RangeKind::GtLtExclusive.violated(Some(10), Some(1), 5));
300 assert!(!RangeKind::GtLtExclusive.violated(Some(10), Some(1), 0));
301 assert!(!RangeKind::GtLtExclusive.violated(Some(10), Some(1), 11));
302
303 assert!(RangeKind::GteLte.violated(Some(0.0), Some(1.0), -0.5));
305 assert!(RangeKind::GteLte.violated(Some(0.0), Some(1.0), 1.5));
306 assert!(!RangeKind::GteLte.violated(Some(0.0), Some(1.0), 0.0));
307 }
308
309 #[test]
310 fn nan_rule_reuses_the_range_table() {
311 let (id, path) =
312 nan_range_rule("float", None, Some(0.0f32), None, Some(100.0)).expect("bounds");
313 assert_eq!(id, "float.gte_lte");
314 assert_eq!(path, "float.gte");
315
316 let (id, path) =
317 nan_range_rule("double", Some(0.0f64), None, None, None).expect("bound");
318 assert_eq!(id, "double.gt");
319 assert_eq!(path, "double.gt");
320
321 assert!(nan_range_rule::<f32>("float", None, None, None, None).is_none());
322 }
323 }
324}
325
326pub mod duration {
328 use super::numeric;
329
330 pub const CONST_ID: &str = "duration.const";
332 pub const IN_ID: &str = "duration.in";
334 pub const NOT_IN_ID: &str = "duration.not_in";
336
337 #[must_use]
340 pub fn fmt(seconds: i64, nanos: i32) -> String {
341 if nanos == 0 {
342 format!("{seconds}s")
343 } else {
344 let sign = if seconds < 0 || nanos < 0 { "-" } else { "" };
345 let secs = seconds.unsigned_abs();
346 let nanos = nanos.unsigned_abs();
347 let frac = format!("{nanos:09}");
348 let frac = frac.trim_end_matches('0');
349 format!("{sign}{secs}.{frac}s")
350 }
351 }
352
353 #[must_use]
355 pub fn const_message(seconds: i64, nanos: i32) -> String {
356 format!("value must equal {}", fmt(seconds, nanos))
357 }
358
359 #[must_use]
361 pub fn in_message(items: &[(i64, i32)]) -> String {
362 format!("value must be in list [{}]", join(items))
363 }
364
365 #[must_use]
367 pub fn not_in_message(items: &[(i64, i32)]) -> String {
368 format!("value must not be in list [{}]", join(items))
369 }
370
371 fn join(items: &[(i64, i32)]) -> String {
372 items
373 .iter()
374 .map(|(s, n)| fmt(*s, *n))
375 .collect::<Vec<_>>()
376 .join(", ")
377 }
378
379 #[must_use]
386 pub fn range_rule(
387 gt: Option<(i64, i32)>,
388 gte: Option<(i64, i32)>,
389 lt: Option<(i64, i32)>,
390 lte: Option<(i64, i32)>,
391 ) -> Option<numeric::RangeRule> {
392 numeric::range_rule("duration", gt, gte, lt, lte, |(s, n)| fmt(*s, *n))
393 }
394
395 #[cfg(test)]
396 mod tests {
397 use super::{fmt, in_message, range_rule};
398
399 #[test]
400 fn fmt_matches_go_style() {
401 assert_eq!(fmt(3, 0), "3s");
402 assert_eq!(fmt(1, 500_000_000), "1.5s");
403 assert_eq!(fmt(-2, 0), "-2s");
404 assert_eq!(fmt(0, -500_000_000), "-0.5s");
405 assert_eq!(fmt(0, 1), "0.000000001s");
406 }
407
408 #[test]
409 fn range_messages_render_bounds_go_style() {
410 let rule = range_rule(Some((0, 0)), None, Some((60, 0)), None).expect("bounds");
411 assert_eq!(rule.rule_id, "duration.gt_lt");
412 assert_eq!(
413 rule.message,
414 "value must be greater than 0s and less than 60s"
415 );
416 }
417
418 #[test]
419 fn list_messages_join_with_brackets() {
420 assert_eq!(
421 in_message(&[(1, 0), (2, 500_000_000)]),
422 "value must be in list [1s, 2.5s]"
423 );
424 }
425 }
426}
427
428pub mod timestamp {
430 use super::numeric;
431
432 pub const CONST_ID: &str = "timestamp.const";
434 pub const CONST_MESSAGE: &str = "must equal const timestamp";
437 pub const LT_NOW_ID: &str = "timestamp.lt_now";
439 pub const LT_NOW_MESSAGE: &str = "must be less than now";
441 pub const GT_NOW_ID: &str = "timestamp.gt_now";
443 pub const GT_NOW_MESSAGE: &str = "must be greater than now";
445 pub const WITHIN_ID: &str = "timestamp.within";
447 pub const WITHIN_MESSAGE: &str = "must be within specified duration of now";
449
450 #[must_use]
452 pub fn range_message(kind: numeric::RangeKind) -> &'static str {
453 match kind {
454 numeric::RangeKind::GtLt => "must be greater than and less than specified timestamps",
455 numeric::RangeKind::GtLtExclusive => {
456 "must be greater than or less than specified timestamps"
457 }
458 numeric::RangeKind::GtLte => {
459 "must be greater than and less than or equal to specified timestamps"
460 }
461 numeric::RangeKind::GtLteExclusive => {
462 "must be greater than or less than or equal to specified timestamps"
463 }
464 numeric::RangeKind::GteLt => {
465 "must be greater than or equal to and less than specified timestamps"
466 }
467 numeric::RangeKind::GteLtExclusive => {
468 "must be greater than or equal to or less than specified timestamps"
469 }
470 numeric::RangeKind::GteLte => "must be between specified timestamps inclusive",
471 numeric::RangeKind::GteLteExclusive => {
472 "must be greater than or equal to or less than or equal to specified timestamps"
473 }
474 numeric::RangeKind::Gt => "must be greater than specified timestamp",
475 numeric::RangeKind::Gte => "must be greater than or equal to specified timestamp",
476 numeric::RangeKind::Lt => "must be less than specified timestamp",
477 numeric::RangeKind::Lte => "must be less than or equal to specified timestamp",
478 }
479 }
480
481 #[must_use]
485 pub fn range_rule(
486 gt: Option<(i64, i32)>,
487 gte: Option<(i64, i32)>,
488 lt: Option<(i64, i32)>,
489 lte: Option<(i64, i32)>,
490 ) -> Option<numeric::RangeRule> {
491 let mut rule = numeric::range_rule("timestamp", gt, gte, lt, lte, |_| String::new())?;
492 rule.message = range_message(rule.kind).to_string();
493 Some(rule)
494 }
495
496 #[cfg(test)]
497 mod tests {
498 use super::range_rule;
499
500 #[test]
501 fn range_rule_uses_constant_messages() {
502 let rule = range_rule(None, Some((100, 0)), None, Some((200, 0))).expect("bounds");
503 assert_eq!(rule.rule_id, "timestamp.gte_lte");
504 assert_eq!(rule.rule_path, "timestamp.gte");
505 assert_eq!(
506 rule.message,
507 "must be between specified timestamps inclusive"
508 );
509
510 let single = range_rule(Some((5, 0)), None, None, None).expect("bound");
511 assert_eq!(single.rule_id, "timestamp.gt");
512 assert_eq!(single.message, "must be greater than specified timestamp");
513 }
514 }
515}
516
517pub mod string {
519 pub const CONST_ID: &str = "string.const";
521 pub const LEN_ID: &str = "string.len";
523 pub const MIN_LEN_ID: &str = "string.min_len";
525 pub const MAX_LEN_ID: &str = "string.max_len";
527 pub const LEN_BYTES_ID: &str = "string.len_bytes";
529 pub const MIN_BYTES_ID: &str = "string.min_bytes";
531 pub const MAX_BYTES_ID: &str = "string.max_bytes";
533 pub const PATTERN_ID: &str = "string.pattern";
535 pub const PREFIX_ID: &str = "string.prefix";
537 pub const SUFFIX_ID: &str = "string.suffix";
539 pub const CONTAINS_ID: &str = "string.contains";
541 pub const NOT_CONTAINS_ID: &str = "string.not_contains";
543 pub const IN_ID: &str = "string.in";
545 pub const NOT_IN_ID: &str = "string.not_in";
547
548 pub const WELL_KNOWN_REGEX_PATH: &str = "string.well_known_regex";
550 pub const HEADER_NAME_ID: &str = "string.well_known_regex.header_name";
552 pub const HEADER_NAME_EMPTY_ID: &str = "string.well_known_regex.header_name_empty";
554 pub const HEADER_VALUE_ID: &str = "string.well_known_regex.header_value";
556
557 #[must_use]
559 pub fn const_message(want: &str) -> String {
560 format!("must equal `{want}`")
561 }
562
563 #[must_use]
565 pub fn len_message(len: u64) -> String {
566 format!("must be {len} characters")
567 }
568
569 #[must_use]
571 pub fn min_len_message(min: u64) -> String {
572 format!("value length must be at least {min} characters")
573 }
574
575 #[must_use]
577 pub fn max_len_message(max: u64) -> String {
578 format!("value length must be at most {max} characters")
579 }
580
581 #[must_use]
583 pub fn len_bytes_message(len: u64) -> String {
584 format!("must be {len} bytes")
585 }
586
587 #[must_use]
589 pub fn min_bytes_message(min: u64) -> String {
590 format!("must be at least {min} bytes")
591 }
592
593 #[must_use]
595 pub fn max_bytes_message(max: u64) -> String {
596 format!("must be at most {max} bytes")
597 }
598
599 #[must_use]
601 pub fn pattern_message(pattern: &str) -> String {
602 format!("does not match regex pattern `{pattern}`")
603 }
604
605 #[must_use]
607 pub fn prefix_message(prefix: &str) -> String {
608 format!("does not have prefix `{prefix}`")
609 }
610
611 #[must_use]
613 pub fn suffix_message(suffix: &str) -> String {
614 format!("does not have suffix `{suffix}`")
615 }
616
617 #[must_use]
619 pub fn contains_message(substring: &str) -> String {
620 format!("does not contain substring `{substring}`")
621 }
622
623 #[must_use]
625 pub fn not_contains_message(substring: &str) -> String {
626 format!("contains substring `{substring}`")
627 }
628
629 #[must_use]
632 pub fn in_message(items: &[String]) -> String {
633 format!("must be in list {:?}", sorted(items))
634 }
635
636 #[must_use]
638 pub fn not_in_message(items: &[String]) -> String {
639 format!("must not be in list {:?}", sorted(items))
640 }
641
642 fn sorted(items: &[String]) -> Vec<&String> {
643 let mut v: Vec<&String> = items.iter().collect();
644 v.sort();
645 v
646 }
647
648 #[must_use]
650 pub fn well_known_id(name: &str) -> String {
651 format!("string.{name}")
652 }
653
654 #[must_use]
657 pub fn well_known_empty_id(name: &str) -> String {
658 format!("string.{name}_empty")
659 }
660
661 #[cfg(test)]
662 mod tests {
663 use super::{in_message, well_known_empty_id, well_known_id};
664
665 #[test]
666 fn list_messages_sort_and_debug_format() {
667 let items = vec!["b".to_string(), "a".to_string()];
668 assert_eq!(in_message(&items), r#"must be in list ["a", "b"]"#);
669 }
670
671 #[test]
672 fn well_known_ids_compose() {
673 assert_eq!(well_known_id("uri_ref"), "string.uri_ref");
674 assert_eq!(
675 well_known_empty_id("ip_with_prefixlen"),
676 "string.ip_with_prefixlen_empty"
677 );
678 }
679 }
680}
681
682pub mod bytes {
684 pub const CONST_ID: &str = "bytes.const";
686 pub const LEN_ID: &str = "bytes.len";
688 pub const MIN_LEN_ID: &str = "bytes.min_len";
690 pub const MAX_LEN_ID: &str = "bytes.max_len";
692 pub const PATTERN_ID: &str = "bytes.pattern";
694 pub const PREFIX_ID: &str = "bytes.prefix";
696 pub const SUFFIX_ID: &str = "bytes.suffix";
698 pub const CONTAINS_ID: &str = "bytes.contains";
700 pub const IN_ID: &str = "bytes.in";
702 pub const NOT_IN_ID: &str = "bytes.not_in";
704
705 pub const IN_MESSAGE: &str = "value must be in list";
707 pub const NOT_IN_MESSAGE: &str = "value must not be in list";
709
710 pub const IP_EMPTY_ID: &str = "bytes.ip_empty";
712 pub const IP_EMPTY_MESSAGE: &str = "value is empty, which is not a valid IP address";
714 pub const IP_ID: &str = "bytes.ip";
716 pub const IP_MESSAGE: &str = "value must be a valid IP address";
718 pub const IPV4_EMPTY_ID: &str = "bytes.ipv4_empty";
720 pub const IPV4_EMPTY_MESSAGE: &str = "value is empty, which is not a valid IPv4 address";
722 pub const IPV4_ID: &str = "bytes.ipv4";
724 pub const IPV4_MESSAGE: &str = "value must be a valid IPv4 address";
726 pub const IPV6_EMPTY_ID: &str = "bytes.ipv6_empty";
728 pub const IPV6_EMPTY_MESSAGE: &str = "value is empty, which is not a valid IPv6 address";
730 pub const IPV6_ID: &str = "bytes.ipv6";
732 pub const IPV6_MESSAGE: &str = "value must be a valid IPv6 address";
734 pub const UUID_EMPTY_ID: &str = "bytes.uuid_empty";
738 pub const UUID_ID: &str = "bytes.uuid";
740 pub const UUID_MESSAGE: &str = "value must be a valid UUID";
742
743 #[must_use]
746 pub fn const_message(value: &[u8]) -> String {
747 format!("value must be {value:?}")
748 }
749
750 #[must_use]
752 pub fn len_message(len: u64) -> String {
753 format!("value length must be {len} bytes")
754 }
755
756 #[must_use]
758 pub fn min_len_message(min: u64) -> String {
759 format!("value length must be at least {min} bytes")
760 }
761
762 #[must_use]
764 pub fn max_len_message(max: u64) -> String {
765 format!("value length must be at most {max} bytes")
766 }
767
768 #[must_use]
770 pub fn pattern_message(pattern: &str) -> String {
771 format!("value must match regex pattern `{pattern}`")
772 }
773
774 #[must_use]
776 pub fn prefix_message(prefix: &[u8]) -> String {
777 format!("value does not have prefix {prefix:?}")
778 }
779
780 #[must_use]
782 pub fn suffix_message(suffix: &[u8]) -> String {
783 format!("value does not have suffix {suffix:?}")
784 }
785
786 #[must_use]
788 pub fn contains_message(substring: &[u8]) -> String {
789 format!("value does not contain {substring:?}")
790 }
791
792 #[cfg(test)]
793 mod tests {
794 use super::const_message;
795
796 #[test]
797 fn byte_messages_use_debug_rendering() {
798 assert_eq!(const_message(&[1, 2, 255]), "value must be [1, 2, 255]");
799 }
800 }
801}
802
803pub mod repeated {
805 pub const MIN_ITEMS_ID: &str = "repeated.min_items";
807 pub const MAX_ITEMS_ID: &str = "repeated.max_items";
809 pub const UNIQUE_ID: &str = "repeated.unique";
811 pub const UNIQUE_MESSAGE: &str = "items must be unique";
813 pub const ITEMS_RULE_PREFIX: &str = "repeated.items";
815
816 #[must_use]
818 pub fn min_items_message(min: u64) -> String {
819 format!("must have at least {min} items")
820 }
821
822 #[must_use]
824 pub fn max_items_message(max: u64) -> String {
825 format!("must have at most {max} items")
826 }
827}
828
829pub mod map {
831 pub const MIN_PAIRS_ID: &str = "map.min_pairs";
833 pub const MAX_PAIRS_ID: &str = "map.max_pairs";
835 pub const KEYS_RULE_PREFIX: &str = "map.keys";
837 pub const VALUES_RULE_PREFIX: &str = "map.values";
839
840 #[must_use]
842 pub fn min_pairs_message(min: u64) -> String {
843 format!("must have at least {min} entries")
844 }
845
846 #[must_use]
848 pub fn max_pairs_message(max: u64) -> String {
849 format!("must have at most {max} entries")
850 }
851}
852
853pub mod enumeration {
855 pub const CONST_ID: &str = "enum.const";
857 pub const DEFINED_ONLY_ID: &str = "enum.defined_only";
859 pub const DEFINED_ONLY_MESSAGE: &str = "value must be one of the defined enum values";
861 pub const IN_ID: &str = "enum.in";
863 pub const NOT_IN_ID: &str = "enum.not_in";
865
866 #[must_use]
868 pub fn const_message(value: i32) -> String {
869 format!("must equal {value}")
870 }
871
872 #[must_use]
875 pub fn in_message(values: &[i32]) -> String {
876 format!("must be in list {:?}", sorted(values))
877 }
878
879 #[must_use]
881 pub fn not_in_message(values: &[i32]) -> String {
882 format!("must not be in list {:?}", sorted(values))
883 }
884
885 fn sorted(values: &[i32]) -> Vec<i32> {
886 let mut v = values.to_vec();
887 v.sort_unstable();
888 v
889 }
890}
891
892pub mod field_mask {
894 pub const CONST_ID: &str = "field_mask.const";
896 pub const CONST_MESSAGE: &str = "must equal paths";
898 pub const IN_ID: &str = "field_mask.in";
900 pub const IN_MESSAGE: &str = "must only contain allowed paths";
902 pub const NOT_IN_ID: &str = "field_mask.not_in";
904 pub const NOT_IN_MESSAGE: &str = "must not contain forbidden paths";
906}
907
908pub mod boolean {
910 pub const CONST_ID: &str = "bool.const";
912
913 #[must_use]
915 pub fn const_message(value: bool) -> String {
916 format!("must equal {value}")
917 }
918}
919
920pub mod any {
922 pub const IN_ID: &str = "any.in";
924 pub const IN_MESSAGE: &str = "type URL must be in the allow list";
926 pub const NOT_IN_ID: &str = "any.not_in";
928 pub const NOT_IN_MESSAGE: &str = "type URL must not be in the block list";
930}
931
932pub mod float {
935 #[must_use]
939 pub fn canonical_f32_bits(value: f32) -> Option<u32> {
940 if value.is_nan() {
941 return None;
942 }
943 Some(if value == 0.0 {
944 0.0_f32.to_bits()
945 } else {
946 value.to_bits()
947 })
948 }
949
950 #[must_use]
952 pub fn canonical_f64_bits(value: f64) -> Option<u64> {
953 if value.is_nan() {
954 return None;
955 }
956 Some(if value == 0.0 {
957 0.0_f64.to_bits()
958 } else {
959 value.to_bits()
960 })
961 }
962
963 #[cfg(test)]
964 mod tests {
965 use super::{canonical_f32_bits, canonical_f64_bits};
966
967 #[test]
968 fn nan_has_no_key_and_zeros_collapse() {
969 assert_eq!(canonical_f32_bits(f32::NAN), None);
970 assert_eq!(canonical_f64_bits(f64::NAN), None);
971 assert_eq!(canonical_f32_bits(0.0), canonical_f32_bits(-0.0));
972 assert_eq!(canonical_f64_bits(0.0), canonical_f64_bits(-0.0));
973 assert_ne!(canonical_f32_bits(1.0), canonical_f32_bits(2.0));
974 }
975 }
976}