tandem-core 0.4.16

Core types and helpers for the Tandem engine
Documentation
pub fn tool_name_matches_policy(pattern: &str, tool_name: &str) -> bool {
    let pattern = pattern.trim();
    if pattern.is_empty() {
        return false;
    }
    if pattern == "*" {
        return true;
    }
    if let Some(prefix) = pattern.strip_suffix('*') {
        return tool_name.starts_with(prefix);
    }
    pattern == tool_name
}

pub fn any_policy_matches(patterns: &[String], tool_name: &str) -> bool {
    patterns
        .iter()
        .any(|pattern| tool_name_matches_policy(pattern, tool_name))
}

#[cfg(test)]
mod tests {
    use super::{any_policy_matches, tool_name_matches_policy};

    #[test]
    fn tool_name_matches_policy_supports_exact_and_wildcards() {
        assert!(tool_name_matches_policy("read", "read"));
        assert!(!tool_name_matches_policy("read", "write"));
        assert!(tool_name_matches_policy("*", "mcp.github.issues_list"));
        assert!(tool_name_matches_policy(
            "mcp.github.*",
            "mcp.github.issues_list"
        ));
        assert!(!tool_name_matches_policy(
            "mcp.github.*",
            "mcp.composio.gmail_send"
        ));
        assert!(tool_name_matches_policy("mcp.composio.", "mcp.composio."));
        assert!(!tool_name_matches_policy("", "read"));
    }

    #[test]
    fn any_policy_matches_handles_mixed_patterns() {
        let patterns = vec![
            "write".to_string(),
            "mcp.composio.*".to_string(),
            "read".to_string(),
        ];
        assert!(any_policy_matches(&patterns, "read"));
        assert!(any_policy_matches(&patterns, "mcp.composio.gmail_send"));
        assert!(!any_policy_matches(&patterns, "mcp.github.issues_list"));
    }
}