1use std::{future::Future, pin::Pin, sync::Arc};
4
5use rmcp::model::{ReadResourceRequestParams, ReadResourceResult, Resource, ResourceTemplate};
6
7type ResourceFuture =
8 Pin<Box<dyn Future<Output = Result<ReadResourceResult, rmcp::ErrorData>> + Send>>;
9
10pub struct ResourceEntry {
12 pub resource: Resource,
14 pub(crate) handler: Arc<dyn Fn(ReadResourceRequestParams) -> ResourceFuture + Send + Sync>,
15}
16
17impl ResourceEntry {
18 pub fn new<F, Fut>(resource: Resource, handler: F) -> Self
20 where
21 F: Fn(ReadResourceRequestParams) -> Fut + Send + Sync + 'static,
22 Fut: Future<Output = Result<ReadResourceResult, rmcp::ErrorData>> + Send + 'static,
23 {
24 Self {
25 resource,
26 handler: Arc::new(move |request| Box::pin(handler(request))),
27 }
28 }
29}
30
31impl Clone for ResourceEntry {
32 fn clone(&self) -> Self {
33 Self {
34 resource: self.resource.clone(),
35 handler: Arc::clone(&self.handler),
36 }
37 }
38}
39
40pub struct ResourceTemplateEntry {
42 pub resource_template: ResourceTemplate,
44 pub(crate) handler: Arc<dyn Fn(ReadResourceRequestParams) -> ResourceFuture + Send + Sync>,
45}
46
47impl ResourceTemplateEntry {
48 pub fn new<F, Fut>(resource_template: ResourceTemplate, handler: F) -> Self
50 where
51 F: Fn(ReadResourceRequestParams) -> Fut + Send + Sync + 'static,
52 Fut: Future<Output = Result<ReadResourceResult, rmcp::ErrorData>> + Send + 'static,
53 {
54 Self {
55 resource_template,
56 handler: Arc::new(move |request| Box::pin(handler(request))),
57 }
58 }
59}
60
61impl Clone for ResourceTemplateEntry {
62 fn clone(&self) -> Self {
63 Self {
64 resource_template: self.resource_template.clone(),
65 handler: Arc::clone(&self.handler),
66 }
67 }
68}
69
70pub(crate) fn resource_uri(resource: &Resource) -> Option<String> {
71 serde_json::to_value(resource).ok().and_then(|value| {
72 value
73 .get("uri")
74 .and_then(|uri| uri.as_str())
75 .map(str::to_string)
76 })
77}
78
79pub(crate) fn resource_template_uri(resource_template: &ResourceTemplate) -> Option<String> {
80 serde_json::to_value(resource_template)
81 .ok()
82 .and_then(|value| {
83 value
84 .get("uriTemplate")
85 .and_then(|uri| uri.as_str())
86 .map(str::to_string)
87 })
88}
89
90pub(crate) fn resource_template_matches(template: &str, uri: &str) -> bool {
91 let literals = template_literals(template);
92 if literals.is_empty() {
93 return template == uri;
94 }
95 let Some(first) = literals.first() else {
96 return false;
97 };
98 if !uri.starts_with(first) {
99 return false;
100 }
101 let mut index = first.len();
102 for literal in literals.iter().skip(1) {
103 if literal.is_empty() {
104 continue;
105 }
106 let Some(found) = uri[index..].find(literal) else {
107 return false;
108 };
109 index += found + literal.len();
110 }
111 if !template.ends_with('}')
112 && let Some(last) = literals.last()
113 && !last.is_empty()
114 {
115 return uri.ends_with(last);
116 }
117 true
118}
119
120fn template_literals(template: &str) -> Vec<String> {
121 let mut literals = Vec::new();
122 let mut current = String::new();
123 let mut depth = 0;
124 for ch in template.chars() {
125 match ch {
126 '{' if depth == 0 => {
127 literals.push(std::mem::take(&mut current));
128 depth += 1;
129 }
130 '{' => depth += 1,
131 '}' if depth > 0 => depth -= 1,
132 _ if depth == 0 => current.push(ch),
133 _ => {}
134 }
135 }
136 literals.push(current);
137 literals
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn resource_templates_match_literals_in_order() {
146 assert!(resource_template_matches(
147 "file:///{workspace}/src/{path}",
148 "file:///rskit/src/lib.rs"
149 ));
150 assert!(resource_template_matches("urn:item/{id}", "urn:item/123"));
151 assert!(!resource_template_matches(
152 "file:///{workspace}/src/{path}",
153 "file:///rskit/tests/lib.rs"
154 ));
155 assert!(!resource_template_matches(
156 "urn:item/{id}/tail",
157 "urn:item/123/other"
158 ));
159 assert!(resource_template_matches("literal", "literal"));
160 assert!(!resource_template_matches("literal", "literal/extra"));
161 }
162
163 #[test]
164 fn nested_template_braces_are_ignored_as_placeholders() {
165 assert_eq!(
166 template_literals("prefix/{outer{inner}}/suffix"),
167 vec!["prefix/".to_string(), "/suffix".to_string()]
168 );
169 }
170}