Skip to main content

erio_tools/
lib.rs

1#![doc = include_str!("../README.md")]
2
3//! Erio Tools - Tool trait, registry, and execution for the agent runtime.
4
5mod registry;
6mod result;
7mod schema;
8mod tool;
9
10pub use erio_core::ToolError;
11pub use registry::ToolRegistry;
12pub use result::ToolResult;
13pub use schema::{PropertyType, ToolSchema, ToolSchemaBuilder};
14pub use tool::Tool;
15
16#[cfg(test)]
17mod tests {
18    use super::*;
19    use serde_json::json;
20
21    // === ToolResult Tests ===
22
23    #[test]
24    fn tool_result_success_creates_success_variant() {
25        let result = ToolResult::success("Hello, world!");
26        assert!(result.is_success());
27        assert_eq!(result.content(), Some("Hello, world!"));
28    }
29
30    #[test]
31    fn tool_result_error_creates_error_variant() {
32        let result = ToolResult::error("Something went wrong");
33        assert!(!result.is_success());
34        assert_eq!(result.error_message(), Some("Something went wrong"));
35    }
36
37    #[test]
38    fn tool_result_serializes_success() {
39        let result = ToolResult::success("output");
40        let json = serde_json::to_value(&result).unwrap();
41        assert_eq!(json["success"], true);
42        assert_eq!(json["content"], "output");
43    }
44
45    #[test]
46    fn tool_result_serializes_error() {
47        let result = ToolResult::error("failed");
48        let json = serde_json::to_value(&result).unwrap();
49        assert_eq!(json["success"], false);
50        assert_eq!(json["error"], "failed");
51    }
52
53    // === ToolSchema Tests ===
54
55    #[test]
56    fn tool_schema_builder_creates_schema() {
57        let schema = ToolSchema::builder()
58            .property("command", PropertyType::String, "The command to run", true)
59            .property(
60                "timeout",
61                PropertyType::Integer,
62                "Timeout in seconds",
63                false,
64            )
65            .build();
66
67        assert!(schema.has_property("command"));
68        assert!(schema.has_property("timeout"));
69        assert!(schema.is_required("command"));
70        assert!(!schema.is_required("timeout"));
71    }
72
73    #[test]
74    fn tool_schema_serializes_to_json_schema() {
75        let schema = ToolSchema::builder()
76            .property("name", PropertyType::String, "The name", true)
77            .build();
78
79        let json = schema.to_json_schema();
80        assert_eq!(json["type"], "object");
81        assert!(json["properties"]["name"].is_object());
82        assert_eq!(json["properties"]["name"]["type"], "string");
83        assert_eq!(json["required"], json!(["name"]));
84    }
85
86    #[test]
87    fn tool_schema_empty_has_no_properties() {
88        let schema = ToolSchema::builder().build();
89        assert_eq!(schema.property_count(), 0);
90    }
91
92    // === Tool Trait Tests ===
93
94    struct EchoTool;
95
96    #[async_trait::async_trait]
97    impl Tool for EchoTool {
98        #[allow(clippy::unnecessary_literal_bound)]
99        fn name(&self) -> &str {
100            "echo"
101        }
102
103        #[allow(clippy::unnecessary_literal_bound)]
104        fn description(&self) -> &str {
105            "Echoes the input message"
106        }
107
108        fn schema(&self) -> ToolSchema {
109            ToolSchema::builder()
110                .property("message", PropertyType::String, "Message to echo", true)
111                .build()
112        }
113
114        async fn execute(&self, params: serde_json::Value) -> Result<ToolResult, ToolError> {
115            let message = params["message"]
116                .as_str()
117                .ok_or_else(|| ToolError::InvalidParams("missing 'message' field".into()))?;
118            Ok(ToolResult::success(message))
119        }
120    }
121
122    #[test]
123    fn tool_returns_name() {
124        let tool = EchoTool;
125        assert_eq!(tool.name(), "echo");
126    }
127
128    #[test]
129    fn tool_returns_description() {
130        let tool = EchoTool;
131        assert_eq!(tool.description(), "Echoes the input message");
132    }
133
134    #[test]
135    fn tool_returns_schema() {
136        let tool = EchoTool;
137        let schema = tool.schema();
138        assert!(schema.has_property("message"));
139    }
140
141    #[tokio::test]
142    async fn tool_executes_successfully() {
143        let tool = EchoTool;
144        let params = json!({"message": "Hello"});
145        let result = tool.execute(params).await.unwrap();
146        assert!(result.is_success());
147        assert_eq!(result.content(), Some("Hello"));
148    }
149
150    #[tokio::test]
151    async fn tool_returns_error_for_invalid_params() {
152        let tool = EchoTool;
153        let params = json!({});
154        let result = tool.execute(params).await;
155        assert!(result.is_err());
156        assert!(matches!(result.unwrap_err(), ToolError::InvalidParams(_)));
157    }
158
159    // === ToolRegistry Tests ===
160
161    #[test]
162    fn registry_starts_empty() {
163        let registry = ToolRegistry::new();
164        assert_eq!(registry.len(), 0);
165        assert!(registry.is_empty());
166    }
167
168    #[test]
169    fn registry_registers_tool() {
170        let mut registry = ToolRegistry::new();
171        registry.register(EchoTool);
172        assert_eq!(registry.len(), 1);
173        assert!(!registry.is_empty());
174    }
175
176    #[test]
177    fn registry_gets_tool_by_name() {
178        let mut registry = ToolRegistry::new();
179        registry.register(EchoTool);
180        let tool = registry.get("echo");
181        assert!(tool.is_some());
182        assert_eq!(tool.unwrap().name(), "echo");
183    }
184
185    #[test]
186    fn registry_returns_none_for_unknown_tool() {
187        let registry = ToolRegistry::new();
188        assert!(registry.get("unknown").is_none());
189    }
190
191    #[test]
192    fn registry_lists_all_tools() {
193        let mut registry = ToolRegistry::new();
194        registry.register(EchoTool);
195        let tools = registry.list();
196        assert_eq!(tools.len(), 1);
197        assert_eq!(tools[0], "echo");
198    }
199
200    #[test]
201    fn registry_contains_checks_existence() {
202        let mut registry = ToolRegistry::new();
203        registry.register(EchoTool);
204        assert!(registry.contains("echo"));
205        assert!(!registry.contains("unknown"));
206    }
207
208    #[tokio::test]
209    async fn registry_executes_tool_by_name() {
210        let mut registry = ToolRegistry::new();
211        registry.register(EchoTool);
212        let result = registry.execute("echo", json!({"message": "test"})).await;
213        assert!(result.is_ok());
214        assert_eq!(result.unwrap().content(), Some("test"));
215    }
216
217    #[tokio::test]
218    async fn registry_returns_not_found_for_unknown_tool() {
219        let registry = ToolRegistry::new();
220        let result = registry.execute("unknown", json!({})).await;
221        assert!(result.is_err());
222        assert!(matches!(result.unwrap_err(), ToolError::NotFound(_)));
223    }
224}