1use std::borrow::Cow;
8use std::future::Future;
9use std::pin::Pin;
10use std::time::Duration;
11
12use base64::Engine as _;
13use serde::Serialize;
14
15use crate::completion::{CompleteFn, Completer};
16use crate::context::Ctx;
17use crate::spec::{self, BlobResourceContents, CacheScope, TextResourceContents};
18use crate::tool::{IntoToolError, Json, StateRequirement, ToolError};
19use crate::uri_template::UriVars;
20
21pub type ResourceContents = spec::ReadResourceResultContentsItem;
23
24pub type ResourceFuture =
26 Pin<Box<dyn Future<Output = Result<Vec<ResourceContents>, ToolError>> + Send + 'static>>;
27
28pub type ReadFn = fn(uri: &str, vars: &UriVars, ctx: Ctx) -> Result<ResourceFuture, ToolError>;
31
32pub trait Resource: 'static {
35 fn definition() -> ResourceDef;
37}
38
39#[derive(Debug, Clone)]
40pub(crate) enum ResourceKind {
41 Static(spec::Resource),
42 Template(spec::ResourceTemplate),
43}
44
45#[derive(Debug, Clone)]
47pub struct ResourceDef {
48 pub(crate) kind: ResourceKind,
49 pub(crate) read: ReadFn,
50 pub(crate) required_state: Vec<StateRequirement>,
51 pub(crate) ttl: Duration,
52 pub(crate) cache_scope: CacheScope,
53 pub(crate) completers: Vec<Completer>,
54 pub(crate) scopes: Vec<String>,
55 pub(crate) app: Option<crate::apps::AppUi>,
56}
57
58impl ResourceDef {
59 fn with_kind(kind: ResourceKind, read: ReadFn) -> Self {
60 Self {
61 kind,
62 read,
63 required_state: Vec::new(),
64 ttl: Duration::ZERO,
65 cache_scope: CacheScope::Private,
66 completers: Vec::new(),
67 scopes: Vec::new(),
68 app: None,
69 }
70 }
71
72 #[must_use]
76 pub fn app(mut self, ui: crate::apps::AppUi) -> Self {
77 if let ResourceKind::Static(resource) = &mut self.kind {
78 resource.mime_type = Some(crate::apps::MIME_TYPE.to_owned());
79 resource
80 .meta
81 .get_or_insert_with(Default::default)
82 .insert("ui".to_owned(), ui.to_meta());
83 }
84 self.app = Some(ui);
85 self
86 }
87
88 #[must_use]
90 pub fn is_app(&self) -> bool {
91 self.app.is_some()
92 }
93
94 #[must_use]
98 pub fn scopes<I, S>(mut self, scopes: I) -> Self
99 where
100 I: IntoIterator<Item = S>,
101 S: Into<String>,
102 {
103 self.scopes = scopes.into_iter().map(Into::into).collect();
104 self
105 }
106
107 #[must_use]
109 pub fn required_scopes(&self) -> &[String] {
110 &self.scopes
111 }
112
113 #[must_use]
115 pub fn new(resource: spec::Resource, read: ReadFn) -> Self {
116 Self::with_kind(ResourceKind::Static(resource), read)
117 }
118
119 #[must_use]
123 pub fn template(template: spec::ResourceTemplate, read: ReadFn) -> Self {
124 Self::with_kind(ResourceKind::Template(template), read)
125 }
126
127 #[must_use]
129 pub fn require_state<T: Send + Sync + 'static>(mut self) -> Self {
130 self.required_state.push(StateRequirement::of::<T>());
131 self
132 }
133
134 #[must_use]
137 pub fn ttl(mut self, ttl: Duration) -> Self {
138 self.ttl = ttl;
139 self
140 }
141
142 #[must_use]
145 pub fn cache_scope(mut self, scope: CacheScope) -> Self {
146 self.cache_scope = scope;
147 self
148 }
149
150 #[must_use]
152 pub fn complete(mut self, variable: impl Into<String>, complete: CompleteFn) -> Self {
153 self.completers.push(Completer {
154 argument: variable.into(),
155 complete,
156 });
157 self
158 }
159
160 #[must_use]
162 pub fn uri(&self) -> &str {
163 match &self.kind {
164 ResourceKind::Static(r) => &r.uri,
165 ResourceKind::Template(t) => &t.uri_template,
166 }
167 }
168
169 #[must_use]
171 pub fn is_template(&self) -> bool {
172 matches!(self.kind, ResourceKind::Template(_))
173 }
174}
175
176pub trait IntoResourceContents {
189 fn into_contents(
196 self,
197 uri: &str,
198 mime_type: Option<&str>,
199 ) -> Result<Vec<ResourceContents>, ToolError>;
200}
201
202fn text(uri: &str, mime_type: Option<&str>, text: String) -> Vec<ResourceContents> {
203 let mut contents = TextResourceContents::new(text, uri.to_owned());
204 contents.mime_type = mime_type.map(str::to_owned);
205 vec![ResourceContents::Text(contents)]
206}
207
208impl IntoResourceContents for String {
209 fn into_contents(
210 self,
211 uri: &str,
212 mime_type: Option<&str>,
213 ) -> Result<Vec<ResourceContents>, ToolError> {
214 Ok(text(uri, mime_type, self))
215 }
216}
217
218impl IntoResourceContents for &'static str {
219 fn into_contents(
220 self,
221 uri: &str,
222 mime_type: Option<&str>,
223 ) -> Result<Vec<ResourceContents>, ToolError> {
224 Ok(text(uri, mime_type, self.to_owned()))
225 }
226}
227
228impl IntoResourceContents for Cow<'static, str> {
229 fn into_contents(
230 self,
231 uri: &str,
232 mime_type: Option<&str>,
233 ) -> Result<Vec<ResourceContents>, ToolError> {
234 Ok(text(uri, mime_type, self.into_owned()))
235 }
236}
237
238impl IntoResourceContents for Vec<u8> {
239 fn into_contents(
240 self,
241 uri: &str,
242 mime_type: Option<&str>,
243 ) -> Result<Vec<ResourceContents>, ToolError> {
244 let blob = base64::engine::general_purpose::STANDARD.encode(self);
245 let mut contents = BlobResourceContents::new(blob, uri.to_owned());
246 contents.mime_type = mime_type.map(str::to_owned);
247 Ok(vec![ResourceContents::Blob(contents)])
248 }
249}
250
251impl<T: Serialize> IntoResourceContents for Json<T> {
252 fn into_contents(
253 self,
254 uri: &str,
255 mime_type: Option<&str>,
256 ) -> Result<Vec<ResourceContents>, ToolError> {
257 let json = serde_json::to_string(&self.0).map_err(ToolError::internal)?;
258 Ok(text(
259 uri,
260 Some(mime_type.unwrap_or("application/json")),
261 json,
262 ))
263 }
264}
265
266impl IntoResourceContents for TextResourceContents {
267 fn into_contents(self, _: &str, _: Option<&str>) -> Result<Vec<ResourceContents>, ToolError> {
268 Ok(vec![ResourceContents::Text(self)])
269 }
270}
271
272impl IntoResourceContents for BlobResourceContents {
273 fn into_contents(self, _: &str, _: Option<&str>) -> Result<Vec<ResourceContents>, ToolError> {
274 Ok(vec![ResourceContents::Blob(self)])
275 }
276}
277
278impl IntoResourceContents for ResourceContents {
279 fn into_contents(self, _: &str, _: Option<&str>) -> Result<Vec<ResourceContents>, ToolError> {
280 Ok(vec![self])
281 }
282}
283
284impl IntoResourceContents for Vec<ResourceContents> {
285 fn into_contents(self, _: &str, _: Option<&str>) -> Result<Vec<ResourceContents>, ToolError> {
286 Ok(self)
287 }
288}
289
290impl<T: IntoResourceContents, E: IntoToolError> IntoResourceContents for Result<T, E> {
291 fn into_contents(
292 self,
293 uri: &str,
294 mime_type: Option<&str>,
295 ) -> Result<Vec<ResourceContents>, ToolError> {
296 match self {
297 Ok(value) => value.into_contents(uri, mime_type),
298 Err(error) => Err(error.into_tool_error()),
299 }
300 }
301}
302
303pub fn parse_uri_var<T: std::str::FromStr>(vars: &UriVars, name: &str) -> Result<T, ToolError> {
310 vars.get(name)
311 .and_then(|v| v.parse().ok())
312 .ok_or_else(ToolError::not_found)
313}
314
315pub fn parse_optional_uri_var<T: std::str::FromStr>(
321 vars: &UriVars,
322 name: &str,
323) -> Result<Option<T>, ToolError> {
324 vars.get(name)
325 .map(|v| v.parse().map_err(|_| ToolError::not_found()))
326 .transpose()
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn contents_conversions() {
335 let t = "hi".into_contents("a://b", Some("text/plain")).unwrap();
336 assert_eq!(
337 serde_json::to_value(&t).unwrap(),
338 serde_json::json!([{ "uri": "a://b", "mimeType": "text/plain", "text": "hi" }])
339 );
340 let b = vec![1u8, 2, 3].into_contents("a://b", None).unwrap();
341 assert_eq!(
342 serde_json::to_value(&b).unwrap(),
343 serde_json::json!([{ "uri": "a://b", "blob": "AQID" }])
344 );
345 let j = Json(serde_json::json!({ "x": 1 }))
346 .into_contents("a://b", None)
347 .unwrap();
348 assert_eq!(
349 serde_json::to_value(&j).unwrap(),
350 serde_json::json!([{ "uri": "a://b", "mimeType": "application/json", "text": "{\"x\":1}" }])
351 );
352 }
353}