sqlite_graphrag/parsers/
mod.rs1use chrono::DateTime;
4
5pub fn parse_expected_updated_at(s: &str) -> Result<i64, String> {
7 if let Ok(secs) = s.parse::<i64>() {
8 if secs >= 0 {
9 return Ok(secs);
10 }
11 }
12 DateTime::parse_from_rfc3339(s)
13 .map(|dt| dt.timestamp())
14 .map_err(|e| {
15 format!(
16 "value must be a Unix epoch (integer >= 0) or RFC 3339 (e.g. 2026-04-19T12:00:00Z): {e}"
17 )
18 })
19}
20
21pub fn parse_k_range(s: &str) -> Result<usize, String> {
27 let value: usize = s
28 .parse()
29 .map_err(|_| format!("'{s}' is not a valid non-negative integer"))?;
30 if !(1..=4096).contains(&value) {
31 return Err(format!(
32 "k must be between 1 and 4096 (inclusive); got {value}"
33 ));
34 }
35 Ok(value)
36}
37
38pub fn parse_bool_flexible(s: &str) -> Result<bool, String> {
44 match s.to_lowercase().as_str() {
45 "1" | "true" | "yes" | "on" => Ok(true),
46 "0" | "false" | "no" | "off" | "" => Ok(false),
47 _ => Err(format!(
48 "invalid boolean value '{s}': expected true/false/1/0/yes/no/on/off"
49 )),
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn accepts_unix_epoch() {
59 assert_eq!(parse_expected_updated_at("1700000000").unwrap(), 1700000000);
60 }
61
62 #[test]
63 fn accepts_zero() {
64 assert_eq!(parse_expected_updated_at("0").unwrap(), 0);
65 }
66
67 #[test]
68 fn accepts_rfc_3339_utc() {
69 let result = parse_expected_updated_at("2020-01-01T00:00:00Z");
70 assert!(result.is_ok());
71 assert_eq!(result.unwrap(), 1577836800);
72 }
73
74 #[test]
75 fn accepts_rfc_3339_with_offset() {
76 let result = parse_expected_updated_at("2026-04-19T12:00:00+00:00");
77 assert!(result.is_ok());
78 }
79
80 #[test]
81 fn rejects_invalid_string() {
82 assert!(parse_expected_updated_at("bananas").is_err());
83 }
84
85 #[test]
86 fn rejects_negative() {
87 let err = parse_expected_updated_at("-1");
88 assert!(err.is_err());
89 }
90
91 #[test]
92 fn error_message_mentions_format() {
93 let msg = parse_expected_updated_at("invalid").unwrap_err();
94 assert!(msg.contains("RFC 3339") || msg.contains("Unix epoch"));
95 }
96
97 #[test]
98 fn k_accepts_valid_range_endpoints() {
99 assert_eq!(parse_k_range("1").unwrap(), 1);
100 assert_eq!(parse_k_range("4096").unwrap(), 4096);
101 assert_eq!(parse_k_range("10").unwrap(), 10);
102 }
103
104 #[test]
105 fn k_rejects_zero() {
106 let msg = parse_k_range("0").unwrap_err();
107 assert!(msg.contains("between 1 and 4096"));
108 }
109
110 #[test]
111 fn k_rejects_above_limit() {
112 let msg = parse_k_range("10000").unwrap_err();
113 assert!(msg.contains("between 1 and 4096"));
114 }
115
116 #[test]
117 fn k_rejects_non_integer() {
118 let msg = parse_k_range("abc").unwrap_err();
119 assert!(msg.contains("not a valid"));
120 }
121
122 #[test]
123 fn k_rejects_negative() {
124 assert!(parse_k_range("-5").is_err());
126 }
127
128 #[test]
129 fn bool_flexible_truthy() {
130 for v in &["1", "true", "True", "TRUE", "yes", "Yes", "on", "ON"] {
131 assert!(parse_bool_flexible(v).unwrap(), "should be true: {v}");
132 }
133 }
134
135 #[test]
136 fn bool_flexible_falsy() {
137 for v in &["0", "false", "False", "FALSE", "no", "No", "off", "OFF", ""] {
138 assert!(!parse_bool_flexible(v).unwrap(), "should be false: {v}");
139 }
140 }
141
142 #[test]
143 fn bool_flexible_rejects_invalid() {
144 assert!(parse_bool_flexible("banana").is_err());
145 assert!(parse_bool_flexible("2").is_err());
146 assert!(parse_bool_flexible("nope").is_err());
147 }
148}