query-forge 0.9.0

Run SQL queries and dataset diffs on XLSX/XML/CSV/JSON/JSONL/Markdown/HTML/Feather/Parquet inputs and export results as text, CSV, JSONL, Markdown, XML, HTML, XLSX, Feather, or Parquet
Documentation
use serde_json::{Value, json};

pub(crate) fn classify_error_code(message: &str) -> &'static str {
    let lower = message.to_ascii_lowercase();

    if lower.contains("invalid value")
        || lower.contains("unexpected argument")
        || lower.contains("required arguments")
        || lower.contains("usage:")
    {
        return "QF_INVALID_ARGUMENT";
    }

    if lower.contains("failed to read") || lower.contains("no such file") || lower.contains("not found") {
        return "QF_INPUT_NOT_FOUND";
    }

    if lower.contains("clipboard") {
        return "QF_CLIPBOARD_ERROR";
    }

    if lower.contains("pivot query syntax error") || lower.contains("sql") {
        return "QF_SQL_ERROR";
    }

    if lower.contains("schema") && lower.contains("diff") {
        return "QF_SCHEMA_MISMATCH";
    }

    if lower.contains("format") {
        return "QF_UNSUPPORTED_FORMAT";
    }

    "QF_RUNTIME_ERROR"
}

pub(crate) fn query_value_to_json(value: &query_forge::QueryValue) -> Value {
    match value {
        query_forge::QueryValue::Null => Value::Null,
        query_forge::QueryValue::Integer(v) => json!(v),
        query_forge::QueryValue::Real(v) => json!(v),
        query_forge::QueryValue::Text(v) => json!(v),
    }
}

pub(crate) fn query_result_to_json(result: &query_forge::QueryResult) -> Value {
    let rows = result
        .rows
        .iter()
        .map(|row| {
            let mut obj = serde_json::Map::new();
            for (col, val) in result.columns.iter().zip(row.iter()) {
                obj.insert(col.clone(), query_value_to_json(val));
            }
            Value::Object(obj)
        })
        .collect::<Vec<_>>();

    json!({
        "columns": result.columns,
        "rows": rows,
        "row_count": result.rows.len(),
    })
}

pub(crate) fn table_summaries_to_json(
    summaries: &[query_forge::TableSummary],
    include_samples: bool,
    include_stats: bool,
) -> Value {
    let tables = summaries
        .iter()
        .map(|summary| {
            let sample_rows = if include_samples {
                summary
                    .sample_rows
                    .iter()
                    .map(|row| {
                        row.iter()
                            .map(query_value_to_json)
                            .collect::<Vec<_>>()
                    })
                    .collect::<Vec<_>>()
            } else {
                Vec::new()
            };

            let columns = summary
                .columns
                .iter()
                .enumerate()
                .map(|(idx, col)| {
                    let mut value = json!({
                        "name": col.column_name,
                        "type": col.inferred_type.as_str(),
                        "nullable": col.nullable,
                    });

                    if include_stats {
                        if let Some(stats) = summary.column_stats.as_ref().and_then(|all| all.get(idx)) {
                            value["stats"] = json!({
                                "distinct_count": stats.distinct_count,
                                "min": stats.min_value,
                                "max": stats.max_value,
                            });
                        }
                    }

                    value
                })
                .collect::<Vec<_>>();

            json!({
                "table": summary.table_name,
                "source": summary.source_path,
                "selector": summary.source_selector,
                "row_count": summary.row_count,
                "columns": columns,
                "warnings": summary.warnings,
                "sample_rows": sample_rows,
            })
        })
        .collect::<Vec<_>>();

    json!(tables)
}

pub(crate) fn ai_success_envelope(
    command: &str,
    data: Value,
    metadata: Value,
    warnings: Vec<String>,
) -> Value {
    json!({
        "ok": true,
        "schema_version": "1.0",
        "command": command,
        "data": data,
        "metadata": metadata,
        "warnings": warnings,
        "error": Value::Null,
    })
}

pub(crate) fn ai_error_envelope(code: &str, message: &str) -> Value {
    json!({
        "ok": false,
        "schema_version": "1.0",
        "command": Value::Null,
        "data": Value::Null,
        "metadata": Value::Null,
        "warnings": [],
        "error": {
            "code": code,
            "message": message,
        },
    })
}

pub(crate) fn capabilities_json() -> Value {
    json!({
        "tool": "query-forge",
        "schema_version": "1.0",
        "ai": {
            "ai_mode": {
                "flag": "--ai-mode",
                "description": "Wraps command results in a structured JSON envelope for AI agents.",
            },
            "error_codes": [
                "QF_INVALID_ARGUMENT",
                "QF_INPUT_NOT_FOUND",
                "QF_CLIPBOARD_ERROR",
                "QF_SQL_ERROR",
                "QF_SCHEMA_MISMATCH",
                "QF_UNSUPPORTED_FORMAT",
                "QF_RUNTIME_ERROR"
            ]
        },
        "commands": {
            "query": {
                "supports_ai_mode": true,
                "supports_meta": true,
                "meta_formats": ["text", "json"],
                "output_formats": ["table", "csv", "json", "jsonl", "markdown", "html", "xml", "xlsx", "feather", "parquet"]
            },
            "tables": {
                "supports_ai_mode": true,
                "output_formats": ["text", "json", "markdown"]
            },
            "schema": {
                "supports_ai_mode": true,
                "output_formats": ["text", "json", "markdown"]
            },
            "inspect": {
                "supports_ai_mode": true,
                "output_formats": ["text", "json", "markdown"]
            },
            "diff": {
                "supports_ai_mode": true,
                "output_formats": ["table", "csv", "json", "jsonl", "markdown", "html"]
            },
            "pivot": {
                "supports_ai_mode": true,
                "output_formats": ["table", "csv", "json", "jsonl", "markdown", "html", "xml", "xlsx", "feather", "parquet"]
            },
            "conv": {
                "supports_ai_mode": true,
                "output_formats": ["table", "csv", "json", "jsonl", "markdown", "html", "xml", "xlsx", "feather", "parquet"]
            },
            "completions": {
                "supports_ai_mode": false
            },
            "man-page": {
                "supports_ai_mode": false
            },
            "capabilities": {
                "supports_ai_mode": false,
                "output": "json"
            }
        }
    })
}