use serde_json::Value;
use std::collections::BTreeSet;
#[derive(Debug, Clone)]
pub struct ResponseEnvelope {
pub status: String,
pub tool: String,
pub format: String,
pub tokens: usize,
pub data: String,
}
impl ResponseEnvelope {
pub fn new(status: &str, tool: &str, format: &str, tokens: usize, data: String) -> Self {
Self {
status: status.to_string(),
tool: tool.to_string(),
format: format.to_string(),
tokens,
data,
}
}
pub fn to_json_string(&self) -> String {
format!(
r#"{{
status: {}
tool: {}
format: {}
tokens: {}
data:
{}
}}"#,
self.status,
self.tool,
self.format,
self.tokens,
indent_lines(&self.data, 4)
)
}
pub fn to_toon_string(&self) -> String {
format!(
"status: {}\ntool: {}\nformat: toon\ntokens: {}\ndata:\n{}",
self.status,
self.tool,
self.tokens,
indent_lines(&self.data, 2)
)
}
}
fn indent_lines(s: &str, spaces: usize) -> String {
let indent = " ".repeat(spaces);
s.lines()
.map(|line| format!("{}{}", indent, line))
.collect::<Vec<_>>()
.join("\n")
}
pub fn to_toon_string(value: &Value) -> String {
let mut output = String::new();
convert_value(value, &mut output, 0, false);
output
}
fn convert_value(value: &Value, output: &mut String, indent: usize, _is_array_item: bool) {
let indent_str = " ".repeat(indent);
match value {
Value::Null => {
output.push_str("null");
}
Value::Bool(b) => {
output.push_str(if *b { "true" } else { "false" });
}
Value::Number(n) => {
output.push_str(&n.to_string());
}
Value::String(s) => {
if s.contains('\n') || s.contains('|') {
output.push_str("|\n");
for line in s.lines() {
output.push_str(&format!("{} {}\n", indent_str, line));
}
} else if s.contains(',')
|| s.contains(':')
|| s.contains('{')
|| s.contains('}')
|| s.contains('"')
{
output.push_str(&format!("\"{}\"", s.replace('\"', "\\\"")));
} else if s.is_empty() {
output.push_str("\"\"");
} else {
output.push_str(s);
}
}
Value::Array(arr) => {
if arr.is_empty() {
output.push_str("[]");
return;
}
if let Some((array_name, schema)) = extract_shared_schema(arr) {
output.push_str(&schema.header(&array_name, arr.len()));
if arr.is_empty() {
output.push('\n');
} else {
output.push('\n');
for item in arr {
output.push_str(&format!("{}{}\n", indent_str, schema.format_item(item)));
}
}
} else {
for item in arr {
output.push_str(&format!("{}- ", indent_str));
convert_value(item, output, indent + 1, true);
output.push('\n');
}
}
}
Value::Object(obj) => {
if obj.is_empty() {
output.push_str("{}\n");
return;
}
let keys: BTreeSet<_> = obj.keys().collect();
let mut first = true;
for k in keys {
if !first {
output.push('\n');
}
first = false;
let v = &obj[k];
output.push_str(&format!("{}{}: ", indent_str, k));
match v {
Value::Object(inner_obj) if inner_obj.is_empty() => {
output.push_str("{}\n");
}
Value::Array(inner_arr) if inner_arr.is_empty() => {
output.push_str("[]\n");
}
Value::Object(_) | Value::Array(_) => {
output.push('\n');
convert_value(v, output, indent + 1, false);
}
_ => {
convert_value(v, output, indent, false);
output.push('\n');
}
}
}
}
}
}
fn extract_shared_schema(arr: &[Value]) -> Option<(String, Schema)> {
if arr.is_empty() {
return None;
}
let objects: Vec<&serde_json::Map<String, Value>> =
arr.iter().filter_map(|v| v.as_object()).collect();
if objects.len() != arr.len() {
return None; }
let first_obj = objects[0];
let mut fields: BTreeSet<String> = BTreeSet::new();
for k in first_obj.keys() {
fields.insert(k.clone());
}
for obj in &objects[1..] {
let obj_keys: BTreeSet<String> = obj.keys().cloned().collect();
if obj_keys != fields {
return None; }
}
let fields_vec: Vec<String> = fields.into_iter().collect();
let array_name = infer_array_name(&fields_vec);
Some((array_name, Schema { fields: fields_vec }))
}
fn infer_array_name(fields: &[String]) -> String {
if fields.iter().any(|f| f == "label")
&& fields.iter().any(|f| f == "members")
&& fields.iter().any(|f| f == "representative_files")
{
return "cluster".to_string();
}
if fields.iter().any(|f| f == "qualified_name") {
return "element".to_string();
}
if fields.iter().any(|f| f == "from") && fields.iter().any(|f| f == "to") {
return "call".to_string();
}
if fields.iter().any(|f| f == "source") && fields.iter().any(|f| f == "target") {
return "rel".to_string();
}
if fields.iter().any(|f| f == "doc") && fields.iter().any(|f| f == "context") {
return "doc".to_string();
}
let priority_fields = [
"qualified_name",
"name",
"id",
"type",
"element",
"file",
"function",
"from",
"to",
];
for pf in &priority_fields {
if fields.iter().any(|f| f == pf) {
if pf.ends_with('s') && !pf.ends_with("ss") {
return pf.trim_end_matches('s').to_string();
}
return pf.to_string();
}
}
fields
.first()
.cloned()
.unwrap_or_else(|| "item".to_string())
}
#[derive(Debug, Clone)]
struct Schema {
fields: Vec<String>,
}
impl Schema {
fn header(&self, array_name: &str, count: usize) -> String {
let fields_str = self.fields.join(",");
format!("{}[{}]{{{}}}:", array_name, count, fields_str)
}
fn format_item(&self, item: &Value) -> String {
let obj = item.as_object().unwrap();
let values: Vec<String> = self
.fields
.iter()
.map(|field| {
let v = obj.get(field).unwrap();
value_to_string(v)
})
.collect();
values.join(",")
}
}
fn value_to_string(v: &Value) -> String {
match v {
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => {
if s.is_empty()
|| s.contains('\n')
|| s.contains('|')
|| s.contains(',')
|| s.contains(':')
|| s.contains('{')
|| s.contains('}')
|| s.contains('"')
{
format!("\"{}\"", s.replace('\"', "\\\""))
} else {
s.clone()
}
}
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(value_to_string).collect();
format!("[{}]", items.join(","))
}
Value::Object(obj) => {
let pairs: Vec<String> = obj
.iter()
.map(|(k, v)| format!("{}:{}", k, value_to_string(v)))
.collect();
format!("{{{}}}", pairs.join(","))
}
}
}
pub fn estimate_tokens(s: &str) -> usize {
s.len() / 4
}
pub fn wrap_response(tool_name: &str, response: &Value, use_toon: bool) -> String {
if use_toon {
let toon_data = to_toon_string(response);
let tokens = estimate_tokens(&toon_data);
let envelope = ResponseEnvelope::new("ok", tool_name, "toon", tokens, toon_data);
envelope.to_toon_string()
} else {
let json_str = serde_json::to_string_pretty(response).unwrap_or_default();
let tokens = estimate_tokens(&json_str);
let envelope = ResponseEnvelope::new("ok", tool_name, "json", tokens, json_str);
envelope.to_json_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_simple_toon() {
let val = json!({
"results": [
{"qualified_name": "src/main.rs::main", "type": "function"},
{"qualified_name": "src/lib.rs::init", "type": "function"}
],
"total": 2
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(
out.contains("element[2]{qualified_name,type}:"),
"Schema header not found. Output:\n{}",
out
);
assert!(
out.contains("\"src/main.rs::main\",function"),
"First item not found. Output:\n{}",
out
);
assert!(
out.contains("\"src/lib.rs::init\",function"),
"Second item not found. Output:\n{}",
out
);
}
#[test]
fn test_toon_with_three_fields() {
let val = json!({
"elements": [
{"qualified_name": "src/main.rs::main", "type": "function", "language": "rust"},
{"qualified_name": "src/lib.rs::init", "type": "function", "language": "rust"}
]
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(
out.contains("element[2]{language,qualified_name,type}:"),
"Schema header not found. Output:\n{}",
out
);
assert!(
out.contains("rust,\"src/main.rs::main\",function"),
"First item not found. Output:\n{}",
out
);
}
#[test]
fn test_mixed_array() {
let val = json!({
"items": [1, "string", {"key": "value"}]
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(out.contains("- 1"), "Number not found. Output:\n{}", out);
assert!(
out.contains("- string"),
"String not found. Output:\n{}",
out
);
}
#[test]
fn test_empty_array() {
let val = json!({
"results": []
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(
out.contains("[]"),
"Empty array not found. Output:\n{}",
out
);
}
#[test]
fn test_response_envelope() {
let response = json!({
"results": [
{"name": "test"}
]
});
let out = wrap_response("search_code", &response, true);
println!("Envelope output:\n{}", out);
assert!(out.contains("status: ok"), "Status not found");
assert!(out.contains("tool: search_code"), "Tool name not found");
assert!(out.contains("format: toon"), "Format not found");
assert!(out.contains("tokens:"), "Tokens not found");
assert!(out.contains("data:"), "Data not found");
}
#[test]
fn test_nested_object() {
let val = json!({
"context": {
"file": "src/main.rs",
"elements": [
{"name": "main", "type": "function"},
{"name": "init", "type": "function"}
]
}
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(
out.contains("name[2]{name,type}:"),
"Schema header not found. Output:\n{}",
out
);
}
#[test]
fn test_impact_radius_style() {
let val = json!({
"impact": [
{"qualified_name": "src/main.rs::main", "type": "function", "severity": "WILL_BREAK", "confidence": 1.0},
{"qualified_name": "src/lib.rs::init", "type": "function", "severity": "LIKELY_AFFECTED", "confidence": 0.85}
]
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(
out.contains("element[2]{confidence,qualified_name,severity,type}:"),
"Schema header not found. Output:\n{}",
out
);
assert!(
out.contains("1.0,\"src/main.rs::main\",WILL_BREAK,function"),
"First item not found. Output:\n{}",
out
);
}
#[test]
fn test_call_graph_style() {
let val = json!({
"calls": [
{"from": "src/main.rs::main", "to": "src/lib.rs::init", "depth": 1},
{"from": "src/lib.rs::init", "to": "src/config.rs::load", "depth": 2}
]
});
let out = to_toon_string(&val);
println!("Output:\n{}", out);
assert!(
out.contains("call[2]{depth,from,to}:"),
"Schema header not found. Output:\n{}",
out
);
assert!(
out.contains("1,\"src/main.rs::main\",\"src/lib.rs::init\""),
"First call not found. Output:\n{}",
out
);
}
}