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