use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListResourcesRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListResourcesResponse {
pub resources: Vec<Resource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Resource {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
impl Resource {
pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
Self {
uri: uri.into(),
name: name.into(),
description: None,
mime_type: None,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReadResourceRequest {
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReadResourceResponse {
#[serde(default)]
pub contents: Vec<ResourceContent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ResourceContent {
#[serde(rename = "text")]
Text {
text: String,
uri: String,
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
},
#[serde(rename = "blob")]
Blob {
blob: String,
uri: String,
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
},
}
impl ResourceContent {
pub fn text(uri: impl Into<String>, text: impl Into<String>) -> Self {
Self::Text {
text: text.into(),
uri: uri.into(),
mime_type: None,
}
}
pub fn text_with_mime_type(
uri: impl Into<String>,
text: impl Into<String>,
mime_type: impl Into<String>,
) -> Self {
Self::Text {
text: text.into(),
uri: uri.into(),
mime_type: Some(mime_type.into()),
}
}
pub fn blob(uri: impl Into<String>, blob: impl Into<String>) -> Self {
Self::Blob {
blob: blob.into(),
uri: uri.into(),
mime_type: None,
}
}
pub fn blob_with_mime_type(
uri: impl Into<String>,
blob: impl Into<String>,
mime_type: impl Into<String>,
) -> Self {
Self::Blob {
blob: blob.into(),
uri: uri.into(),
mime_type: Some(mime_type.into()),
}
}
pub fn uri(&self) -> &str {
match self {
Self::Text { uri, .. } => uri,
Self::Blob { uri, .. } => uri,
}
}
pub fn mime_type(&self) -> Option<&str> {
match self {
Self::Text { mime_type, .. } => mime_type.as_deref(),
Self::Blob { mime_type, .. } => mime_type.as_deref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubscribeRequest {
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnsubscribeRequest {
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceUpdatedNotification {
pub uri: String,
#[serde(flatten)]
pub metadata: HashMap<String, Value>,
}
impl ResourceUpdatedNotification {
pub fn new(uri: impl Into<String>) -> Self {
Self {
uri: uri.into(),
metadata: HashMap::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ResourceListChangedNotification {
#[serde(flatten)]
pub metadata: HashMap<String, Value>,
}
impl ResourceListChangedNotification {
pub fn new() -> Self {
Self::default()
}
pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_resource_creation() {
let resource = Resource::new("file:///path/to/file.txt", "file.txt")
.with_description("A text file")
.with_mime_type("text/plain");
assert_eq!(resource.uri, "file:///path/to/file.txt");
assert_eq!(resource.name, "file.txt");
assert_eq!(resource.description, Some("A text file".to_string()));
assert_eq!(resource.mime_type, Some("text/plain".to_string()));
}
#[test]
fn test_list_resources_request() {
let request = ListResourcesRequest { cursor: None };
let json = serde_json::to_string(&request).unwrap();
let deserialized: ListResourcesRequest = serde_json::from_str(&json).unwrap();
assert_eq!(request, deserialized);
}
#[test]
fn test_read_resource_request() {
let request = ReadResourceRequest {
uri: "file:///path/to/file.txt".to_string(),
};
let json = serde_json::to_string(&request).unwrap();
let deserialized: ReadResourceRequest = serde_json::from_str(&json).unwrap();
assert_eq!(request, deserialized);
}
#[test]
fn test_resource_content_text() {
let content =
ResourceContent::text_with_mime_type("file:///test.txt", "Hello world", "text/plain");
let json = serde_json::to_value(&content).unwrap();
assert_eq!(json["type"], "text");
assert_eq!(json["text"], "Hello world");
assert_eq!(json["mimeType"], "text/plain");
assert_eq!(content.uri(), "file:///test.txt");
assert_eq!(content.mime_type(), Some("text/plain"));
}
#[test]
fn test_resource_content_blob() {
let content =
ResourceContent::blob_with_mime_type("file:///test.png", "base64data", "image/png");
let json = serde_json::to_value(&content).unwrap();
assert_eq!(json["type"], "blob");
assert_eq!(json["blob"], "base64data");
assert_eq!(json["mimeType"], "image/png");
assert_eq!(content.uri(), "file:///test.png");
assert_eq!(content.mime_type(), Some("image/png"));
}
#[test]
fn test_resource_updated_notification() {
let notification = ResourceUpdatedNotification::new("file:///test.txt")
.with_metadata("timestamp", json!("2024-01-01T00:00:00Z"));
assert_eq!(notification.uri, "file:///test.txt");
assert_eq!(
notification.metadata.get("timestamp"),
Some(&json!("2024-01-01T00:00:00Z"))
);
}
}