component-shape-mcp-macros 0.3.4

Proc macros for component-shape-mcp schema helpers.
Documentation
use super::*;

#[cfg(test)]
pub(crate) fn resolve_default_mcp_crate_path() -> syn::Result<Path> {
    Ok(parse_quote!(component_shape_mcp))
}

#[cfg(not(test))]
pub(crate) fn resolve_default_mcp_crate_path() -> syn::Result<Path> {
    if let Some(path) = resolve_package_path("component-shape-mcp", "crate", None) {
        return Ok(path);
    }

    let facade_paths = [
        ("gpui-form", "crate::mcp", "mcp"),
        ("gpui-table", "crate::mcp", "mcp"),
    ]
    .into_iter()
    .filter_map(|(package, itself_path, module)| {
        resolve_package_path(package, itself_path, Some(module)).map(|path| (package, path))
    })
    .collect::<Vec<_>>();

    match facade_paths.as_slice() {
        [(_, path)] => Ok(path.clone()),
        [] => Err(syn::Error::new(
            proc_macro2::Span::call_site(),
            "MCP derive could not find `component-shape-mcp`, `gpui-form`, or `gpui-table` in this crate's manifest; add one of those dependencies or set `#[mcp(crate = path::to::mcp)]`",
        )),
        _ => Err(syn::Error::new(
            proc_macro2::Span::call_site(),
            multiple_facade_error_message(&facade_paths),
        )),
    }
}

pub(crate) fn multiple_facade_error_message(facade_paths: &[(&str, Path)]) -> String {
    let packages = facade_paths
        .iter()
        .map(|(package, _)| format!("`{package}`"))
        .collect::<Vec<_>>()
        .join(", ");
    let suggestions = facade_paths
        .iter()
        .map(|(_, path)| format!("`#[mcp(crate = {})]`", display_path(path)))
        .collect::<Vec<_>>()
        .join(" or ");

    format!(
        "MCP derive found multiple MCP facade crates: {packages}; add {suggestions} to choose one"
    )
}

fn display_path(path: &Path) -> String {
    path.segments
        .iter()
        .map(|segment| segment.ident.to_string())
        .collect::<Vec<_>>()
        .join("::")
}

#[cfg(not(test))]
fn resolve_package_path(package: &str, itself_path: &str, module: Option<&str>) -> Option<Path> {
    let path = match crate_name(package).ok()? {
        FoundCrate::Itself => itself_path.to_string(),
        FoundCrate::Name(name) => match module {
            Some(module) => format!("::{name}::{module}"),
            None => format!("::{name}"),
        },
    };
    syn::parse_str(&path).ok()
}