1use std::collections::HashMap;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11
12use crate::CapabilityManifest;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(tag = "type", rename_all = "lowercase")]
17pub enum ResourceContent {
18 Text {
20 uri: String,
21 #[serde(rename = "mimeType")]
22 mime_type: String,
23 text: String,
24 },
25 Blob {
27 uri: String,
28 #[serde(rename = "mimeType")]
29 mime_type: String,
30 blob: String,
31 },
32}
33
34impl ResourceContent {
35 pub fn text(
37 uri: impl Into<String>,
38 mime_type: impl Into<String>,
39 text: impl Into<String>,
40 ) -> Self {
41 Self::Text {
42 uri: uri.into(),
43 mime_type: mime_type.into(),
44 text: text.into(),
45 }
46 }
47
48 pub fn blob(uri: impl Into<String>, mime_type: impl Into<String>, data: &[u8]) -> Self {
50 use base64::Engine;
51 Self::Blob {
52 uri: uri.into(),
53 mime_type: mime_type.into(),
54 blob: base64::engine::general_purpose::STANDARD.encode(data),
55 }
56 }
57
58 pub fn uri(&self) -> &str {
60 match self {
61 Self::Text { uri, .. } => uri,
62 Self::Blob { uri, .. } => uri,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ResourceInfo {
70 pub uri: String,
72 pub name: String,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub description: Option<String>,
77 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
79 pub mime_type: Option<String>,
80}
81
82impl ResourceInfo {
83 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
85 Self {
86 uri: uri.into(),
87 name: name.into(),
88 description: None,
89 mime_type: None,
90 }
91 }
92
93 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
95 self.description = Some(desc.into());
96 self
97 }
98
99 pub fn with_mime_type(mut self, mime: impl Into<String>) -> Self {
101 self.mime_type = Some(mime.into());
102 self
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ResourceList {
109 pub resources: Vec<ResourceInfo>,
111 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
113 pub next_cursor: Option<String>,
114}
115
116#[derive(Debug, Clone, thiserror::Error)]
118pub enum ResourceError {
119 #[error("resource not found: {0}")]
120 NotFound(String),
121 #[error("invalid URI: {0}")]
122 InvalidUri(String),
123 #[error("handler error: {0}")]
124 HandlerError(String),
125 #[error("{kind} capacity exceeded")]
126 CapacityExceeded { kind: &'static str, max: usize },
127 #[error("subscription not supported")]
128 SubscriptionNotSupported,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
133pub struct SubscriptionId(pub u64);
134
135pub trait ResourceHandler: Send + Sync {
137 fn uri_template(&self) -> &str;
139
140 fn list(&self, cursor: Option<&str>) -> Result<ResourceList, ResourceError>;
142
143 fn read(&self, uri: &str) -> Result<ResourceContent, ResourceError>;
145
146 fn supports_subscribe(&self) -> bool {
148 false
149 }
150
151 fn matches(&self, uri: &str) -> bool {
153 uri_matches_template(uri, self.uri_template())
154 }
155}
156
157struct RegisteredResourceHandler {
158 id: u16,
159 handler: Box<dyn ResourceHandler>,
160}
161
162pub fn uri_matches_template(uri: &str, template: &str) -> bool {
165 let mut template_parts = Vec::new();
167 let mut current = template;
168
169 while let Some(start) = current.find('{') {
170 template_parts.push(¤t[..start]);
171 if let Some(end) = current[start..].find('}') {
172 current = ¤t[start + end + 1..];
173 } else {
174 break;
175 }
176 }
177 template_parts.push(current);
178
179 let mut uri_pos = 0;
181 for (i, part) in template_parts.iter().enumerate() {
182 if part.is_empty() {
183 continue;
184 }
185 if let Some(pos) = uri[uri_pos..].find(part) {
186 if i == 0 && pos != 0 {
187 return false; }
189 uri_pos += pos + part.len();
190 } else {
191 return false;
192 }
193 }
194
195 match template_parts.last() {
196 Some(last_part) if !last_part.is_empty() => uri_pos == uri.len(),
197 _ => true,
198 }
199}
200
201pub struct ResourceRegistry {
203 handlers: Vec<RegisteredResourceHandler>,
205 subscriptions: RwLock<HashMap<String, Vec<SubscriptionId>>>,
207 subscription_counter: AtomicU64,
209 callbacks: RwLock<HashMap<SubscriptionId, Arc<dyn Fn(&str) + Send + Sync>>>,
211}
212
213impl Default for ResourceRegistry {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219impl ResourceRegistry {
220 pub fn new() -> Self {
222 Self {
223 handlers: Vec::new(),
224 subscriptions: RwLock::new(HashMap::new()),
225 subscription_counter: AtomicU64::new(1),
226 callbacks: RwLock::new(HashMap::new()),
227 }
228 }
229
230 pub fn register(
232 &mut self,
233 handler: impl ResourceHandler + 'static,
234 ) -> Result<u16, ResourceError> {
235 if self.handlers.len() >= CapabilityManifest::MAX_RESOURCES {
236 return Err(ResourceError::CapacityExceeded {
237 kind: "resource",
238 max: CapabilityManifest::MAX_RESOURCES,
239 });
240 }
241
242 let id = self.handlers.len() as u16;
243 self.handlers.push(RegisteredResourceHandler {
244 id,
245 handler: Box::new(handler),
246 });
247 Ok(id)
248 }
249
250 pub fn handler_count(&self) -> usize {
252 self.handlers.len()
253 }
254
255 pub fn match_uri(&self, uri: &str) -> Option<&dyn ResourceHandler> {
257 self.handlers
258 .iter()
259 .find(|registered| registered.handler.matches(uri))
260 .map(|registered| registered.handler.as_ref())
261 }
262
263 pub fn handler_id_for_uri(&self, uri: &str) -> Option<u16> {
265 self.handlers
266 .iter()
267 .find(|registered| registered.handler.matches(uri))
268 .map(|registered| registered.id)
269 }
270
271 pub fn handler_ids(&self) -> Vec<u16> {
273 self.handlers
274 .iter()
275 .map(|registered| registered.id)
276 .collect()
277 }
278
279 pub fn allowed_uri_templates<F>(&self, mut allow_handler: F) -> Vec<String>
281 where
282 F: FnMut(u16) -> bool,
283 {
284 self.handlers
285 .iter()
286 .filter(|registered| allow_handler(registered.id))
287 .map(|registered| registered.handler.uri_template().to_string())
288 .collect()
289 }
290
291 pub fn any_allowed_supports_subscribe<F>(&self, mut allow_handler: F) -> bool
293 where
294 F: FnMut(u16) -> bool,
295 {
296 self.handlers.iter().any(|registered| {
297 allow_handler(registered.id) && registered.handler.supports_subscribe()
298 })
299 }
300
301 pub fn list_all(&self, cursor: Option<&str>) -> Result<ResourceList, ResourceError> {
303 self.list_allowed(cursor, |_| true)
304 }
305
306 pub fn list_allowed<F>(
308 &self,
309 cursor: Option<&str>,
310 mut allow_handler: F,
311 ) -> Result<ResourceList, ResourceError>
312 where
313 F: FnMut(u16) -> bool,
314 {
315 let mut all_resources = Vec::new();
316
317 for registered in &self.handlers {
318 if !allow_handler(registered.id) {
319 continue;
320 }
321 match registered.handler.list(cursor) {
322 Ok(list) => all_resources.extend(list.resources),
323 Err(e) => return Err(e),
324 }
325 }
326
327 Ok(ResourceList {
328 resources: all_resources,
329 next_cursor: None, })
331 }
332
333 pub fn read(&self, uri: &str) -> Result<ResourceContent, ResourceError> {
335 let handler = self
336 .match_uri(uri)
337 .ok_or_else(|| ResourceError::NotFound(uri.to_string()))?;
338 handler.read(uri)
339 }
340
341 pub fn ensure_subscribable(&self, uri: &str) -> Result<(), ResourceError> {
343 let handler = self
344 .match_uri(uri)
345 .ok_or_else(|| ResourceError::NotFound(uri.to_string()))?;
346
347 if !handler.supports_subscribe() {
348 return Err(ResourceError::SubscriptionNotSupported);
349 }
350
351 handler.read(uri).map(|_| ())
352 }
353
354 pub async fn subscribe<F>(
356 &self,
357 uri: &str,
358 callback: F,
359 ) -> Result<SubscriptionId, ResourceError>
360 where
361 F: Fn(&str) + Send + Sync + 'static,
362 {
363 let handler = self
365 .match_uri(uri)
366 .ok_or_else(|| ResourceError::NotFound(uri.to_string()))?;
367
368 if !handler.supports_subscribe() {
369 return Err(ResourceError::SubscriptionNotSupported);
370 }
371
372 let id = SubscriptionId(self.subscription_counter.fetch_add(1, Ordering::SeqCst));
373
374 {
376 let mut subs = self.subscriptions.write().await;
377 subs.entry(uri.to_string()).or_default().push(id);
378 }
379
380 {
382 let mut callbacks = self.callbacks.write().await;
383 callbacks.insert(id, Arc::new(callback));
384 }
385
386 Ok(id)
387 }
388
389 pub async fn unsubscribe(&self, id: SubscriptionId) -> bool {
391 let removed = {
393 let mut callbacks = self.callbacks.write().await;
394 callbacks.remove(&id).is_some()
395 };
396
397 if removed {
398 let mut subs = self.subscriptions.write().await;
400 for ids in subs.values_mut() {
401 ids.retain(|&sub_id| sub_id != id);
402 }
403 }
404
405 removed
406 }
407
408 pub async fn notify_change(&self, uri: &str) {
410 let callbacks_to_call: Vec<Arc<dyn Fn(&str) + Send + Sync>> = {
411 let subs = self.subscriptions.read().await;
412 let callbacks = self.callbacks.read().await;
413
414 subs.get(uri)
415 .map(|ids| {
416 ids.iter()
417 .filter_map(|id| callbacks.get(id).cloned())
418 .collect()
419 })
420 .unwrap_or_default()
421 };
422
423 for callback in callbacks_to_call {
424 callback(uri);
425 }
426 }
427
428 pub async fn subscription_count(&self, uri: &str) -> usize {
430 self.subscriptions
431 .read()
432 .await
433 .get(uri)
434 .map(|ids| ids.len())
435 .unwrap_or(0)
436 }
437}
438
439pub struct MemoryResourceHandler {
441 template: String,
442 resources: HashMap<String, ResourceContent>,
443 supports_subscribe: bool,
444}
445
446impl MemoryResourceHandler {
447 pub fn new(template: impl Into<String>) -> Self {
449 Self {
450 template: template.into(),
451 resources: HashMap::new(),
452 supports_subscribe: false,
453 }
454 }
455
456 pub fn with_subscriptions(mut self) -> Self {
458 self.supports_subscribe = true;
459 self
460 }
461
462 pub fn add_resource(&mut self, uri: impl Into<String>, content: ResourceContent) {
464 self.resources.insert(uri.into(), content);
465 }
466}
467
468impl ResourceHandler for MemoryResourceHandler {
469 fn uri_template(&self) -> &str {
470 &self.template
471 }
472
473 fn list(&self, _cursor: Option<&str>) -> Result<ResourceList, ResourceError> {
474 let resources: Vec<ResourceInfo> = self
475 .resources
476 .iter()
477 .map(|(uri, content)| {
478 let (mime_type, name) = match content {
479 ResourceContent::Text { mime_type, .. } => (mime_type.clone(), uri.clone()),
480 ResourceContent::Blob { mime_type, .. } => (mime_type.clone(), uri.clone()),
481 };
482 ResourceInfo::new(uri, name).with_mime_type(mime_type)
483 })
484 .collect();
485
486 Ok(ResourceList {
487 resources,
488 next_cursor: None,
489 })
490 }
491
492 fn read(&self, uri: &str) -> Result<ResourceContent, ResourceError> {
493 self.resources
494 .get(uri)
495 .cloned()
496 .ok_or_else(|| ResourceError::NotFound(uri.to_string()))
497 }
498
499 fn supports_subscribe(&self) -> bool {
500 self.supports_subscribe
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507
508 #[test]
509 fn test_uri_matches_template() {
510 assert!(uri_matches_template(
511 "file:///path/to/file.txt",
512 "file:///{path}"
513 ));
514 assert!(uri_matches_template("file:///a.txt", "file:///{path}"));
515 assert!(uri_matches_template(
516 "http://example.com/api/users/123",
517 "http://example.com/api/users/{id}"
518 ));
519 assert!(!uri_matches_template("ftp://example.com", "http://{host}"));
520 }
521
522 #[test]
523 fn test_resource_content_text() {
524 let content = ResourceContent::text("file:///test.txt", "text/plain", "Hello");
525 assert_eq!(content.uri(), "file:///test.txt");
526 }
527
528 #[test]
529 fn test_resource_info() {
530 let info = ResourceInfo::new("file:///test.txt", "Test File")
531 .with_description("A test file")
532 .with_mime_type("text/plain");
533
534 assert_eq!(info.uri, "file:///test.txt");
535 assert_eq!(info.name, "Test File");
536 assert_eq!(info.description, Some("A test file".to_string()));
537 assert_eq!(info.mime_type, Some("text/plain".to_string()));
538 }
539
540 #[test]
541 fn test_memory_resource_handler() {
542 let mut handler = MemoryResourceHandler::new("file:///{path}");
543 handler.add_resource(
544 "file:///test.txt",
545 ResourceContent::text("file:///test.txt", "text/plain", "Hello"),
546 );
547
548 assert!(handler.matches("file:///test.txt"));
549 assert!(handler.matches("file:///other.txt"));
550
551 let list = handler.list(None).unwrap();
552 assert_eq!(list.resources.len(), 1);
553
554 let content = handler.read("file:///test.txt").unwrap();
555 assert_eq!(content.uri(), "file:///test.txt");
556 }
557
558 #[tokio::test]
559 async fn test_resource_registry() {
560 let mut registry = ResourceRegistry::new();
561
562 let mut handler = MemoryResourceHandler::new("file:///{path}");
563 handler.add_resource(
564 "file:///test.txt",
565 ResourceContent::text("file:///test.txt", "text/plain", "Hello"),
566 );
567 registry.register(handler).unwrap();
568
569 assert_eq!(registry.handler_count(), 1);
570
571 let content = registry.read("file:///test.txt").unwrap();
572 assert_eq!(content.uri(), "file:///test.txt");
573
574 let result = registry.read("file:///nonexistent.txt");
575 assert!(result.is_err());
576 }
577
578 #[tokio::test]
579 async fn test_resource_subscriptions() {
580 let mut registry = ResourceRegistry::new();
581
582 let handler = MemoryResourceHandler::new("file:///{path}").with_subscriptions();
583 registry.register(handler).unwrap();
584
585 let notified = Arc::new(std::sync::atomic::AtomicBool::new(false));
587 let notified_clone = Arc::clone(¬ified);
588
589 let sub_id = registry
590 .subscribe("file:///test.txt", move |_uri| {
591 notified_clone.store(true, Ordering::SeqCst);
592 })
593 .await
594 .unwrap();
595
596 assert_eq!(registry.subscription_count("file:///test.txt").await, 1);
597
598 registry.notify_change("file:///test.txt").await;
600 assert!(notified.load(Ordering::SeqCst));
601
602 assert!(registry.unsubscribe(sub_id).await);
604 assert_eq!(registry.subscription_count("file:///test.txt").await, 0);
605 }
606}