tools-kit 0.0.2

Portable Local and MCP tool execution with narrow extension traits.
Documentation
use std::{
    fs,
    path::{Path, PathBuf},
    process::Command,
};

fn manifest_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

fn ui_fixture(name: &str) -> PathBuf {
    manifest_dir().join("tests/ui").join(name)
}

fn cargo_toml_path_value(path: &Path) -> String {
    path.display().to_string().replace('\\', "\\\\")
}

fn temp_project(name: &str, source: &str) -> tempfile::TempDir {
    let temp = tempfile::tempdir().expect("temp dir should build");
    let crate_name = name.replace(['.', '-'], "_");
    let src_dir = temp.path().join("src");
    let cargo_dir = temp.path().join(".cargo");
    fs::create_dir_all(&src_dir).expect("src dir should exist");
    fs::create_dir_all(&cargo_dir).expect(".cargo dir should exist");
    fs::write(src_dir.join("main.rs"), source).expect("main.rs should write");
    fs::write(
        temp.path().join("Cargo.toml"),
        format!(
            r#"[package]
name = "{crate_name}"
version = "0.0.0"
edition = "2024"

[workspace]

[dependencies]
tools-kit = {{ path = "{}", features = ["mcp-server"] }}
"#,
            cargo_toml_path_value(&manifest_dir()),
        ),
    )
    .expect("Cargo.toml should write");
    fs::write(cargo_dir.join("config.toml"), "[net]\noffline = true\n")
        .expect(".cargo/config.toml should write");
    let workspace_lock = manifest_dir()
        .parent()
        .expect("workspace root should exist")
        .join("Cargo.lock");
    if workspace_lock.is_file() {
        fs::copy(workspace_lock, temp.path().join("Cargo.lock"))
            .expect("workspace Cargo.lock should copy");
    }
    temp
}

fn run_compile_fail_case(name: &str, expected_fragments: &[&str]) {
    let source = fs::read_to_string(ui_fixture(name)).expect("ui fixture should read");
    let project = temp_project(name, &source);
    let target_dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("compile-contracts");
    let output = Command::new("cargo")
        .arg("check")
        .arg("--offline")
        .arg("--quiet")
        .current_dir(project.path())
        .env("CARGO_TARGET_DIR", target_dir)
        .output()
        .expect("nested cargo check should run");

    assert!(
        !output.status.success(),
        "expected `{name}` to fail to compile, but it succeeded"
    );

    let stderr = String::from_utf8_lossy(&output.stderr);
    for fragment in expected_fragments {
        assert!(
            stderr.contains(fragment),
            "expected `{name}` stderr to contain `{fragment}`\nfull stderr:\n{stderr}"
        );
    }
}

#[test]
fn tools_kit_public_surface_rejects_backend_payload_access() {
    run_compile_fail_case(
        "toolset_direct_init.rs",
        &[
            "no `EmptyToolsetBackend` in the root",
            "error[E0599]",
            "named `Empty` found for struct `Toolset`",
        ],
    );
}

#[test]
fn tool_extension_contract_enforces_async_and_shared_ownership_bounds() {
    run_compile_fail_case(
        "tool_handler_future_not_send.rs",
        &["future cannot be sent between threads safely"],
    );
    run_compile_fail_case(
        "toolset_backend_not_sync.rs",
        &["cannot be shared between threads safely"],
    );
}

#[test]
fn tool_extension_contract_keeps_erased_adapters_private() {
    run_compile_fail_case(
        "tool_backend_erasure_private.rs",
        &["ErasedToolHandlerBackend", "ErasedToolsetBackend"],
    );
}

#[test]
fn child_handler_ingress_is_not_public() {
    run_compile_fail_case(
        "tool_handler_from_backend_private.rs",
        &[
            "error[E0599]",
            "named `from_backend` found for struct `ToolHandler`",
        ],
    );
}

#[test]
fn supplied_tool_behavior_requires_its_public_traits_in_scope() {
    run_compile_fail_case(
        "tool_behavior_without_traits.rs",
        &[
            "error[E0599]",
            "named `list_tools` found for struct `Toolset`",
            "named `tool_handler` found for struct `Toolset`",
            "named `shutdown` found for struct `Toolset`",
            "named `step` found for struct `ToolHandler`",
        ],
    );
}

#[test]
fn unique_runtime_owners_are_not_cloneable() {
    run_compile_fail_case("tool_handler_not_clone.rs", &["no method named `clone`"]);
    run_compile_fail_case("mcp_server_not_clone.rs", &["no method named `clone`"]);
}