use async_trait::async_trait;
use crate::error::Result;
use super::{ReadResourceResult, Resource, ResourceTemplate};
#[async_trait]
pub trait McpResource: Send + Sync + std::fmt::Debug {
fn uri(&self) -> &str;
fn name(&self) -> &str;
fn description(&self) -> Option<&str> {
None
}
fn mime_type(&self) -> Option<&str> {
None
}
fn is_template(&self) -> bool {
false
}
async fn read(&self, uri: &str) -> Result<ReadResourceResult>;
fn to_resource(&self) -> Option<Resource> {
None
}
fn to_template(&self) -> Option<ResourceTemplate> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
#[derive(Debug)]
struct ConcreteResource;
#[async_trait]
impl McpResource for ConcreteResource {
fn uri(&self) -> &str {
"file:///readme.md"
}
fn name(&self) -> &str {
"README"
}
fn is_template(&self) -> bool {
false
}
async fn read(&self, _uri: &str) -> Result<ReadResourceResult> {
Ok(ReadResourceResult::text("file:///readme.md", "# Hello"))
}
fn to_resource(&self) -> Option<Resource> {
Some(Resource {
uri: self.uri().to_string(),
name: self.name().to_string(),
description: self.description().map(|s| s.to_string()),
mime_type: self.mime_type().map(|s| s.to_string()),
size: None,
annotations: None,
})
}
}
#[derive(Debug)]
struct TemplateResource;
#[async_trait]
impl McpResource for TemplateResource {
fn uri(&self) -> &str {
"docs://{topic}/readme"
}
fn name(&self) -> &str {
"Documentation"
}
fn is_template(&self) -> bool {
true
}
async fn read(&self, _uri: &str) -> Result<ReadResourceResult> {
Ok(ReadResourceResult::text("docs://rust/readme", "# Rust"))
}
fn to_template(&self) -> Option<ResourceTemplate> {
Some(ResourceTemplate {
uri_template: self.uri().to_string(),
name: self.name().to_string(),
description: self.description().map(|s| s.to_string()),
mime_type: self.mime_type().map(|s| s.to_string()),
})
}
}
#[test]
fn test_default_description_is_none() {
let resource = ConcreteResource;
assert!(resource.description().is_none());
}
#[test]
fn test_default_mime_type_is_none() {
let resource = ConcreteResource;
assert!(resource.mime_type().is_none());
}
#[test]
fn test_default_is_template_is_false() {
let resource = ConcreteResource;
assert!(!resource.is_template());
}
#[test]
fn test_concrete_to_template_returns_none() {
let resource = ConcreteResource;
assert!(resource.to_template().is_none());
}
#[test]
fn test_template_to_resource_returns_none() {
let resource = TemplateResource;
assert!(resource.to_resource().is_none());
}
#[tokio::test]
async fn test_concrete_read() {
let resource = ConcreteResource;
let result = resource.read("file:///readme.md").await.unwrap();
assert_eq!(result.contents.len(), 1);
assert_eq!(result.contents[0].uri(), "file:///readme.md");
}
#[tokio::test]
async fn test_template_read() {
let resource = TemplateResource;
let result = resource.read("docs://rust/readme").await.unwrap();
assert_eq!(result.contents[0].uri(), "docs://rust/readme");
}
}