use alloc::{
borrow::Cow,
boxed::Box,
format,
string::{String, ToString},
};
use core::{future::Future, pin::Pin};
use super::types::{ResourceDefinition, ResourceOutput, ToolError};
pub trait RustResource: Send + Sync {
type Params: serde::de::DeserializeOwned + Send;
const URI_TEMPLATE: &'static str;
const NAME: &'static str;
const DESCRIPTION: &'static str;
const MIME_TYPE: Option<&'static str>;
fn description(&self) -> Cow<'static, str> {
Cow::Borrowed(Self::DESCRIPTION)
}
fn read(
&self,
uri: &str,
params: Self::Params,
) -> impl Future<Output = Result<ResourceOutput, ToolError>> + Send;
}
pub fn definition_of_resource<T: RustResource>(resource: &T) -> ResourceDefinition {
ResourceDefinition {
uri_template: T::URI_TEMPLATE.to_string(),
name: T::NAME.to_string(),
description: resource.description().into_owned(),
mime_type: T::MIME_TYPE.map(ToString::to_string),
}
}
#[must_use]
pub fn match_uri_template(
template: &str,
uri: &str,
) -> Option<alloc::collections::BTreeMap<String, String>> {
let mut map = alloc::collections::BTreeMap::new();
let mut t_rem = template;
let mut u_rem = uri;
while let Some(start_idx) = t_rem.find('{') {
let prefix = &t_rem[..start_idx];
if !u_rem.starts_with(prefix) {
return None;
}
u_rem = &u_rem[prefix.len()..];
t_rem = &t_rem[start_idx + 1..];
let end_idx = t_rem.find('}')?;
let var_name = &t_rem[..end_idx];
t_rem = &t_rem[end_idx + 1..];
let val_str = if t_rem.is_empty() {
let val = u_rem;
u_rem = "";
val
} else {
let next_char = t_rem.chars().next()?;
let val_end = u_rem.find(next_char)?;
let val = &u_rem[..val_end];
u_rem = &u_rem[val_end..];
val
};
map.insert(var_name.to_string(), val_str.to_string());
}
if t_rem == u_rem { Some(map) } else { None }
}
pub type BoxResourceFuture<'a> =
Pin<Box<dyn Future<Output = Result<ResourceOutput, ToolError>> + Send + 'a>>;
pub trait ErasedResource: Send + Sync {
fn definition(&self) -> ResourceDefinition;
fn read_erased<'a>(&'a self, uri: &'a str) -> Option<BoxResourceFuture<'a>>;
}
impl<T: RustResource> ErasedResource for T {
fn definition(&self) -> ResourceDefinition {
definition_of_resource(self)
}
fn read_erased<'a>(&'a self, uri: &'a str) -> Option<BoxResourceFuture<'a>> {
let params_map = match_uri_template(T::URI_TEMPLATE, uri)?;
Some(Box::pin(async move {
let deserializer = serde::de::value::MapDeserializer::new(
params_map
.into_iter()
.map(|(k, v)| (k, serde::de::value::StringDeserializer::new(v))),
);
let params: T::Params = serde::de::Deserialize::deserialize(deserializer).map_err(
|e: serde::de::value::Error| {
ToolError::new(format!(
"Failed to deserialize resource parameters from URI variables: {e}"
))
},
)?;
self.read(uri, params).await
}))
}
}