use base64::Engine; pub use rmcp::model::ErrorData; use rmcp::model::ResourceContents; pub type ToolResult = Result; pub fn extract_text_from_resource(resource: &ResourceContents) -> String { match resource { ResourceContents::TextResourceContents { text, .. } => text.clone(), ResourceContents::BlobResourceContents { blob, mime_type, .. } => match base64::engine::general_purpose::STANDARD.decode(blob) { Ok(bytes) => { let byte_len = bytes.len(); match String::from_utf8(bytes) { Ok(text) => text, Err(_) => { let mime = mime_type .as_ref() .map(|m| m.as_str()) .unwrap_or("application/octet-stream"); format!("[Binary content ({}) - {} bytes]", mime, byte_len) } } } Err(_) => blob.clone(), }, } } #[cfg(test)] mod tests { use super::*; use test_case::test_case; #[test_case("Hello, World!", "Hello, World!" ; "simple text")] #[test_case("Hello from GitHub!", "Hello from GitHub!" ; "github content")] #[test_case("", "" ; "empty text")] fn test_extract_text_from_text_resource(input: &str, expected: &str) { let resource = ResourceContents::TextResourceContents { uri: "file:///test.txt".to_string(), mime_type: Some("text/plain".to_string()), text: input.to_string(), meta: None, }; assert_eq!(extract_text_from_resource(&resource), expected); } #[test_case("Hello from GitHub!", "Hello from GitHub!" ; "utf8 markdown")] #[test_case("Simple text", "Simple text" ; "utf8 plain")] fn test_extract_text_from_blob_utf8(input: &str, expected: &str) { let blob = base64::engine::general_purpose::STANDARD.encode(input.as_bytes()); let resource = ResourceContents::BlobResourceContents { uri: "github://repo/file.md".to_string(), mime_type: Some("text/markdown".to_string()), blob, meta: None, }; assert_eq!(extract_text_from_resource(&resource), expected); } #[test] fn test_extract_text_from_blob_binary() { let binary_data: Vec = vec![0xFF, 0xFE, 0x00, 0x01, 0x89, 0x50, 0x4E, 0x47]; let blob = base64::engine::general_purpose::STANDARD.encode(&binary_data); let resource = ResourceContents::BlobResourceContents { uri: "file:///image.png".to_string(), mime_type: Some("image/png".to_string()), blob, meta: None, }; assert_eq!( extract_text_from_resource(&resource), "[Binary content (image/png) - 8 bytes]" ); } #[test] fn test_extract_text_from_blob_binary_no_mime_type() { let binary_data: Vec = vec![0xFF, 0xFE]; let blob = base64::engine::general_purpose::STANDARD.encode(&binary_data); let resource = ResourceContents::BlobResourceContents { uri: "file:///unknown".to_string(), mime_type: None, blob, meta: None, }; assert_eq!( extract_text_from_resource(&resource), "[Binary content (application/octet-stream) - 2 bytes]" ); } #[test] fn test_extract_text_from_blob_invalid_base64() { let resource = ResourceContents::BlobResourceContents { uri: "file:///test.txt".to_string(), mime_type: Some("text/plain".to_string()), blob: "not valid base64!!!".to_string(), meta: None, }; assert_eq!(extract_text_from_resource(&resource), "not valid base64!!!"); } }