lean_ctx/server/
helpers.rs1use serde_json::Value;
2
3pub fn get_str_array(
4 args: Option<&serde_json::Map<String, Value>>,
5 key: &str,
6) -> Option<Vec<String>> {
7 let val = args?.get(key)?;
8
9 if let Some(arr) = val.as_array() {
11 let mut out = Vec::with_capacity(arr.len());
12 for v in arr {
13 out.push(v.as_str()?.to_string());
14 }
15 return Some(out);
16 }
17
18 if let Some(s) = val.as_str()
21 && let Ok(Value::Array(arr)) = serde_json::from_str::<Value>(s)
22 {
23 let mut out = Vec::with_capacity(arr.len());
24 for v in &arr {
25 out.push(v.as_str()?.to_string());
26 }
27 return Some(out);
28 }
29
30 None
31}
32
33pub fn get_str(args: Option<&serde_json::Map<String, Value>>, key: &str) -> Option<String> {
34 args?
35 .get(key)?
36 .as_str()
37 .map(std::string::ToString::to_string)
38}
39
40pub fn get_int(args: Option<&serde_json::Map<String, Value>>, key: &str) -> Option<i64> {
41 args?.get(key)?.as_i64()
42}
43
44pub fn get_bool(args: Option<&serde_json::Map<String, Value>>, key: &str) -> Option<bool> {
45 args?.get(key)?.as_bool()
46}
47
48pub fn hash_fast(s: &str) -> String {
49 const THRESHOLD: usize = 16 * 1024;
50 if s.len() <= THRESHOLD {
51 crate::core::hasher::hash_str(s)
52 } else {
53 let prefix = &s[..s.floor_char_boundary(4096)];
54 let suffix = &s[s.ceil_char_boundary(s.len().saturating_sub(4096))..];
55 let mut hasher = blake3::Hasher::new();
56 hasher.update(prefix.as_bytes());
57 hasher.update(s.len().to_string().as_bytes());
58 hasher.update(suffix.as_bytes());
59 hasher.finalize().to_hex().to_string()
60 }
61}
62
63pub fn canonicalize_json(v: &Value) -> Value {
64 match v {
65 Value::Object(map) => {
66 let mut keys: Vec<&String> = map.keys().collect();
67 keys.sort();
68 let mut out = serde_json::Map::new();
69 for k in keys {
70 if let Some(val) = map.get(k) {
71 out.insert(k.clone(), canonicalize_json(val));
72 }
73 }
74 Value::Object(out)
75 }
76 Value::Array(arr) => Value::Array(arr.iter().map(canonicalize_json).collect()),
77 other => other.clone(),
78 }
79}
80
81pub fn canonical_args_string(args: Option<&serde_json::Map<String, Value>>) -> String {
82 let v = args.map_or(Value::Null, |m| Value::Object(m.clone()));
83 let canon = canonicalize_json(&v);
84 serde_json::to_string(&canon).unwrap_or_default()
85}
86
87pub fn extract_search_pattern_from_command(command: &str) -> Option<String> {
88 let parts: Vec<&str> = command.split_whitespace().collect();
89 if parts.len() < 2 {
90 return None;
91 }
92 let cmd = parts[0];
93 if cmd == "grep" || cmd == "rg" || cmd == "ag" || cmd == "ack" {
94 for (i, part) in parts.iter().enumerate().skip(1) {
95 if !part.starts_with('-') {
96 return Some(part.to_string());
97 }
98 if (*part == "-e" || *part == "--regexp" || *part == "-m") && i + 1 < parts.len() {
99 return Some(parts[i + 1].to_string());
100 }
101 }
102 }
103 if cmd == "find" || cmd == "fd" {
104 for (i, part) in parts.iter().enumerate() {
105 if (*part == "-name" || *part == "-iname") && i + 1 < parts.len() {
106 return Some(
107 parts[i + 1]
108 .trim_matches('\'')
109 .trim_matches('"')
110 .to_string(),
111 );
112 }
113 }
114 if cmd == "fd" && parts.len() >= 2 && !parts[1].starts_with('-') {
115 return Some(parts[1].to_string());
116 }
117 }
118 None
119}