1use serde_json::Value;
5
6pub const EXIT_CALLER_ERROR: i32 = 2;
8pub const EXIT_RUNTIME_ERROR: i32 = 1;
10
11pub fn format_error_json(message: &str) -> String {
12 classify_error(message).0
13}
14
15pub fn classify_error(message: &str) -> (String, i32) {
24 let message = message.trim_end();
25
26 if let Some(rest) = strip_json_rpc_code(message, -32602) {
27 return (error_envelope("CALLER_ERROR", rest), EXIT_CALLER_ERROR);
28 }
29 if let Some(rest) = strip_json_rpc_code(message, -32603) {
30 return (error_envelope("RUNTIME_ERROR", rest), EXIT_RUNTIME_ERROR);
31 }
32
33 let (code, message) = split_error_code(message).unwrap_or(("RUNTIME_ERROR", message));
34 (error_envelope(code, message), EXIT_RUNTIME_ERROR)
35}
36
37fn error_envelope(code: &str, message: &str) -> String {
38 serde_json::json!({"error": {"code": code, "message": message}}).to_string()
39}
40
41fn strip_json_rpc_code(message: &str, code: i64) -> Option<&str> {
44 let prefix = format!("[{code}]");
45 message.strip_prefix(&prefix).map(|rest| rest.trim_start())
46}
47
48pub(crate) fn split_error_code(message: &str) -> Option<(&str, &str)> {
49 let (code, rest) = message.split_once(':')?;
50 if !code.is_empty()
51 && code
52 .chars()
53 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
54 {
55 Some((code, rest.trim_start()))
56 } else {
57 None
58 }
59}
60
61pub(crate) fn normalize_list_envelope(
62 value: Value,
63 list_key: &str,
64 limit: Option<u64>,
65 offset: u64,
66) -> Value {
67 if let Value::Array(arr) = value {
68 let total = arr.len() as u64;
69 let mut obj = serde_json::Map::new();
70 obj.insert(list_key.to_string(), Value::Array(arr));
71 obj.insert("total".to_string(), Value::from(total));
72 if let Some(limit) = limit {
73 obj.insert("limit".to_string(), Value::from(limit));
74 }
75 obj.insert("offset".to_string(), Value::from(offset));
76 obj.insert("hasMore".to_string(), Value::Bool(false));
77 return Value::Object(obj);
78 }
79
80 let mut obj = match value {
81 Value::Object(obj) if obj.contains_key(list_key) => obj,
82 other => return other,
83 };
84
85 let items_len = obj
86 .get(list_key)
87 .and_then(Value::as_array)
88 .map_or(0, |a| a.len()) as u64;
89 let total = obj
90 .get("total")
91 .and_then(Value::as_u64)
92 .unwrap_or(items_len);
93
94 obj.insert("total".to_string(), Value::from(total));
95 if let Some(limit) = limit {
96 obj.insert("limit".to_string(), Value::from(limit));
97 }
98 obj.insert("offset".to_string(), Value::from(offset));
99 obj.insert(
100 "hasMore".to_string(),
101 Value::Bool(offset + items_len < total),
102 );
103
104 Value::Object(obj)
105}
106
107pub(crate) fn raw_value_output(value: &Value) -> Result<String, String> {
108 let mut out = match value {
111 Value::Null => String::new(),
112 Value::String(content) => content.clone(),
113 other => serde_json::to_string(other).map_err(|e| e.to_string())?,
114 };
115 out.push('\n');
116 Ok(out)
117}
118
119pub(crate) fn format_json(value: &Value) -> Result<String, String> {
120 let mut out = serde_json::to_string(value).map_err(|e| e.to_string())?;
121 out.push('\n');
122 Ok(out)
123}