descry_tool_core/
registry.rs1use inventory::collect;
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::Arc;
9
10use crate::context::ToolContext;
11use crate::error::ToolError;
12
13pub struct ToolMeta {
17 pub name: &'static str,
19
20 pub description: &'static str,
22
23 pub call: fn(Arc<ToolContext>, serde_json::Value) -> Pin<Box<dyn Future<Output = Result<serde_json::Value, ToolError>> + Send>>,
27
28 pub schema: fn() -> &'static serde_json::Value,
30
31 pub examples: fn() -> &'static [(&'static str, &'static str)],
33}
34
35collect!(ToolMeta);
37
38pub fn all_tools() -> impl Iterator<Item = &'static ToolMeta> {
49 inventory::iter::<ToolMeta>.into_iter()
50}
51
52pub fn find_tool(name: &str) -> Option<&'static ToolMeta> {
61 all_tools().find(|meta| meta.name == name)
62}
63
64pub async fn call_tool(
79 name: &str,
80 params: serde_json::Value,
81 ctx: Arc<ToolContext>,
82) -> Result<serde_json::Value, ToolError> {
83 let meta = find_tool(name).ok_or_else(|| ToolError::not_found(name))?;
84 (meta.call)(ctx, params).await
85}
86
87pub fn get_tool_schema(name: &str) -> Option<&'static serde_json::Value> {
89 find_tool(name).map(|meta| (meta.schema)())
90}
91
92pub fn get_tool_examples(name: &str) -> Option<&'static [(&'static str, &'static str)]> {
94 find_tool(name).map(|meta| (meta.examples)())
95}
96
97pub fn tool_exists(name: &str) -> bool {
99 find_tool(name).is_some()
100}
101
102pub fn tool_count() -> usize {
104 all_tools().count()
105}
106
107pub fn tool_names() -> Vec<&'static str> {
109 all_tools().map(|meta| meta.name).collect()
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::Tool;
116 use serde::{Deserialize, Serialize};
117 use schemars::JsonSchema;
118
119 #[derive(Deserialize, JsonSchema)]
120 struct TestParams {
121 value: i32,
122 }
123
124 #[derive(Serialize, JsonSchema)]
125 struct TestOutput {
126 result: i32,
127 }
128
129 struct TestTool;
130
131 impl Tool for TestTool {
132 type Params = TestParams;
133 type Output = TestOutput;
134
135 const NAME: &'static str = "test_tool_v2";
136 const DESCRIPTION: &'static str = "Test tool for v2";
137
138 async fn call(
139 _ctx: Arc<ToolContext>,
140 params: Self::Params,
141 ) -> Result<Self::Output, ToolError> {
142 Ok(TestOutput {
143 result: params.value * 2,
144 })
145 }
146 }
147
148 inventory::submit! {
150 ToolMeta {
151 name: TestTool::NAME,
152 description: TestTool::DESCRIPTION,
153 call: |ctx, params| {
154 Box::pin(async move {
155 let params: TestParams = serde_json::from_value(params)?;
156 let result = <TestTool as Tool>::call(ctx, params).await?;
157 Ok(serde_json::to_value(result)?)
158 })
159 },
160 schema: || <TestTool as Tool>::schema(),
161 examples: || <TestTool as Tool>::EXAMPLES,
162 }
163 }
164
165 #[test]
166 fn test_find_tool() {
167 let tool = find_tool("test_tool_v2");
168 assert!(tool.is_some());
169 assert_eq!(tool.unwrap().name, "test_tool_v2");
170 }
171
172 #[test]
173 fn test_tool_exists() {
174 assert!(tool_exists("test_tool_v2"));
175 assert!(!tool_exists("nonexistent"));
176 }
177
178 #[test]
179 fn test_tool_count() {
180 assert!(tool_count() > 0);
181 }
182
183 #[test]
184 fn test_tool_names() {
185 let names = tool_names();
186 assert!(names.contains(&"test_tool_v2"));
187 }
188
189 #[tokio::test]
190 async fn test_call_tool() {
191 let ctx = Arc::new(ToolContext::new());
192 let params = serde_json::json!({"value": 5});
193 let result = call_tool("test_tool_v2", params, ctx).await.unwrap();
194 assert_eq!(result["result"], 10);
195 }
196
197 #[tokio::test]
198 async fn test_call_nonexistent_tool() {
199 let ctx = Arc::new(ToolContext::new());
200 let result = call_tool("nonexistent", serde_json::json!({}), ctx).await;
201 assert!(result.is_err());
202 }
203}