1use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct ListResourcesRequest {
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub cursor: Option<String>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct ListResourcesResponse {
24 pub resources: Vec<Resource>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub next_cursor: Option<String>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct Resource {
35 pub uri: String,
37
38 pub name: String,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub description: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub mime_type: Option<String>,
48}
49
50impl Resource {
51 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
53 Self {
54 uri: uri.into(),
55 name: name.into(),
56 description: None,
57 mime_type: None,
58 }
59 }
60
61 pub fn with_description(mut self, description: impl Into<String>) -> Self {
63 self.description = Some(description.into());
64 self
65 }
66
67 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
69 self.mime_type = Some(mime_type.into());
70 self
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct ReadResourceRequest {
77 pub uri: String,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct ReadResourceResponse {
84 #[serde(default)]
86 pub contents: Vec<ResourceContent>,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(tag = "type")]
92pub enum ResourceContent {
93 #[serde(rename = "text")]
95 Text {
96 text: String,
98
99 uri: String,
101
102 #[serde(rename = "mimeType")]
104 #[serde(skip_serializing_if = "Option::is_none")]
105 mime_type: Option<String>,
106 },
107
108 #[serde(rename = "blob")]
110 Blob {
111 blob: String,
113
114 uri: String,
116
117 #[serde(rename = "mimeType")]
119 #[serde(skip_serializing_if = "Option::is_none")]
120 mime_type: Option<String>,
121 },
122}
123
124impl ResourceContent {
125 pub fn text(uri: impl Into<String>, text: impl Into<String>) -> Self {
127 Self::Text {
128 text: text.into(),
129 uri: uri.into(),
130 mime_type: None,
131 }
132 }
133
134 pub fn text_with_mime_type(
136 uri: impl Into<String>,
137 text: impl Into<String>,
138 mime_type: impl Into<String>,
139 ) -> Self {
140 Self::Text {
141 text: text.into(),
142 uri: uri.into(),
143 mime_type: Some(mime_type.into()),
144 }
145 }
146
147 pub fn blob(uri: impl Into<String>, blob: impl Into<String>) -> Self {
149 Self::Blob {
150 blob: blob.into(),
151 uri: uri.into(),
152 mime_type: None,
153 }
154 }
155
156 pub fn blob_with_mime_type(
158 uri: impl Into<String>,
159 blob: impl Into<String>,
160 mime_type: impl Into<String>,
161 ) -> Self {
162 Self::Blob {
163 blob: blob.into(),
164 uri: uri.into(),
165 mime_type: Some(mime_type.into()),
166 }
167 }
168
169 pub fn uri(&self) -> &str {
171 match self {
172 Self::Text { uri, .. } => uri,
173 Self::Blob { uri, .. } => uri,
174 }
175 }
176
177 pub fn mime_type(&self) -> Option<&str> {
179 match self {
180 Self::Text { mime_type, .. } => mime_type.as_deref(),
181 Self::Blob { mime_type, .. } => mime_type.as_deref(),
182 }
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct SubscribeRequest {
189 pub uri: String,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195pub struct UnsubscribeRequest {
196 pub uri: String,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct ResourceUpdatedNotification {
203 pub uri: String,
205
206 #[serde(flatten)]
208 pub metadata: HashMap<String, Value>,
209}
210
211impl ResourceUpdatedNotification {
212 pub fn new(uri: impl Into<String>) -> Self {
214 Self {
215 uri: uri.into(),
216 metadata: HashMap::new(),
217 }
218 }
219
220 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
222 self.metadata.insert(key.into(), value);
223 self
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
229pub struct ResourceListChangedNotification {
230 #[serde(flatten)]
232 pub metadata: HashMap<String, Value>,
233}
234
235impl ResourceListChangedNotification {
236 pub fn new() -> Self {
238 Self::default()
239 }
240
241 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
243 self.metadata.insert(key.into(), value);
244 self
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use serde_json::json;
252
253 #[test]
254 fn test_resource_creation() {
255 let resource = Resource::new("file:///path/to/file.txt", "file.txt")
256 .with_description("A text file")
257 .with_mime_type("text/plain");
258
259 assert_eq!(resource.uri, "file:///path/to/file.txt");
260 assert_eq!(resource.name, "file.txt");
261 assert_eq!(resource.description, Some("A text file".to_string()));
262 assert_eq!(resource.mime_type, Some("text/plain".to_string()));
263 }
264
265 #[test]
266 fn test_list_resources_request() {
267 let request = ListResourcesRequest { cursor: None };
268 let json = serde_json::to_string(&request).unwrap();
269 let deserialized: ListResourcesRequest = serde_json::from_str(&json).unwrap();
270 assert_eq!(request, deserialized);
271 }
272
273 #[test]
274 fn test_read_resource_request() {
275 let request = ReadResourceRequest {
276 uri: "file:///path/to/file.txt".to_string(),
277 };
278
279 let json = serde_json::to_string(&request).unwrap();
280 let deserialized: ReadResourceRequest = serde_json::from_str(&json).unwrap();
281 assert_eq!(request, deserialized);
282 }
283
284 #[test]
285 fn test_resource_content_text() {
286 let content =
287 ResourceContent::text_with_mime_type("file:///test.txt", "Hello world", "text/plain");
288
289 let json = serde_json::to_value(&content).unwrap();
290 assert_eq!(json["type"], "text");
291 assert_eq!(json["text"], "Hello world");
292 assert_eq!(json["mimeType"], "text/plain");
293 assert_eq!(content.uri(), "file:///test.txt");
294 assert_eq!(content.mime_type(), Some("text/plain"));
295 }
296
297 #[test]
298 fn test_resource_content_blob() {
299 let content =
300 ResourceContent::blob_with_mime_type("file:///test.png", "base64data", "image/png");
301
302 let json = serde_json::to_value(&content).unwrap();
303 assert_eq!(json["type"], "blob");
304 assert_eq!(json["blob"], "base64data");
305 assert_eq!(json["mimeType"], "image/png");
306 assert_eq!(content.uri(), "file:///test.png");
307 assert_eq!(content.mime_type(), Some("image/png"));
308 }
309
310 #[test]
311 fn test_resource_updated_notification() {
312 let notification = ResourceUpdatedNotification::new("file:///test.txt")
313 .with_metadata("timestamp", json!("2024-01-01T00:00:00Z"));
314
315 assert_eq!(notification.uri, "file:///test.txt");
316 assert_eq!(
317 notification.metadata.get("timestamp"),
318 Some(&json!("2024-01-01T00:00:00Z"))
319 );
320 }
321}