robotrt-cli 0.1.0-beta.2

RobotRT modular robotics runtime and middleware components.
use std::collections::HashMap;
use std::path::Path;

pub(super) fn load_error_severity_overrides(
    path: Option<&Path>,
) -> Result<HashMap<String, String>, String> {
    let Some(path) = path else {
        return Ok(HashMap::new());
    };

    let payload = super::read_json_file(path)?;
    let mapping = payload.get("code_to_severity").unwrap_or(&payload);
    let entries = mapping.as_object().ok_or_else(|| {
        format!(
            "severity map {} must be a JSON object or include code_to_severity object",
            path.display()
        )
    })?;

    let mut overrides = HashMap::new();
    for (code, severity_value) in entries {
        let raw = severity_value.as_str().ok_or_else(|| {
            format!(
                "severity map {} has non-string severity for code {}",
                path.display(),
                code
            )
        })?;
        let normalized = raw.trim().to_ascii_lowercase();
        if !is_supported_failure_severity(&normalized) {
            return Err(format!(
                "severity map {} has invalid severity '{}' for code {} (expected info|warn|critical)",
                path.display(),
                raw,
                code
            ));
        }
        overrides.insert(code.to_string(), normalized);
    }

    Ok(overrides)
}

pub(super) fn summarize_error_group(error: &str) -> String {
    if error.contains("missing robotrt config") {
        return String::from("missing_config");
    }
    if error.contains("target schema") && error.contains("is lower than current schema") {
        return String::from("schema_downgrade_risk");
    }
    if error.contains("project dir not found") {
        return String::from("project_not_found");
    }
    if error.contains("unsupported target schema") {
        return String::from("unsupported_target_schema");
    }

    let first_line = error.lines().next().unwrap_or("unknown_error").trim();
    if first_line.is_empty() {
        String::from("unknown_error")
    } else {
        first_line.to_string()
    }
}

pub(super) fn summarize_error_code(error: &str) -> String {
    let group = summarize_error_group(error);
    let code = match group.as_str() {
        "missing_config" => "E_MISSING_CONFIG",
        "schema_downgrade_risk" => "E_SCHEMA_DOWNGRADE_RISK",
        "project_not_found" => "E_PROJECT_NOT_FOUND",
        "unsupported_target_schema" => "E_UNSUPPORTED_TARGET_SCHEMA",
        _ => "E_UNKNOWN",
    };
    code.to_string()
}

pub(super) fn summarize_error_severity(
    error: &str,
    severity_overrides: &HashMap<String, String>,
) -> String {
    let code = summarize_error_code(error);
    summarize_error_code_severity(&code, severity_overrides)
}

fn summarize_error_code_severity(
    code: &str,
    severity_overrides: &HashMap<String, String>,
) -> String {
    if let Some(severity) = severity_overrides.get(code) {
        return severity.clone();
    }

    let severity = match code {
        "E_MISSING_CONFIG" => "critical",
        "E_PROJECT_NOT_FOUND" => "critical",
        "E_SCHEMA_DOWNGRADE_RISK" => "warn",
        "E_UNSUPPORTED_TARGET_SCHEMA" => "warn",
        _ => "info",
    };
    severity.to_string()
}

fn is_supported_failure_severity(value: &str) -> bool {
    matches!(value, "info" | "warn" | "critical")
}