vex_llm/tools/
uuid_tool.rs1use async_trait::async_trait;
12use serde_json::Value;
13use uuid::Uuid;
14
15use crate::tool::{Capability, Tool, ToolDefinition};
16use crate::tool_error::ToolError;
17
18pub struct UuidTool {
34 definition: ToolDefinition,
35}
36
37impl UuidTool {
38 pub fn new() -> Self {
40 Self {
41 definition: ToolDefinition::new(
42 "uuid",
43 "Generate a universally unique identifier (UUID v4).",
44 r#"{
45 "type": "object",
46 "properties": {
47 "count": {
48 "type": "integer",
49 "minimum": 1,
50 "maximum": 100,
51 "default": 1,
52 "description": "Number of UUIDs to generate (1-100)"
53 },
54 "format": {
55 "type": "string",
56 "enum": ["hyphenated", "simple", "urn"],
57 "default": "hyphenated",
58 "description": "Output format: 'hyphenated' (550e8400-e29b-41d4-a716-446655440000), 'simple' (550e8400e29b41d4a716446655440000), 'urn' (urn:uuid:550e8400-e29b-41d4-a716-446655440000)"
59 }
60 }
61 }"#,
62 ),
63 }
64 }
65}
66
67impl Default for UuidTool {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73#[async_trait]
74impl Tool for UuidTool {
75 fn definition(&self) -> &ToolDefinition {
76 &self.definition
77 }
78
79 fn capabilities(&self) -> Vec<Capability> {
80 vec![Capability::PureComputation, Capability::Cryptography]
82 }
83
84 fn validate(&self, args: &Value) -> Result<(), ToolError> {
85 if let Some(count) = args.get("count") {
87 if let Some(n) = count.as_i64() {
88 if !(1..=100).contains(&n) {
89 return Err(ToolError::invalid_args(
90 "uuid",
91 format!("Count must be between 1 and 100, got {}", n),
92 ));
93 }
94 } else if !count.is_null() {
95 return Err(ToolError::invalid_args("uuid", "Count must be an integer"));
96 }
97 }
98
99 if let Some(fmt) = args.get("format").and_then(|v| v.as_str()) {
101 if fmt != "hyphenated" && fmt != "simple" && fmt != "urn" {
102 return Err(ToolError::invalid_args(
103 "uuid",
104 format!(
105 "Invalid format '{}'. Must be 'hyphenated', 'simple', or 'urn'",
106 fmt
107 ),
108 ));
109 }
110 }
111
112 Ok(())
113 }
114
115 async fn execute(&self, args: Value) -> Result<Value, ToolError> {
116 let count = args.get("count").and_then(|v| v.as_i64()).unwrap_or(1) as usize;
117
118 let format = args
119 .get("format")
120 .and_then(|v| v.as_str())
121 .unwrap_or("hyphenated");
122
123 let uuids: Vec<String> = (0..count)
124 .map(|_| {
125 let uuid = Uuid::new_v4();
126 match format {
127 "simple" => uuid.simple().to_string(),
128 "urn" => uuid.urn().to_string(),
129 _ => uuid.hyphenated().to_string(),
130 }
131 })
132 .collect();
133
134 if count == 1 {
135 Ok(serde_json::json!({
136 "uuid": uuids[0],
137 "format": format,
138 "version": 4
139 }))
140 } else {
141 Ok(serde_json::json!({
142 "uuids": uuids,
143 "count": count,
144 "format": format,
145 "version": 4
146 }))
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[tokio::test]
156 async fn test_generate_single_uuid() {
157 let tool = UuidTool::new();
158 let result = tool.execute(serde_json::json!({})).await.unwrap();
159
160 assert!(result["uuid"].is_string());
161 let uuid = result["uuid"].as_str().unwrap();
162
163 assert_eq!(uuid.len(), 36);
165 assert!(uuid.contains('-'));
166 }
167
168 #[tokio::test]
169 async fn test_generate_multiple_uuids() {
170 let tool = UuidTool::new();
171 let result = tool.execute(serde_json::json!({"count": 5})).await.unwrap();
172
173 let uuids = result["uuids"].as_array().unwrap();
174 assert_eq!(uuids.len(), 5);
175
176 let mut unique: std::collections::HashSet<&str> = std::collections::HashSet::new();
178 for uuid in uuids {
179 unique.insert(uuid.as_str().unwrap());
180 }
181 assert_eq!(unique.len(), 5);
182 }
183
184 #[tokio::test]
185 async fn test_simple_format() {
186 let tool = UuidTool::new();
187 let result = tool
188 .execute(serde_json::json!({"format": "simple"}))
189 .await
190 .unwrap();
191
192 let uuid = result["uuid"].as_str().unwrap();
193 assert_eq!(uuid.len(), 32); assert!(!uuid.contains('-'));
195 }
196
197 #[tokio::test]
198 async fn test_urn_format() {
199 let tool = UuidTool::new();
200 let result = tool
201 .execute(serde_json::json!({"format": "urn"}))
202 .await
203 .unwrap();
204
205 let uuid = result["uuid"].as_str().unwrap();
206 assert!(uuid.starts_with("urn:uuid:"));
207 }
208
209 #[tokio::test]
210 async fn test_invalid_count() {
211 let tool = UuidTool::new();
212 let result = tool.validate(&serde_json::json!({"count": 500}));
213
214 assert!(matches!(result, Err(ToolError::InvalidArguments { .. })));
215 }
216
217 #[tokio::test]
218 async fn test_invalid_format() {
219 let tool = UuidTool::new();
220 let result = tool.validate(&serde_json::json!({"format": "invalid"}));
221
222 assert!(matches!(result, Err(ToolError::InvalidArguments { .. })));
223 }
224
225 #[tokio::test]
226 async fn test_uuid_version() {
227 let tool = UuidTool::new();
228 let result = tool.execute(serde_json::json!({})).await.unwrap();
229
230 assert_eq!(result["version"], 4);
231 }
232}