1use crate::{Error, Result};
2use pforge_config::{HandlerRef, ResourceDef, ResourceOperation};
3use regex::Regex;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[async_trait::async_trait]
9pub trait ResourceHandler: Send + Sync {
10 async fn read(&self, uri: &str, params: HashMap<String, String>) -> Result<Vec<u8>>;
12
13 async fn write(&self, uri: &str, params: HashMap<String, String>, content: Vec<u8>) -> Result<()> {
15 let _ = (uri, params, content);
16 Err(Error::Handler("Write operation not supported".to_string()))
17 }
18
19 async fn subscribe(&self, uri: &str, params: HashMap<String, String>) -> Result<()> {
21 let _ = (uri, params);
22 Err(Error::Handler("Subscribe operation not supported".to_string()))
23 }
24}
25
26pub struct ResourceManager {
28 resources: Vec<ResourceEntry>,
29}
30
31struct ResourceEntry {
32 uri_template: String,
33 pattern: Regex,
34 param_names: Vec<String>,
35 supports: Vec<ResourceOperation>,
36 handler: Arc<dyn ResourceHandler>,
37}
38
39impl ResourceManager {
40 pub fn new() -> Self {
41 Self {
42 resources: Vec::new(),
43 }
44 }
45
46 pub fn register(
48 &mut self,
49 def: ResourceDef,
50 handler: Arc<dyn ResourceHandler>,
51 ) -> Result<()> {
52 let (pattern, param_names) = Self::compile_uri_template(&def.uri_template)?;
53
54 self.resources.push(ResourceEntry {
55 uri_template: def.uri_template,
56 pattern,
57 param_names,
58 supports: def.supports,
59 handler,
60 });
61
62 Ok(())
63 }
64
65 fn match_uri(&self, uri: &str) -> Option<(&ResourceEntry, HashMap<String, String>)> {
67 for entry in &self.resources {
68 if let Some(captures) = entry.pattern.captures(uri) {
69 let mut params = HashMap::new();
70
71 for (i, name) in entry.param_names.iter().enumerate() {
72 if let Some(value) = captures.get(i + 1) {
73 params.insert(name.clone(), value.as_str().to_string());
74 }
75 }
76
77 return Some((entry, params));
78 }
79 }
80
81 None
82 }
83
84 pub async fn read(&self, uri: &str) -> Result<Vec<u8>> {
86 let (entry, params) = self
87 .match_uri(uri)
88 .ok_or_else(|| Error::Handler(format!("No resource matches URI: {}", uri)))?;
89
90 if !entry.supports.contains(&ResourceOperation::Read) {
91 return Err(Error::Handler(format!(
92 "Resource {} does not support read operation",
93 entry.uri_template
94 )));
95 }
96
97 entry.handler.read(uri, params).await
98 }
99
100 pub async fn write(&self, uri: &str, content: Vec<u8>) -> Result<()> {
102 let (entry, params) = self
103 .match_uri(uri)
104 .ok_or_else(|| Error::Handler(format!("No resource matches URI: {}", uri)))?;
105
106 if !entry.supports.contains(&ResourceOperation::Write) {
107 return Err(Error::Handler(format!(
108 "Resource {} does not support write operation",
109 entry.uri_template
110 )));
111 }
112
113 entry.handler.write(uri, params, content).await
114 }
115
116 pub async fn subscribe(&self, uri: &str) -> Result<()> {
118 let (entry, params) = self
119 .match_uri(uri)
120 .ok_or_else(|| Error::Handler(format!("No resource matches URI: {}", uri)))?;
121
122 if !entry.supports.contains(&ResourceOperation::Subscribe) {
123 return Err(Error::Handler(format!(
124 "Resource {} does not support subscribe operation",
125 entry.uri_template
126 )));
127 }
128
129 entry.handler.subscribe(uri, params).await
130 }
131
132 fn compile_uri_template(template: &str) -> Result<(Regex, Vec<String>)> {
136 let mut pattern = String::from("^");
137 let mut param_names = Vec::new();
138 let mut chars = template.chars().peekable();
139
140 while let Some(ch) = chars.next() {
141 if ch == '{' {
142 let mut param_name = String::new();
144 while let Some(&next_ch) = chars.peek() {
145 if next_ch == '}' {
146 chars.next(); break;
148 }
149 param_name.push(chars.next().unwrap());
150 }
151
152 if param_name.is_empty() {
153 return Err(Error::Handler("Empty parameter name in URI template".to_string()));
154 }
155
156 param_names.push(param_name);
157
158 if chars.peek() == Some(&'/') {
162 pattern.push_str("([^/]+)"); } else {
164 pattern.push_str("(.+)"); }
166 } else {
167 if ".*+?^$[](){}|\\".contains(ch) {
169 pattern.push('\\');
170 }
171 pattern.push(ch);
172 }
173 }
174
175 pattern.push('$');
176
177 let regex = Regex::new(&pattern)
178 .map_err(|e| Error::Handler(format!("Invalid URI template regex: {}", e)))?;
179
180 Ok((regex, param_names))
181 }
182
183 pub fn list_templates(&self) -> Vec<&str> {
185 self.resources.iter().map(|e| e.uri_template.as_str()).collect()
186 }
187}
188
189impl Default for ResourceManager {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 struct TestResourceHandler {
200 read_response: Vec<u8>,
201 }
202
203 #[async_trait::async_trait]
204 impl ResourceHandler for TestResourceHandler {
205 async fn read(&self, _uri: &str, _params: HashMap<String, String>) -> Result<Vec<u8>> {
206 Ok(self.read_response.clone())
207 }
208
209 async fn write(&self, _uri: &str, _params: HashMap<String, String>, _content: Vec<u8>) -> Result<()> {
210 Ok(())
211 }
212 }
213
214 #[test]
215 fn test_uri_template_compilation() {
216 let (pattern, params) = ResourceManager::compile_uri_template("file:///{path}").unwrap();
217 assert_eq!(params, vec!["path"]);
218
219 let captures = pattern.captures("file:///home/user/test.txt").unwrap();
220 assert_eq!(captures.get(1).unwrap().as_str(), "home/user/test.txt");
221 }
222
223 #[test]
224 fn test_uri_template_multiple_params() {
225 let (pattern, params) = ResourceManager::compile_uri_template("api://{service}/{resource}").unwrap();
226 assert_eq!(params, vec!["service", "resource"]);
227
228 let captures = pattern.captures("api://users/profile").unwrap();
229 assert_eq!(captures.get(1).unwrap().as_str(), "users");
230 assert_eq!(captures.get(2).unwrap().as_str(), "profile");
231 }
232
233 #[tokio::test]
234 async fn test_resource_registration_and_matching() {
235 let mut manager = ResourceManager::new();
236
237 let def = ResourceDef {
238 uri_template: "file:///{path}".to_string(),
239 handler: HandlerRef {
240 path: "test::handler".to_string(),
241 inline: None,
242 },
243 supports: vec![ResourceOperation::Read],
244 };
245
246 let handler = Arc::new(TestResourceHandler {
247 read_response: b"test content".to_vec(),
248 });
249
250 manager.register(def, handler).unwrap();
251
252 let (entry, params) = manager.match_uri("file:///test.txt").unwrap();
253 assert_eq!(entry.uri_template, "file:///{path}");
254 assert_eq!(params.get("path").unwrap(), "test.txt");
255 }
256
257 #[tokio::test]
258 async fn test_resource_read() {
259 let mut manager = ResourceManager::new();
260
261 let def = ResourceDef {
262 uri_template: "file:///{path}".to_string(),
263 handler: HandlerRef {
264 path: "test::handler".to_string(),
265 inline: None,
266 },
267 supports: vec![ResourceOperation::Read],
268 };
269
270 let handler = Arc::new(TestResourceHandler {
271 read_response: b"hello world".to_vec(),
272 });
273
274 manager.register(def, handler).unwrap();
275
276 let content = manager.read("file:///test.txt").await.unwrap();
277 assert_eq!(content, b"hello world");
278 }
279
280 #[tokio::test]
281 async fn test_resource_unsupported_operation() {
282 let mut manager = ResourceManager::new();
283
284 let def = ResourceDef {
285 uri_template: "file:///{path}".to_string(),
286 handler: HandlerRef {
287 path: "test::handler".to_string(),
288 inline: None,
289 },
290 supports: vec![ResourceOperation::Read],
291 };
292
293 let handler = Arc::new(TestResourceHandler {
294 read_response: b"test".to_vec(),
295 });
296
297 manager.register(def, handler).unwrap();
298
299 let result = manager.write("file:///test.txt", b"data".to_vec()).await;
300 assert!(result.is_err());
301 assert!(result.unwrap_err().to_string().contains("does not support write"));
302 }
303}