llm_tool/
rust_resource.rs1use alloc::{
4 borrow::Cow,
5 boxed::Box,
6 format,
7 string::{String, ToString},
8};
9use core::{future::Future, pin::Pin};
10
11use super::types::{ResourceDefinition, ResourceOutput, ToolError};
12
13pub trait RustResource: Send + Sync {
15 type Params: serde::de::DeserializeOwned + Send;
17
18 const URI_TEMPLATE: &'static str;
20
21 const NAME: &'static str;
23
24 const DESCRIPTION: &'static str;
26
27 const MIME_TYPE: Option<&'static str>;
29
30 fn description(&self) -> Cow<'static, str> {
32 Cow::Borrowed(Self::DESCRIPTION)
33 }
34
35 fn read(
37 &self,
38 uri: &str,
39 params: Self::Params,
40 ) -> impl Future<Output = Result<ResourceOutput, ToolError>> + Send;
41}
42
43pub fn definition_of_resource<T: RustResource>(resource: &T) -> ResourceDefinition {
45 ResourceDefinition {
46 uri_template: T::URI_TEMPLATE.to_string(),
47 name: T::NAME.to_string(),
48 description: resource.description().into_owned(),
49 mime_type: T::MIME_TYPE.map(ToString::to_string),
50 }
51}
52
53#[must_use]
58pub fn match_uri_template(
59 template: &str,
60 uri: &str,
61) -> Option<alloc::collections::BTreeMap<String, String>> {
62 let mut map = alloc::collections::BTreeMap::new();
63 let mut t_rem = template;
64 let mut u_rem = uri;
65
66 while let Some(start_idx) = t_rem.find('{') {
67 let prefix = &t_rem[..start_idx];
68 if !u_rem.starts_with(prefix) {
69 return None;
70 }
71 u_rem = &u_rem[prefix.len()..];
72 t_rem = &t_rem[start_idx + 1..];
73
74 let end_idx = t_rem.find('}')?;
75 let var_name = &t_rem[..end_idx];
76 t_rem = &t_rem[end_idx + 1..];
77
78 let val_str = if t_rem.is_empty() {
79 let val = u_rem;
80 u_rem = "";
81 val
82 } else {
83 let next_char = t_rem.chars().next()?;
84 let val_end = u_rem.find(next_char)?;
85 let val = &u_rem[..val_end];
86 u_rem = &u_rem[val_end..];
87 val
88 };
89
90 map.insert(var_name.to_string(), val_str.to_string());
91 }
92
93 if t_rem == u_rem { Some(map) } else { None }
94}
95
96pub type BoxResourceFuture<'a> =
98 Pin<Box<dyn Future<Output = Result<ResourceOutput, ToolError>> + Send + 'a>>;
99
100pub trait ErasedResource: Send + Sync {
102 fn definition(&self) -> ResourceDefinition;
104
105 fn read_erased<'a>(&'a self, uri: &'a str) -> Option<BoxResourceFuture<'a>>;
107}
108
109impl<T: RustResource> ErasedResource for T {
110 fn definition(&self) -> ResourceDefinition {
111 definition_of_resource(self)
112 }
113
114 fn read_erased<'a>(&'a self, uri: &'a str) -> Option<BoxResourceFuture<'a>> {
115 let params_map = match_uri_template(T::URI_TEMPLATE, uri)?;
116 Some(Box::pin(async move {
117 let deserializer = serde::de::value::MapDeserializer::new(
118 params_map
119 .into_iter()
120 .map(|(k, v)| (k, serde::de::value::StringDeserializer::new(v))),
121 );
122 let params: T::Params = serde::de::Deserialize::deserialize(deserializer).map_err(
123 |e: serde::de::value::Error| {
124 ToolError::new(format!(
125 "Failed to deserialize resource parameters from URI variables: {e}"
126 ))
127 },
128 )?;
129 self.read(uri, params).await
130 }))
131 }
132}