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