Skip to main content

wist_validate/
lib.rs

1//! Static validators for contracts, config, and runtime state.
2
3pub mod action_plan;
4pub mod action_result;
5pub mod config;
6pub mod gateway;
7pub mod state;
8
9use time::OffsetDateTime;
10use time::format_description::well_known::Rfc3339;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ValidationError {
14    pub code: &'static str,
15}
16
17impl ValidationError {
18    pub const fn new(code: &'static str) -> Self {
19        Self { code }
20    }
21}
22
23pub(crate) fn require_non_empty(value: &str, code: &'static str) -> Result<(), ValidationError> {
24    if value.trim().is_empty() {
25        return Err(ValidationError::new(code));
26    }
27    Ok(())
28}
29
30pub(crate) fn parse_rfc3339(
31    value: &str,
32    code: &'static str,
33) -> Result<OffsetDateTime, ValidationError> {
34    OffsetDateTime::parse(value, &Rfc3339).map_err(|_| ValidationError::new(code))
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn require_non_empty_accepts_non_blank_values() {
43        assert!(require_non_empty("value", "code").is_ok());
44        // 只做空白判定,不裁剪返回值:前后带空格仍算非空。
45        assert!(require_non_empty(" value ", "code").is_ok());
46    }
47
48    #[test]
49    fn require_non_empty_rejects_empty_and_blank_values() {
50        for value in ["", " ", "\t", "\n", "   \t\n "] {
51            let err = require_non_empty(value, "missing_field").expect_err("should reject");
52            assert_eq!(err.code, "missing_field");
53        }
54    }
55
56    #[test]
57    fn parse_rfc3339_accepts_valid_timestamps() {
58        assert!(parse_rfc3339("2026-04-12T10:00:00Z", "bad").is_ok());
59        assert!(parse_rfc3339("2026-04-12T10:00:00+08:00", "bad").is_ok());
60        assert!(parse_rfc3339("2026-04-12T10:00:00.123Z", "bad").is_ok());
61    }
62
63    #[test]
64    fn parse_rfc3339_rejects_invalid_timestamps() {
65        for value in [
66            "",
67            "not-a-time",
68            // 只有日期、缺时区、缺分秒都不算 RFC3339。
69            "2026-04-12",
70            "2026-04-12T10:00:00",
71        ] {
72            let err = parse_rfc3339(value, "invalid_time").expect_err("should reject");
73            assert_eq!(err.code, "invalid_time");
74        }
75    }
76
77    #[test]
78    fn parse_rfc3339_compares_instants_across_offsets() {
79        // 同一时刻的不同时区写法应相等:比较按瞬时,而不是字面量。
80        let utc = parse_rfc3339("2026-04-12T10:00:00Z", "bad").expect("utc");
81        let plus8 = parse_rfc3339("2026-04-12T18:00:00+08:00", "bad").expect("plus8");
82        assert_eq!(utc, plus8);
83    }
84
85    #[test]
86    fn validation_error_carries_and_compares_by_code() {
87        let err = ValidationError::new("some_code");
88        assert_eq!(err.code, "some_code");
89        assert_eq!(err, ValidationError::new("some_code"));
90        assert_ne!(err, ValidationError::new("other_code"));
91    }
92}