oxide-gen 0.3.0

Spec-to-crate generator for Rust Oxide. Generates Rust clients, CLI commands, SKILL.md, and MCP server configs from OpenAPI, GraphQL, and gRPC specs.
Documentation
//! Emit a `Cargo.toml` for the generated crate.

use crate::ir::{ApiKind, ApiSpec};

/// Render the `Cargo.toml` contents.
pub fn render(spec: &ApiSpec) -> String {
    let needs_tonic = matches!(spec.kind, ApiKind::Grpc);
    let needs_http = matches!(spec.kind, ApiKind::OpenApi | ApiKind::GraphQl);

    let mut deps = String::new();
    deps.push_str("serde = { version = \"1\", features = [\"derive\"] }\n");
    deps.push_str("serde_json = \"1\"\n");
    deps.push_str("clap = { version = \"4\", features = [\"derive\", \"env\"] }\n");
    deps.push_str("tokio = { version = \"1\", features = [\"full\"] }\n");
    deps.push_str("anyhow = \"1\"\n");
    if needs_http {
        deps.push_str("reqwest = { version = \"0.12\", features = [\"json\", \"rustls-tls\"], default-features = false }\n");
    }
    if spec.operations.iter().any(|op| op.streaming.is_streaming()) {
        deps.push_str("futures-util = \"0.3\"\n");
        if spec.kind == ApiKind::GraphQl {
            deps.push_str("tokio-tungstenite = { version = \"0.23\", features = [\"rustls-tls-native-roots\"] }\n");
        }
    }
    if needs_tonic {
        deps.push_str(
            "# gRPC scaffold — wire up `tonic` + `prost` once you compile the .proto file.\n",
        );
        deps.push_str("tonic = \"0.12\"\n");
        deps.push_str("prost = \"0.13\"\n");
        deps.push_str("tokio-stream = { version = \"0.1\", features = [\"net\"] }\n");
    }

    let bin_name = format!("{}-cli", spec.name.replace('_', "-"));
    let build_script = if needs_tonic {
        "\nbuild = \"build.rs\""
    } else {
        ""
    };
    let build_deps = if needs_tonic {
        "\n[build-dependencies]\ntonic-build = \"0.12\"\n"
    } else {
        ""
    };

    format!(
        r#"# Generated by oxide-gen. Edit at your own risk.
[package]
name = "{name}"
version = "{version}"
edition = "2021"
description = "{description}"{build_script}

[lib]
name = "{lib_name}"
path = "src/lib.rs"

[[bin]]
name = "{bin_name}"
path = "src/main.rs"

[dependencies]
{deps}
{build_deps}"#,
        name = spec.name.replace('_', "-"),
        version = spec.version,
        description = escape_toml(spec.description.as_deref().unwrap_or(&spec.display_name)),
        lib_name = spec.name,
        bin_name = bin_name,
        deps = deps.trim_end(),
        build_script = build_script,
        build_deps = build_deps,
    )
}

fn escape_toml(s: &str) -> String {
    s.replace('\\', "\\\\").replace('"', "\\\"")
}