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