patchloom/selector/
mod.rs1pub mod eval;
2pub mod parser;
3
4pub use eval::eval;
5pub use parser::{Segment, Selector, parse};
6
7pub fn parse_anyhow(input: &str) -> anyhow::Result<Selector> {
10 parse(input).map_err(|e| {
11 anyhow::Error::new(crate::exit::InvalidInputError {
12 msg: format!("selector error: {e}"),
13 })
14 })
15}
16
17pub fn get_nested<'a>(value: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
25 if !key.contains('.') {
27 return value.get(key);
28 }
29 if let Some(v) = value.get(key) {
31 return Some(v);
32 }
33 let mut current = value;
35 for segment in key.split('.') {
36 current = current.get(segment)?;
37 }
38 Some(current)
39}
40
41pub fn value_matches_str(field: &serde_json::Value, pred_val: &str) -> bool {
44 match field {
45 serde_json::Value::String(s) => s == pred_val,
46 serde_json::Value::Number(n) => n.to_string() == pred_val,
47 serde_json::Value::Bool(b) => b.to_string() == pred_val,
48 serde_json::Value::Null => pred_val == "null",
49 _ => false,
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use serde_json::json;
57
58 #[test]
59 fn value_matches_str_string() {
60 assert!(value_matches_str(&json!("hello"), "hello"));
61 assert!(!value_matches_str(&json!("hello"), "world"));
62 }
63
64 #[test]
65 fn value_matches_str_number() {
66 assert!(value_matches_str(&json!(42), "42"));
67 assert!(!value_matches_str(&json!(42), "43"));
68 }
69
70 #[test]
71 fn value_matches_str_bool() {
72 assert!(value_matches_str(&json!(true), "true"));
73 assert!(value_matches_str(&json!(false), "false"));
74 assert!(!value_matches_str(&json!(true), "false"));
75 }
76
77 #[test]
79 fn value_matches_str_null_matches_null_string() {
80 assert!(value_matches_str(&json!(null), "null"));
81 assert!(!value_matches_str(&json!(null), "other"));
82 }
83
84 #[test]
85 fn get_nested_flat_key() {
86 let data = json!({"name": "Alice"});
87 assert_eq!(get_nested(&data, "name"), Some(&json!("Alice")));
88 }
89
90 #[test]
91 fn get_nested_dotted_path() {
92 let data = json!({"settings": {"theme": "dark"}});
93 assert_eq!(get_nested(&data, "settings.theme"), Some(&json!("dark")));
94 }
95
96 #[test]
97 fn get_nested_deep_path() {
98 let data = json!({"a": {"b": {"c": 42}}});
99 assert_eq!(get_nested(&data, "a.b.c"), Some(&json!(42)));
100 }
101
102 #[test]
103 fn get_nested_literal_dot_key_takes_priority() {
104 let data = json!({"a.b": "literal", "a": {"b": "nested"}});
106 assert_eq!(get_nested(&data, "a.b"), Some(&json!("literal")));
107 }
108
109 #[test]
110 fn get_nested_missing_returns_none() {
111 let data = json!({"a": {"b": 1}});
112 assert_eq!(get_nested(&data, "a.c"), None);
113 }
114
115 #[test]
116 fn value_matches_str_object_returns_false() {
117 assert!(!value_matches_str(&json!({"a": 1}), ""));
118 }
119}