hutc 0.1.0

Simple af rest api testing client using lua
use mlua::Variadic;
use mlua::prelude::*;

use crate::{expect::Expect, http::client::HttpClient, registry::TestRegistry};

pub fn setup_lua(registry: TestRegistry) -> Result<Lua, Box<dyn std::error::Error>> {
    let lua = Lua::new();

    let globals = lua.globals();

    let test_fn = lua.create_function(move |_, (name, func): (String, LuaFunction)| {
        if name.trim().is_empty() {
            return Err(mlua::Error::RuntimeError(
                "test name cannot be empty".to_string(),
            ));
        }
        registry.add_test(name, func);
        Ok(())
    })?;

    let expect_fn = lua.create_function(move |_, value: LuaValue| {
        Ok(Expect {
            value,
            error_message: None,
        })
    })?;

    let http_fn = lua.create_function(|_, ()| {
        let http_client = HttpClient::new();
        Ok(http_client)
    })?;

    let log_fn = lua.create_function(|_, values: Variadic<LuaValue>| {
        let line = values
            .into_iter()
            .map(|value| format!("{value:#?}"))
            .collect::<Vec<_>>()
            .join("\t");
        println!("{line}");
        Ok(())
    })?;

    globals.set("http", http_fn)?;
    globals.set("test", test_fn)?;
    globals.set("expect", expect_fn)?;
    globals.set("log", log_fn)?;

    Ok(lua)
}

pub fn extract_lua_error(err: mlua::Error) -> String {
    let mut lines = Vec::new();
    collect_error_lines(&err, &mut lines, 0);
    if lines.is_empty() {
        "<unknown Lua error>".to_string()
    } else {
        lines.join("\n")
    }
}

fn collect_error_lines(err: &mlua::Error, lines: &mut Vec<String>, depth: usize) {
    let indent = "  ".repeat(depth);
    match err {
        mlua::Error::CallbackError { traceback, cause } => {
            lines.push(format!("{indent}lua callback error"));

            let traceback = traceback.trim();
            if !traceback.is_empty() {
                lines.push(format!("{indent}traceback:"));
                for line in traceback.lines() {
                    let line = line.trim_end();
                    if !line.is_empty() && !line.contains("src/main.rs:") {
                        lines.push(format!("{indent}  {line}"));
                    }
                }
            }

            lines.push(format!("{indent}cause:"));
            collect_error_lines(cause.as_ref(), lines, depth + 1);
        }
        other => {
            let message = other.to_string();
            if message.trim().is_empty() {
                lines.push(format!("{indent}<empty error>"));
            } else {
                for line in message.lines() {
                    lines.push(format!("{indent}{line}"));
                }
            }
        }
    }
}