use serde::Deserialize;
use serde_json::{Value, json};
use crate::error::ToolError;
use crate::protocol::ServerState;
use crate::tools::is_safe_identifier;
pub fn metadata() -> Value {
json!({
"name": "describe_table",
"description": "Return column metadata and row count for a table. \
Use this to learn a table's shape before composing a \
SELECT or INSERT. Each column reports its name, declared \
type, primary-key flag, NOT NULL flag, and UNIQUE flag.",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The table name. Must match `[A-Za-z_][A-Za-z0-9_]*`.",
},
},
"required": ["name"],
"additionalProperties": false,
}
})
}
#[derive(Deserialize)]
struct Args {
name: String,
}
pub fn handle(args: Value, state: &mut ServerState) -> Result<String, ToolError> {
let args: Args = serde_json::from_value(args)
.map_err(|e| ToolError::new(format!("invalid arguments: {e}")))?;
if !is_safe_identifier(&args.name) {
return Err(ToolError::new(format!(
"invalid table name `{}` — only `[A-Za-z_][A-Za-z0-9_]*` is accepted",
args.name
)));
}
let db = state.conn.database();
let table = db
.get_table(args.name.clone())
.map_err(|e| ToolError::new(format!("table `{}` not found: {e}", args.name)))?;
let columns: Vec<Value> = table
.columns
.iter()
.map(|c| {
json!({
"name": c.column_name,
"type": c.datatype.to_string(),
"primary_key": c.is_pk,
"not_null": c.not_null,
"unique": c.is_unique,
})
})
.collect();
let row_count = table.rowids().len() as i64;
let result = json!({
"name": args.name,
"columns": columns,
"row_count": row_count,
});
serde_json::to_string_pretty(&result)
.map_err(|e| ToolError::new(format!("internal: failed to serialize: {e}")))
}