1use crate::json::{Map, Value as JsonValue};
7
8pub struct ToolDef {
10 pub name: &'static str,
11 pub description: &'static str,
12 pub input_schema: JsonValue,
13}
14
15fn schema(properties: Vec<(&str, &str, &str)>, required: Vec<&str>) -> JsonValue {
17 let mut props = Map::new();
18 for (name, field_type, description) in properties {
19 let mut field = Map::new();
20 field.insert(
21 "type".to_string(),
22 JsonValue::String(field_type.to_string()),
23 );
24 if !description.is_empty() {
25 field.insert(
26 "description".to_string(),
27 JsonValue::String(description.to_string()),
28 );
29 }
30 props.insert(name.to_string(), JsonValue::Object(field));
31 }
32
33 let mut obj = Map::new();
34 obj.insert("type".to_string(), JsonValue::String("object".to_string()));
35 obj.insert("properties".to_string(), JsonValue::Object(props));
36 obj.insert(
37 "required".to_string(),
38 JsonValue::Array(
39 required
40 .into_iter()
41 .map(|s| JsonValue::String(s.to_string()))
42 .collect(),
43 ),
44 );
45 obj.insert("additionalProperties".to_string(), JsonValue::Bool(false));
46 JsonValue::Object(obj)
47}
48
49fn schema_with_nested(properties: Vec<(&str, JsonValue)>, required: Vec<&str>) -> JsonValue {
51 let mut props = Map::new();
52 for (name, descriptor) in properties {
53 props.insert(name.to_string(), descriptor);
54 }
55
56 let mut obj = Map::new();
57 obj.insert("type".to_string(), JsonValue::String("object".to_string()));
58 obj.insert("properties".to_string(), JsonValue::Object(props));
59 obj.insert(
60 "required".to_string(),
61 JsonValue::Array(
62 required
63 .into_iter()
64 .map(|s| JsonValue::String(s.to_string()))
65 .collect(),
66 ),
67 );
68 obj.insert("additionalProperties".to_string(), JsonValue::Bool(false));
69 JsonValue::Object(obj)
70}
71
72fn string_field(description: &str) -> JsonValue {
74 let mut f = Map::new();
75 f.insert("type".to_string(), JsonValue::String("string".to_string()));
76 f.insert(
77 "description".to_string(),
78 JsonValue::String(description.to_string()),
79 );
80 JsonValue::Object(f)
81}
82
83fn number_field(description: &str) -> JsonValue {
85 let mut f = Map::new();
86 f.insert("type".to_string(), JsonValue::String("number".to_string()));
87 f.insert(
88 "description".to_string(),
89 JsonValue::String(description.to_string()),
90 );
91 JsonValue::Object(f)
92}
93
94fn integer_field(description: &str) -> JsonValue {
96 let mut f = Map::new();
97 f.insert("type".to_string(), JsonValue::String("integer".to_string()));
98 f.insert(
99 "description".to_string(),
100 JsonValue::String(description.to_string()),
101 );
102 JsonValue::Object(f)
103}
104
105fn boolean_field(description: &str) -> JsonValue {
107 let mut f = Map::new();
108 f.insert("type".to_string(), JsonValue::String("boolean".to_string()));
109 f.insert(
110 "description".to_string(),
111 JsonValue::String(description.to_string()),
112 );
113 JsonValue::Object(f)
114}
115
116fn object_field(description: &str) -> JsonValue {
118 let mut f = Map::new();
119 f.insert("type".to_string(), JsonValue::String("object".to_string()));
120 f.insert(
121 "description".to_string(),
122 JsonValue::String(description.to_string()),
123 );
124 JsonValue::Object(f)
125}
126
127fn number_array_field(description: &str) -> JsonValue {
129 let mut items = Map::new();
130 items.insert("type".to_string(), JsonValue::String("number".to_string()));
131
132 let mut f = Map::new();
133 f.insert("type".to_string(), JsonValue::String("array".to_string()));
134 f.insert("items".to_string(), JsonValue::Object(items));
135 f.insert(
136 "description".to_string(),
137 JsonValue::String(description.to_string()),
138 );
139 JsonValue::Object(f)
140}
141
142fn string_array_field(description: &str) -> JsonValue {
144 let mut items = Map::new();
145 items.insert("type".to_string(), JsonValue::String("string".to_string()));
146
147 let mut f = Map::new();
148 f.insert("type".to_string(), JsonValue::String("array".to_string()));
149 f.insert("items".to_string(), JsonValue::Object(items));
150 f.insert(
151 "description".to_string(),
152 JsonValue::String(description.to_string()),
153 );
154 JsonValue::Object(f)
155}
156
157pub fn all_tools() -> Vec<ToolDef> {
159 vec![
160 ToolDef {
161 name: "reddb_query",
162 description: "Execute a SQL or universal query against RedDB. Supports SELECT, INSERT, UPDATE, DELETE, and graph queries (Gremlin, Cypher, SPARQL).",
163 input_schema: schema(
164 vec![("sql", "string", "SQL or universal query to execute")],
165 vec!["sql"],
166 ),
167 },
168 ToolDef {
169 name: "reddb_collections",
170 description: "List all collections in the database.",
171 input_schema: schema(vec![], vec![]),
172 },
173 ToolDef {
174 name: "reddb_insert_row",
175 description: "Insert a table row into a collection.",
176 input_schema: schema_with_nested(
177 vec![
178 ("collection", string_field("Target collection name")),
179 ("data", object_field("Object with field name/value pairs to insert")),
180 ("metadata", object_field("Optional metadata key/value pairs")),
181 ],
182 vec!["collection", "data"],
183 ),
184 },
185 ToolDef {
186 name: "reddb_insert_node",
187 description: "Insert a graph node into a collection.",
188 input_schema: schema_with_nested(
189 vec![
190 ("collection", string_field("Target collection name")),
191 ("label", string_field("Node label (identifier)")),
192 ("node_type", string_field("Optional node type classification")),
193 ("properties", object_field("Optional node properties as key/value pairs")),
194 ("metadata", object_field("Optional metadata key/value pairs")),
195 ],
196 vec!["collection", "label"],
197 ),
198 },
199 ToolDef {
200 name: "reddb_insert_edge",
201 description: "Insert a graph edge between two nodes.",
202 input_schema: schema_with_nested(
203 vec![
204 ("collection", string_field("Target collection name")),
205 ("label", string_field("Edge label (relationship type)")),
206 ("from", integer_field("Source node entity ID")),
207 ("to", integer_field("Target node entity ID")),
208 ("weight", number_field("Optional edge weight (default 1.0)")),
209 ("properties", object_field("Optional edge properties")),
210 ("metadata", object_field("Optional metadata key/value pairs")),
211 ],
212 vec!["collection", "label", "from", "to"],
213 ),
214 },
215 ToolDef {
216 name: "reddb_insert_vector",
217 description: "Insert a vector embedding into a collection.",
218 input_schema: schema_with_nested(
219 vec![
220 ("collection", string_field("Target collection name")),
221 ("dense", number_array_field("Dense vector (array of floats)")),
222 ("content", string_field("Optional text content associated with the vector")),
223 ("metadata", object_field("Optional metadata key/value pairs")),
224 ],
225 vec!["collection", "dense"],
226 ),
227 },
228 ToolDef {
229 name: "reddb_insert_document",
230 description: "Insert a JSON document into a collection.",
231 input_schema: schema_with_nested(
232 vec![
233 ("collection", string_field("Target collection name")),
234 ("body", object_field("JSON document body")),
235 ("metadata", object_field("Optional metadata key/value pairs")),
236 ],
237 vec!["collection", "body"],
238 ),
239 },
240 ToolDef {
241 name: "reddb_kv_get",
242 description: "Get a value by key from a key-value collection.",
243 input_schema: schema(
244 vec![
245 ("collection", "string", "Collection name"),
246 ("key", "string", "Key to retrieve"),
247 ],
248 vec!["collection", "key"],
249 ),
250 },
251 ToolDef {
252 name: "reddb_kv_set",
253 description: "Set a key-value pair in a collection.",
254 input_schema: schema_with_nested(
255 vec![
256 ("collection", string_field("Collection name")),
257 ("key", string_field("Key to set")),
258 ("value", {
259 let mut f = Map::new();
260 f.insert("description".to_string(), JsonValue::String("Value to store (string, number, boolean, or null)".to_string()));
261 JsonValue::Object(f)
262 }),
263 ("tags", string_array_field("Optional KV invalidation tags")),
264 ("metadata", object_field("Optional metadata key/value pairs")),
265 ],
266 vec!["collection", "key", "value"],
267 ),
268 },
269 ToolDef {
270 name: "reddb_kv_invalidate_tags",
271 description: "Delete every KV entry in a collection tagged with any listed tag.",
272 input_schema: schema_with_nested(
273 vec![
274 ("collection", string_field("Collection name")),
275 ("tags", string_array_field("Tags to invalidate")),
276 ],
277 vec!["collection", "tags"],
278 ),
279 },
280 ToolDef {
281 name: "reddb_config_get",
282 description: "Get a Config value without resolving SecretRef targets.",
283 input_schema: schema(
284 vec![
285 ("collection", "string", "Config collection name"),
286 ("key", "string", "Config key to retrieve"),
287 ],
288 vec!["collection", "key"],
289 ),
290 },
291 ToolDef {
292 name: "reddb_config_put",
293 description: "Set a Config value. TTL and counter operations are not supported for Config.",
294 input_schema: schema_with_nested(
295 vec![
296 ("collection", string_field("Config collection name")),
297 ("key", string_field("Config key to set")),
298 ("value", {
299 let mut f = Map::new();
300 f.insert("description".to_string(), JsonValue::String("Value to store, or an object when paired with secret_ref".to_string()));
301 JsonValue::Object(f)
302 }),
303 ("secret_ref", object_field("Optional { collection, key } Vault SecretRef")),
304 ("tags", string_array_field("Optional Config tags")),
305 ],
306 vec!["collection", "key"],
307 ),
308 },
309 ToolDef {
310 name: "reddb_config_resolve",
311 description: "Explicitly resolve a Config SecretRef. Requires the corresponding Vault unseal permission.",
312 input_schema: schema(
313 vec![
314 ("collection", "string", "Config collection name"),
315 ("key", "string", "Config key to resolve"),
316 ],
317 vec!["collection", "key"],
318 ),
319 },
320 ToolDef {
321 name: "reddb_vault_get",
322 description: "Get Vault metadata for a secret. Does not return plaintext.",
323 input_schema: schema(
324 vec![
325 ("collection", "string", "Vault collection name"),
326 ("key", "string", "Vault key to retrieve metadata for"),
327 ],
328 vec!["collection", "key"],
329 ),
330 },
331 ToolDef {
332 name: "reddb_vault_put",
333 description: "Store a sealed Vault secret. TTL and counter operations are not supported for Vault.",
334 input_schema: schema_with_nested(
335 vec![
336 ("collection", string_field("Vault collection name")),
337 ("key", string_field("Vault key to set")),
338 ("value", {
339 let mut f = Map::new();
340 f.insert("description".to_string(), JsonValue::String("Secret value to seal".to_string()));
341 JsonValue::Object(f)
342 }),
343 ("tags", string_array_field("Optional Vault tags")),
344 ],
345 vec!["collection", "key", "value"],
346 ),
347 },
348 ToolDef {
349 name: "reddb_vault_unseal",
350 description: "Explicitly unseal a Vault secret and return plaintext to an authorized caller.",
351 input_schema: schema(
352 vec![
353 ("collection", "string", "Vault collection name"),
354 ("key", "string", "Vault key to unseal"),
355 ],
356 vec!["collection", "key"],
357 ),
358 },
359 ToolDef {
360 name: "reddb_delete",
361 description: "Delete an entity by ID from a collection.",
362 input_schema: schema(
363 vec![
364 ("collection", "string", "Collection name"),
365 ("id", "integer", "Entity ID to delete"),
366 ],
367 vec!["collection", "id"],
368 ),
369 },
370 ToolDef {
371 name: "reddb_search_vector",
372 description: "Search for similar vectors using cosine similarity.",
373 input_schema: schema_with_nested(
374 vec![
375 ("collection", string_field("Collection to search in")),
376 ("vector", number_array_field("Query vector (array of floats)")),
377 ("k", integer_field("Number of results to return (default 10)")),
378 ("min_score", number_field("Minimum similarity score threshold (default 0.0)")),
379 ],
380 vec!["collection", "vector"],
381 ),
382 },
383 ToolDef {
384 name: "reddb_search_text",
385 description: "Full-text search across collections.",
386 input_schema: schema_with_nested(
387 vec![
388 ("query", string_field("Search query string")),
389 ("collections", {
390 let mut items = Map::new();
391 items.insert("type".to_string(), JsonValue::String("string".to_string()));
392 let mut f = Map::new();
393 f.insert("type".to_string(), JsonValue::String("array".to_string()));
394 f.insert("items".to_string(), JsonValue::Object(items));
395 f.insert("description".to_string(), JsonValue::String("Optional list of collections to search".to_string()));
396 JsonValue::Object(f)
397 }),
398 ("limit", integer_field("Maximum number of results (default 10)")),
399 ("fuzzy", boolean_field("Enable fuzzy matching (default false)")),
400 ],
401 vec!["query"],
402 ),
403 },
404 ToolDef {
405 name: "reddb_health",
406 description: "Check database health and return runtime statistics.",
407 input_schema: schema(vec![], vec![]),
408 },
409 ToolDef {
410 name: "reddb_graph_traverse",
411 description: "Traverse the graph from a source node using BFS or DFS.",
412 input_schema: schema_with_nested(
413 vec![
414 ("source", string_field("Source node label to start traversal from")),
415 ("direction", string_field("Traversal direction: 'outgoing', 'incoming', or 'both' (default 'outgoing')")),
416 ("max_depth", integer_field("Maximum traversal depth (default 3)")),
417 ("strategy", string_field("Traversal strategy: 'bfs' or 'dfs' (default 'bfs')")),
418 ],
419 vec!["source"],
420 ),
421 },
422 ToolDef {
423 name: "reddb_graph_shortest_path",
424 description: "Find the shortest path between two graph nodes.",
425 input_schema: schema_with_nested(
426 vec![
427 ("source", string_field("Source node label")),
428 ("target", string_field("Target node label")),
429 ("direction", string_field("Edge direction: 'outgoing', 'incoming', or 'both' (default 'outgoing')")),
430 (
431 "algorithm",
432 string_field(
433 "Path algorithm: 'bfs', 'dijkstra', 'astar', or 'bellman_ford' (default 'bfs')",
434 ),
435 ),
436 ],
437 vec!["source", "target"],
438 ),
439 },
440 ToolDef {
442 name: "reddb_auth_bootstrap",
443 description: "Bootstrap the first admin user. Only works when no users exist yet. Returns the admin user and an API key.",
444 input_schema: schema(
445 vec![
446 ("username", "string", "Admin username"),
447 ("password", "string", "Admin password"),
448 ],
449 vec!["username", "password"],
450 ),
451 },
452 ToolDef {
453 name: "reddb_auth_create_user",
454 description: "Create a new database user with a role (admin, write, or read).",
455 input_schema: schema(
456 vec![
457 ("username", "string", "Username for the new user"),
458 ("password", "string", "Password for the new user"),
459 ("role", "string", "Role: 'admin', 'write', or 'read'"),
460 ],
461 vec!["username", "password", "role"],
462 ),
463 },
464 ToolDef {
465 name: "reddb_auth_login",
466 description: "Login with username and password. Returns a session token.",
467 input_schema: schema(
468 vec![
469 ("username", "string", "Username"),
470 ("password", "string", "Password"),
471 ],
472 vec!["username", "password"],
473 ),
474 },
475 ToolDef {
476 name: "reddb_auth_create_api_key",
477 description: "Create a persistent API key for a user.",
478 input_schema: schema(
479 vec![
480 ("username", "string", "Username to create the key for"),
481 ("name", "string", "Human-readable label for the key"),
482 ("role", "string", "Role for this key: 'admin', 'write', or 'read'"),
483 ],
484 vec!["username", "name", "role"],
485 ),
486 },
487 ToolDef {
488 name: "reddb_auth_list_users",
489 description: "List all database users and their roles.",
490 input_schema: schema(vec![], vec![]),
491 },
492 ToolDef {
494 name: "reddb_update",
495 description: "Update entities in a collection matching a filter.",
496 input_schema: schema_with_nested(
497 vec![
498 ("collection", string_field("Collection name")),
499 ("set", object_field("Key-value pairs to update")),
500 (
501 "where_filter",
502 string_field(
503 "Optional SQL WHERE clause (e.g., \"age > 21\")",
504 ),
505 ),
506 ],
507 vec!["collection", "set"],
508 ),
509 },
510 ToolDef {
511 name: "reddb_scan",
512 description: "Scan entities from a collection with pagination.",
513 input_schema: schema_with_nested(
514 vec![
515 ("collection", string_field("Collection to scan")),
516 ("limit", integer_field("Maximum number of results (default 10)")),
517 ("offset", integer_field("Number of records to skip (default 0)")),
518 ],
519 vec!["collection"],
520 ),
521 },
522 ToolDef {
524 name: "reddb_graph_centrality",
525 description: "Compute centrality scores for graph nodes. Algorithms: degree, closeness, betweenness, eigenvector, pagerank.",
526 input_schema: schema_with_nested(
527 vec![(
528 "algorithm",
529 string_field(
530 "Centrality algorithm: 'degree', 'closeness', 'betweenness', 'eigenvector', 'pagerank'",
531 ),
532 )],
533 vec!["algorithm"],
534 ),
535 },
536 ToolDef {
537 name: "reddb_graph_community",
538 description: "Detect communities in the graph. Algorithms: label_propagation, louvain.",
539 input_schema: schema_with_nested(
540 vec![
541 (
542 "algorithm",
543 string_field(
544 "Community detection algorithm: 'label_propagation' or 'louvain'",
545 ),
546 ),
547 (
548 "max_iterations",
549 integer_field("Maximum iterations (default 100)"),
550 ),
551 ],
552 vec!["algorithm"],
553 ),
554 },
555 ToolDef {
556 name: "reddb_graph_components",
557 description: "Find connected components in the graph.",
558 input_schema: schema_with_nested(
559 vec![(
560 "mode",
561 string_field(
562 "Component mode: 'weakly_connected' or 'strongly_connected' (default 'weakly_connected')",
563 ),
564 )],
565 vec![],
566 ),
567 },
568 ToolDef {
569 name: "reddb_graph_cycles",
570 description: "Detect cycles in the graph.",
571 input_schema: schema_with_nested(
572 vec![
573 (
574 "max_length",
575 integer_field("Maximum cycle length (default 10)"),
576 ),
577 (
578 "max_cycles",
579 integer_field("Maximum number of cycles to return (default 100)"),
580 ),
581 ],
582 vec![],
583 ),
584 },
585 ToolDef {
586 name: "reddb_graph_clustering",
587 description: "Compute clustering coefficient for the graph.",
588 input_schema: schema(vec![], vec![]),
589 },
590 ToolDef {
592 name: "reddb_create_collection",
593 description: "Create a new collection (table) in the database.",
594 input_schema: schema(
595 vec![("name", "string", "Collection name to create")],
596 vec!["name"],
597 ),
598 },
599 ToolDef {
600 name: "reddb_drop_collection",
601 description: "Drop (delete) a collection from the database.",
602 input_schema: schema(
603 vec![("name", "string", "Collection name to drop")],
604 vec!["name"],
605 ),
606 },
607 ]
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613
614 #[test]
615 fn test_all_tools_defined() {
616 let tools = all_tools();
617 assert!(tools.len() >= 24);
618 let names: Vec<&str> = tools.iter().map(|t| t.name).collect();
619 assert!(names.contains(&"reddb_query"));
620 assert!(names.contains(&"reddb_collections"));
621 assert!(names.contains(&"reddb_insert_row"));
622 assert!(names.contains(&"reddb_insert_node"));
623 assert!(names.contains(&"reddb_insert_edge"));
624 assert!(names.contains(&"reddb_insert_vector"));
625 assert!(names.contains(&"reddb_insert_document"));
626 assert!(names.contains(&"reddb_kv_get"));
627 assert!(names.contains(&"reddb_kv_set"));
628 assert!(names.contains(&"reddb_config_get"));
629 assert!(names.contains(&"reddb_config_put"));
630 assert!(names.contains(&"reddb_config_resolve"));
631 assert!(names.contains(&"reddb_vault_get"));
632 assert!(names.contains(&"reddb_vault_put"));
633 assert!(names.contains(&"reddb_vault_unseal"));
634 assert!(names.contains(&"reddb_delete"));
635 assert!(names.contains(&"reddb_search_vector"));
636 assert!(names.contains(&"reddb_search_text"));
637 assert!(names.contains(&"reddb_health"));
638 assert!(names.contains(&"reddb_graph_traverse"));
639 assert!(names.contains(&"reddb_graph_shortest_path"));
640 assert!(names.contains(&"reddb_update"));
642 assert!(names.contains(&"reddb_scan"));
643 assert!(names.contains(&"reddb_graph_centrality"));
644 assert!(names.contains(&"reddb_graph_community"));
645 assert!(names.contains(&"reddb_graph_components"));
646 assert!(names.contains(&"reddb_graph_cycles"));
647 assert!(names.contains(&"reddb_graph_clustering"));
648 assert!(names.contains(&"reddb_create_collection"));
649 assert!(names.contains(&"reddb_drop_collection"));
650 }
651
652 #[test]
653 fn test_tool_schemas_have_type() {
654 for tool in all_tools() {
655 assert_eq!(
656 tool.input_schema.get("type").and_then(|v| v.as_str()),
657 Some("object"),
658 "tool '{}' schema must have type=object",
659 tool.name,
660 );
661 }
662 }
663
664 #[test]
665 fn test_update_tool_schema() {
666 let tools = all_tools();
667 let tool = tools.iter().find(|t| t.name == "reddb_update").unwrap();
668 assert_eq!(tool.name, "reddb_update");
669 let props = tool
670 .input_schema
671 .get("properties")
672 .and_then(|v| v.as_object())
673 .unwrap();
674 assert!(props.contains_key("collection"));
675 assert!(props.contains_key("set"));
676 assert!(props.contains_key("where_filter"));
677 let required = tool
678 .input_schema
679 .get("required")
680 .and_then(|v| v.as_array())
681 .unwrap();
682 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
683 assert!(required_strs.contains(&"collection"));
684 assert!(required_strs.contains(&"set"));
685 assert!(!required_strs.contains(&"where_filter"));
686 }
687
688 #[test]
689 fn test_scan_tool_schema() {
690 let tools = all_tools();
691 let tool = tools.iter().find(|t| t.name == "reddb_scan").unwrap();
692 let props = tool
693 .input_schema
694 .get("properties")
695 .and_then(|v| v.as_object())
696 .unwrap();
697 assert!(props.contains_key("collection"));
698 assert!(props.contains_key("limit"));
699 assert!(props.contains_key("offset"));
700 let required = tool
701 .input_schema
702 .get("required")
703 .and_then(|v| v.as_array())
704 .unwrap();
705 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
706 assert!(required_strs.contains(&"collection"));
707 assert!(!required_strs.contains(&"limit"));
709 assert!(!required_strs.contains(&"offset"));
710 }
711
712 #[test]
713 fn test_graph_centrality_tool_schema() {
714 let tools = all_tools();
715 let tool = tools
716 .iter()
717 .find(|t| t.name == "reddb_graph_centrality")
718 .unwrap();
719 let props = tool
720 .input_schema
721 .get("properties")
722 .and_then(|v| v.as_object())
723 .unwrap();
724 assert!(props.contains_key("algorithm"));
725 let required = tool
726 .input_schema
727 .get("required")
728 .and_then(|v| v.as_array())
729 .unwrap();
730 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
731 assert!(required_strs.contains(&"algorithm"));
732 }
733
734 #[test]
735 fn test_graph_community_tool_schema() {
736 let tools = all_tools();
737 let tool = tools
738 .iter()
739 .find(|t| t.name == "reddb_graph_community")
740 .unwrap();
741 let props = tool
742 .input_schema
743 .get("properties")
744 .and_then(|v| v.as_object())
745 .unwrap();
746 assert!(props.contains_key("algorithm"));
747 assert!(props.contains_key("max_iterations"));
748 let required = tool
749 .input_schema
750 .get("required")
751 .and_then(|v| v.as_array())
752 .unwrap();
753 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
754 assert!(required_strs.contains(&"algorithm"));
755 assert!(!required_strs.contains(&"max_iterations"));
756 }
757
758 #[test]
759 fn test_graph_components_tool_schema() {
760 let tools = all_tools();
761 let tool = tools
762 .iter()
763 .find(|t| t.name == "reddb_graph_components")
764 .unwrap();
765 let props = tool
766 .input_schema
767 .get("properties")
768 .and_then(|v| v.as_object())
769 .unwrap();
770 assert!(props.contains_key("mode"));
771 let required = tool
772 .input_schema
773 .get("required")
774 .and_then(|v| v.as_array())
775 .unwrap();
776 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
778 assert!(required_strs.is_empty());
779 }
780
781 #[test]
782 fn test_graph_cycles_tool_schema() {
783 let tools = all_tools();
784 let tool = tools
785 .iter()
786 .find(|t| t.name == "reddb_graph_cycles")
787 .unwrap();
788 let props = tool
789 .input_schema
790 .get("properties")
791 .and_then(|v| v.as_object())
792 .unwrap();
793 assert!(props.contains_key("max_length"));
794 assert!(props.contains_key("max_cycles"));
795 let required = tool
796 .input_schema
797 .get("required")
798 .and_then(|v| v.as_array())
799 .unwrap();
800 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
802 assert!(required_strs.is_empty());
803 }
804
805 #[test]
806 fn test_graph_clustering_tool_schema() {
807 let tools = all_tools();
808 let tool = tools
809 .iter()
810 .find(|t| t.name == "reddb_graph_clustering")
811 .unwrap();
812 let props = tool
813 .input_schema
814 .get("properties")
815 .and_then(|v| v.as_object())
816 .unwrap();
817 assert!(props.is_empty());
819 }
820
821 #[test]
822 fn test_create_collection_tool_schema() {
823 let tools = all_tools();
824 let tool = tools
825 .iter()
826 .find(|t| t.name == "reddb_create_collection")
827 .unwrap();
828 let props = tool
829 .input_schema
830 .get("properties")
831 .and_then(|v| v.as_object())
832 .unwrap();
833 assert!(props.contains_key("name"));
834 let name_type = props
835 .get("name")
836 .and_then(|v| v.get("type"))
837 .and_then(|v| v.as_str());
838 assert_eq!(name_type, Some("string"));
839 let required = tool
840 .input_schema
841 .get("required")
842 .and_then(|v| v.as_array())
843 .unwrap();
844 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
845 assert!(required_strs.contains(&"name"));
846 }
847
848 #[test]
849 fn test_drop_collection_tool_schema() {
850 let tools = all_tools();
851 let tool = tools
852 .iter()
853 .find(|t| t.name == "reddb_drop_collection")
854 .unwrap();
855 let props = tool
856 .input_schema
857 .get("properties")
858 .and_then(|v| v.as_object())
859 .unwrap();
860 assert!(props.contains_key("name"));
861 let required = tool
862 .input_schema
863 .get("required")
864 .and_then(|v| v.as_array())
865 .unwrap();
866 let required_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
867 assert!(required_strs.contains(&"name"));
868 }
869}