Skip to main content

synaptic_tools/
calculator.rs

1//! Calculator tool for evaluating mathematical expressions.
2
3use async_trait::async_trait;
4use serde_json::{json, Value};
5use synaptic_core::{SynapticError, Tool};
6
7/// Calculator tool for evaluating mathematical expressions.
8///
9/// Uses the `meval` crate to evaluate expressions. Supports arithmetic,
10/// power, trigonometric, and logarithmic functions.
11///
12/// # Example
13///
14/// ```rust,ignore
15/// use synaptic_tools::CalculatorTool;
16/// use synaptic_core::Tool;
17///
18/// let tool = CalculatorTool;
19/// let result = tool.call(serde_json::json!({"expression": "2 + 3 * 4"})).await?;
20/// assert_eq!(result["result"], 14.0);
21/// ```
22pub struct CalculatorTool;
23
24impl Default for CalculatorTool {
25    fn default() -> Self {
26        CalculatorTool
27    }
28}
29
30#[async_trait]
31impl Tool for CalculatorTool {
32    fn name(&self) -> &'static str {
33        "calculator"
34    }
35
36    fn description(&self) -> &'static str {
37        "Evaluate mathematical expressions. Supports +, -, *, /, ^ (power), sqrt(), abs(), \
38         sin(), cos(), tan(), log(). Example: '2 + 3 * 4' returns 14."
39    }
40
41    fn parameters(&self) -> Option<Value> {
42        Some(json!({
43            "type": "object",
44            "properties": {
45                "expression": {
46                    "type": "string",
47                    "description": "Mathematical expression to evaluate, e.g. '2 + 3 * 4' or 'sqrt(16)'"
48                }
49            },
50            "required": ["expression"]
51        }))
52    }
53
54    async fn call(&self, args: Value) -> Result<Value, SynapticError> {
55        let expr = args["expression"]
56            .as_str()
57            .ok_or_else(|| SynapticError::Tool("missing 'expression' parameter".to_string()))?;
58
59        let result = meval::eval_str(expr)
60            .map_err(|e| SynapticError::Tool(format!("math evaluation error: {e}")))?;
61
62        Ok(json!({ "expression": expr, "result": result }))
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn tool_metadata() {
72        let tool = CalculatorTool;
73        assert_eq!(tool.name(), "calculator");
74        assert!(!tool.description().is_empty());
75    }
76
77    #[test]
78    fn tool_schema() {
79        let tool = CalculatorTool;
80        let schema = tool.parameters().unwrap();
81        assert_eq!(schema["type"], "object");
82        assert!(schema["properties"]["expression"].is_object());
83    }
84
85    #[tokio::test]
86    async fn basic_arithmetic() {
87        let tool = CalculatorTool;
88        let result = tool.call(json!({"expression": "2 + 3 * 4"})).await.unwrap();
89        assert_eq!(result["result"].as_f64().unwrap(), 14.0);
90        assert_eq!(result["expression"], "2 + 3 * 4");
91    }
92
93    #[tokio::test]
94    async fn sqrt_expression() {
95        let tool = CalculatorTool;
96        let result = tool.call(json!({"expression": "sqrt(16)"})).await.unwrap();
97        assert_eq!(result["result"].as_f64().unwrap(), 4.0);
98    }
99
100    #[tokio::test]
101    async fn power_expression() {
102        let tool = CalculatorTool;
103        let result = tool.call(json!({"expression": "2 ^ 10"})).await.unwrap();
104        assert_eq!(result["result"].as_f64().unwrap(), 1024.0);
105    }
106
107    #[tokio::test]
108    async fn missing_expression_returns_error() {
109        let tool = CalculatorTool;
110        let result = tool.call(json!({})).await;
111        assert!(result.is_err());
112        assert!(result.unwrap_err().to_string().contains("expression"));
113    }
114
115    #[tokio::test]
116    async fn invalid_expression_returns_error() {
117        let tool = CalculatorTool;
118        let result = tool.call(json!({"expression": "not_a_number + ???"})).await;
119        assert!(result.is_err());
120    }
121}