use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OperationType {
Read,
Write,
Delete,
Admin,
}
pub fn classify_operation(method: &str, tool_name: Option<&str>) -> OperationType {
match method {
"resources/read" | "resources/subscribe" | "completion/complete" => {
return OperationType::Read;
}
_ => {}
}
let tool = match tool_name {
Some(t) => t.to_lowercase(),
None => return OperationType::Read, };
if contains_any_token(&tool, &["admin", "manage", "configure", "grant", "revoke"]) {
return OperationType::Admin;
}
if contains_any_token(&tool, &["delete", "remove", "drop", "purge"]) {
return OperationType::Delete;
}
if contains_any_token(
&tool,
&[
"read",
"get",
"list",
"search",
"view",
"describe",
"fetch",
"query",
"analyze",
"summarize",
"report",
"calculate",
"compute",
"check",
"inspect",
"review",
],
) {
return OperationType::Read;
}
OperationType::Write
}
fn contains_any_token(haystack: &str, needles: &[&str]) -> bool {
let lower = haystack.to_lowercase();
let tokens: Vec<&str> = lower.split(['_', '-', '.', '/', ' ']).collect();
needles.iter().any(|needle| {
let lower_needle = needle.to_lowercase();
tokens.iter().any(|token| *token == lower_needle)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_resource_read_method() {
assert_eq!(
classify_operation("resources/read", None),
OperationType::Read
);
}
#[test]
fn classify_tool_by_name() {
assert_eq!(
classify_operation("tools/call", Some("read_file")),
OperationType::Read
);
assert_eq!(
classify_operation("tools/call", Some("write_file")),
OperationType::Write
);
assert_eq!(
classify_operation("tools/call", Some("delete_resource")),
OperationType::Delete
);
assert_eq!(
classify_operation("tools/call", Some("admin_users")),
OperationType::Admin
);
}
#[test]
fn classify_mixed_patterns() {
assert_eq!(
classify_operation("tools/call", Some("get_user")),
OperationType::Read
);
assert_eq!(
classify_operation("tools/call", Some("create_user")),
OperationType::Write
);
assert_eq!(
classify_operation("tools/call", Some("list_files")),
OperationType::Read
);
}
#[test]
fn admin_beats_delete() {
assert_eq!(
classify_operation("tools/call", Some("admin_delete")),
OperationType::Admin
);
}
#[test]
fn classify_novel_tool_names() {
let result = classify_operation("tools/call", Some("extract_intelligence"));
assert_eq!(result, OperationType::Write);
let result = classify_operation("tools/call", Some("xread_file"));
assert_eq!(result, OperationType::Write);
let result = classify_operation("tools/call", Some("readonly_report"));
assert_eq!(result, OperationType::Read);
}
#[test]
fn empty_tool_name() {
let result = classify_operation("tools/call", Some(""));
assert_eq!(result, OperationType::Write);
let result = classify_operation("", None);
assert_eq!(result, OperationType::Read);
let result = classify_operation("", Some(""));
assert_eq!(result, OperationType::Write);
}
#[test]
fn tool_name_with_only_delimiters() {
let result = classify_operation("tools/call", Some("___---..."));
assert_eq!(result, OperationType::Write);
}
}