pulseengine_mcp_protocol/
validation.rs

1//! Validation utilities for MCP protocol types
2
3use crate::{Error, Result};
4use jsonschema::{JSONSchema, ValidationError};
5use serde_json::Value;
6use std::collections::HashMap;
7use uuid::Uuid;
8use validator::Validate;
9
10/// Protocol validation utilities
11pub struct Validator;
12
13impl Validator {
14    /// Validate a UUID string
15    ///
16    /// # Errors
17    ///
18    /// Returns an error if the string is not a valid UUID format
19    pub fn validate_uuid(uuid_str: &str) -> Result<Uuid> {
20        uuid_str
21            .parse::<Uuid>()
22            .map_err(|e| Error::validation_error(format!("Invalid UUID: {e}")))
23    }
24
25    /// Validate that a string is not empty
26    ///
27    /// # Errors
28    ///
29    /// Returns an error if the string is empty or contains only whitespace
30    pub fn validate_non_empty(value: &str, field_name: &str) -> Result<()> {
31        if value.trim().is_empty() {
32            Err(Error::validation_error(format!(
33                "{field_name} cannot be empty"
34            )))
35        } else {
36            Ok(())
37        }
38    }
39
40    /// Validate a tool name (must be alphanumeric with underscores)
41    ///
42    /// # Errors
43    ///
44    /// Returns an error if the name is empty or contains invalid characters
45    pub fn validate_tool_name(name: &str) -> Result<()> {
46        Self::validate_non_empty(name, "Tool name")?;
47
48        if !name
49            .chars()
50            .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
51        {
52            return Err(Error::validation_error(
53                "Tool name must contain only alphanumeric characters, underscores, and hyphens",
54            ));
55        }
56
57        Ok(())
58    }
59
60    /// Validate a resource URI
61    ///
62    /// # Errors
63    ///
64    /// Returns an error if the URI is empty or contains control characters
65    pub fn validate_resource_uri(uri: &str) -> Result<()> {
66        Self::validate_non_empty(uri, "Resource URI")?;
67
68        // Basic URI validation - must not contain control characters
69        if uri.chars().any(char::is_control) {
70            return Err(Error::validation_error(
71                "Resource URI cannot contain control characters",
72            ));
73        }
74
75        Ok(())
76    }
77
78    /// Validate JSON schema
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if the schema is not a valid JSON object with a type field
83    pub fn validate_json_schema(schema: &Value) -> Result<()> {
84        // Basic validation - ensure it's an object with a "type" field
85        if let Some(obj) = schema.as_object() {
86            if !obj.contains_key("type") {
87                return Err(Error::validation_error(
88                    "JSON schema must have a 'type' field",
89                ));
90            }
91        } else {
92            return Err(Error::validation_error("JSON schema must be an object"));
93        }
94
95        Ok(())
96    }
97
98    /// Validate tool arguments against a schema
99    ///
100    /// # Errors
101    ///
102    /// Returns an error if required arguments are missing from the provided arguments
103    pub fn validate_tool_arguments(args: &HashMap<String, Value>, schema: &Value) -> Result<()> {
104        // Basic validation - check required properties if defined
105        if let Some(schema_obj) = schema.as_object() {
106            if let Some(_properties) = schema_obj.get("properties").and_then(|p| p.as_object()) {
107                if let Some(required) = schema_obj.get("required").and_then(|r| r.as_array()) {
108                    for req_field in required {
109                        if let Some(field_name) = req_field.as_str() {
110                            if !args.contains_key(field_name) {
111                                return Err(Error::validation_error(format!(
112                                    "Required argument '{field_name}' is missing"
113                                )));
114                            }
115                        }
116                    }
117                }
118            }
119        }
120
121        Ok(())
122    }
123
124    /// Validate pagination parameters
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if cursor is empty, limit is 0, or limit exceeds 1000
129    pub fn validate_pagination(cursor: Option<&str>, limit: Option<u32>) -> Result<()> {
130        if let Some(cursor_val) = cursor {
131            Self::validate_non_empty(cursor_val, "Cursor")?;
132        }
133
134        if let Some(limit_val) = limit {
135            if limit_val == 0 {
136                return Err(Error::validation_error("Limit must be greater than 0"));
137            }
138            if limit_val > 1000 {
139                return Err(Error::validation_error("Limit cannot exceed 1000"));
140            }
141        }
142
143        Ok(())
144    }
145
146    /// Validate prompt name
147    ///
148    /// # Errors
149    ///
150    /// Returns an error if the name is empty or contains invalid characters
151    pub fn validate_prompt_name(name: &str) -> Result<()> {
152        Self::validate_non_empty(name, "Prompt name")?;
153
154        if !name
155            .chars()
156            .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
157        {
158            return Err(Error::validation_error(
159                "Prompt name must contain only alphanumeric characters, underscores, hyphens, and dots",
160            ));
161        }
162
163        Ok(())
164    }
165
166    /// Validate a struct using the validator crate
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if the struct fails validation according to its validation rules
171    pub fn validate_struct<T: Validate>(item: &T) -> Result<()> {
172        item.validate()
173            .map_err(|e| Error::validation_error(e.to_string()))
174    }
175
176    /// Validate structured content against a JSON schema
177    ///
178    /// # Errors
179    ///
180    /// Returns an error if the content doesn't match the schema or if the schema is invalid
181    pub fn validate_structured_content(content: &Value, output_schema: &Value) -> Result<()> {
182        // First validate that the schema itself is valid
183        Self::validate_json_schema(output_schema)?;
184
185        // Compile the schema
186        let schema = JSONSchema::compile(output_schema)
187            .map_err(|e| Error::validation_error(format!("Invalid JSON schema: {e}")))?;
188
189        // Validate the content against the schema
190        if let Err(errors) = schema.validate(content) {
191            let error_messages: Vec<String> = errors
192                .map(|e| format!("{}: {}", e.instance_path, e))
193                .collect();
194            return Err(Error::validation_error(format!(
195                "Structured content validation failed: {}",
196                error_messages.join(", ")
197            )));
198        }
199
200        Ok(())
201    }
202
203    /// Validate that a tool's output schema is properly defined
204    ///
205    /// # Errors
206    ///
207    /// Returns an error if the output schema is invalid or incompatible with MCP requirements
208    pub fn validate_tool_output_schema(output_schema: &Value) -> Result<()> {
209        // Basic JSON schema validation
210        Self::validate_json_schema(output_schema)?;
211
212        // Additional MCP-specific validations for tool output schemas
213        if let Some(obj) = output_schema.as_object() {
214            // Ensure the schema describes structured data (object or array)
215            if let Some(schema_type) = obj.get("type").and_then(|t| t.as_str()) {
216                match schema_type {
217                    "object" | "array" => {
218                        // Valid structured types
219                    }
220                    "string" | "number" | "integer" | "boolean" | "null" => {
221                        return Err(Error::validation_error(
222                            "Tool output schema should define structured data (object or array), not primitive types",
223                        ));
224                    }
225                    _ => {
226                        return Err(Error::validation_error(
227                            "Invalid type specified in tool output schema",
228                        ));
229                    }
230                }
231            }
232
233            // Check for required properties in object schemas
234            if obj.get("type").and_then(|t| t.as_str()) == Some("object") {
235                if let Some(properties) = obj.get("properties") {
236                    if !properties.is_object() {
237                        return Err(Error::validation_error(
238                            "Object schema properties must be an object",
239                        ));
240                    }
241                } else {
242                    return Err(Error::validation_error(
243                        "Object schema must define properties",
244                    ));
245                }
246            }
247        }
248
249        Ok(())
250    }
251
252    /// Extract validation errors in a user-friendly format
253    ///
254    /// # Errors
255    ///
256    /// Returns formatted validation error messages
257    pub fn format_validation_errors<'a>(
258        errors: impl Iterator<Item = ValidationError<'a>>,
259    ) -> String {
260        let messages: Vec<String> = errors
261            .map(|error| {
262                let path_str = error.instance_path.to_string();
263                if path_str.is_empty() {
264                    error.to_string()
265                } else {
266                    format!("at '{path_str}': {error}")
267                }
268            })
269            .collect();
270
271        if messages.is_empty() {
272            "Unknown validation error".to_string()
273        } else {
274            messages.join("; ")
275        }
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use serde_json::json;
283
284    #[test]
285    fn test_validate_uuid() {
286        let valid_uuid = "550e8400-e29b-41d4-a716-446655440000";
287        assert!(Validator::validate_uuid(valid_uuid).is_ok());
288
289        let invalid_uuid = "not-a-uuid";
290        assert!(Validator::validate_uuid(invalid_uuid).is_err());
291    }
292
293    #[test]
294    fn test_validate_non_empty() {
295        assert!(Validator::validate_non_empty("valid", "field").is_ok());
296        assert!(Validator::validate_non_empty("", "field").is_err());
297        assert!(Validator::validate_non_empty("   ", "field").is_err());
298    }
299
300    #[test]
301    fn test_validate_tool_name() {
302        assert!(Validator::validate_tool_name("valid_tool").is_ok());
303        assert!(Validator::validate_tool_name("tool-name").is_ok());
304        assert!(Validator::validate_tool_name("tool123").is_ok());
305        assert!(Validator::validate_tool_name("").is_err());
306        assert!(Validator::validate_tool_name("invalid tool").is_err());
307        assert!(Validator::validate_tool_name("tool@name").is_err());
308    }
309
310    #[test]
311    fn test_validate_json_schema() {
312        let valid_schema = json!({"type": "object"});
313        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
314
315        let invalid_schema = json!("not an object");
316        assert!(Validator::validate_json_schema(&invalid_schema).is_err());
317
318        let no_type_schema = json!({"properties": {}});
319        assert!(Validator::validate_json_schema(&no_type_schema).is_err());
320    }
321
322    #[test]
323    fn test_validate_pagination() {
324        assert!(Validator::validate_pagination(None, None).is_ok());
325        assert!(Validator::validate_pagination(Some("cursor"), Some(10)).is_ok());
326        assert!(Validator::validate_pagination(Some(""), None).is_err());
327        assert!(Validator::validate_pagination(None, Some(0)).is_err());
328        assert!(Validator::validate_pagination(None, Some(1001)).is_err());
329    }
330
331    #[test]
332    fn test_validate_resource_uri() {
333        // Valid URIs
334        assert!(Validator::validate_resource_uri("http://example.com/resource").is_ok());
335        assert!(Validator::validate_resource_uri("file:///path/to/resource").is_ok());
336        assert!(Validator::validate_resource_uri("custom://protocol/resource").is_ok());
337
338        // Invalid URIs
339        assert!(Validator::validate_resource_uri("").is_err());
340        assert!(Validator::validate_resource_uri("   ").is_err());
341        assert!(Validator::validate_resource_uri("uri\nwith\nnewlines").is_err());
342        assert!(Validator::validate_resource_uri("uri\twith\ttabs").is_err());
343        assert!(Validator::validate_resource_uri("uri\rwith\rcarriage\rreturns").is_err());
344    }
345
346    #[test]
347    fn test_validate_prompt_name() {
348        // Valid prompt names
349        assert!(Validator::validate_prompt_name("valid_prompt").is_ok());
350        assert!(Validator::validate_prompt_name("prompt-name").is_ok());
351        assert!(Validator::validate_prompt_name("prompt.name").is_ok());
352        assert!(Validator::validate_prompt_name("prompt123").is_ok());
353        assert!(Validator::validate_prompt_name("Prompt_Name-123.test").is_ok());
354
355        // Invalid prompt names
356        assert!(Validator::validate_prompt_name("").is_err());
357        assert!(Validator::validate_prompt_name("   ").is_err());
358        assert!(Validator::validate_prompt_name("prompt name").is_err());
359        assert!(Validator::validate_prompt_name("prompt@name").is_err());
360        assert!(Validator::validate_prompt_name("prompt/name").is_err());
361        assert!(Validator::validate_prompt_name("prompt:name").is_err());
362    }
363
364    #[test]
365    fn test_validate_tool_arguments() {
366        // Valid schema with no required fields
367        let schema = json!({
368            "type": "object",
369            "properties": {
370                "name": {"type": "string"},
371                "age": {"type": "number"}
372            }
373        });
374        let args = HashMap::new();
375        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
376
377        // Valid schema with required fields - all present
378        let schema = json!({
379            "type": "object",
380            "properties": {
381                "name": {"type": "string"},
382                "age": {"type": "number"}
383            },
384            "required": ["name"]
385        });
386        let mut args = HashMap::new();
387        args.insert("name".to_string(), json!("John"));
388        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
389
390        // Invalid - missing required field
391        let args = HashMap::new();
392        let result = Validator::validate_tool_arguments(&args, &schema);
393        assert!(result.is_err());
394        assert!(
395            result
396                .unwrap_err()
397                .message
398                .contains("Required argument 'name' is missing")
399        );
400
401        // Valid schema with multiple required fields
402        let schema = json!({
403            "type": "object",
404            "properties": {
405                "name": {"type": "string"},
406                "age": {"type": "number"},
407                "email": {"type": "string"}
408            },
409            "required": ["name", "email"]
410        });
411        let mut args = HashMap::new();
412        args.insert("name".to_string(), json!("John"));
413        args.insert("email".to_string(), json!("john@example.com"));
414        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
415
416        // Invalid - missing one required field
417        let mut args = HashMap::new();
418        args.insert("name".to_string(), json!("John"));
419        let result = Validator::validate_tool_arguments(&args, &schema);
420        assert!(result.is_err());
421        assert!(
422            result
423                .unwrap_err()
424                .message
425                .contains("Required argument 'email' is missing")
426        );
427
428        // Schema without properties
429        let schema = json!({
430            "type": "object"
431        });
432        let args = HashMap::new();
433        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
434
435        // Schema with empty required array
436        let schema = json!({
437            "type": "object",
438            "properties": {
439                "name": {"type": "string"}
440            },
441            "required": []
442        });
443        let args = HashMap::new();
444        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
445
446        // Schema with invalid required field (not a string)
447        let schema = json!({
448            "type": "object",
449            "properties": {
450                "name": {"type": "string"}
451            },
452            "required": [123]
453        });
454        let args = HashMap::new();
455        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
456    }
457
458    #[test]
459    fn test_validate_structured_content() {
460        // Valid structured content
461        let content = json!({
462            "name": "John Doe",
463            "age": 30,
464            "email": "john@example.com"
465        });
466        let schema = json!({
467            "type": "object",
468            "properties": {
469                "name": {"type": "string"},
470                "age": {"type": "integer", "minimum": 0},
471                "email": {"type": "string", "format": "email"}
472            },
473            "required": ["name", "age"]
474        });
475
476        assert!(Validator::validate_structured_content(&content, &schema).is_ok());
477
478        // Invalid content - missing required field
479        let invalid_content = json!({
480            "name": "John Doe"
481        });
482        let result = Validator::validate_structured_content(&invalid_content, &schema);
483        assert!(result.is_err());
484        assert!(result.unwrap_err().message.contains("validation failed"));
485
486        // Invalid content - wrong type
487        let invalid_content = json!({
488            "name": "John Doe",
489            "age": "thirty"
490        });
491        let result = Validator::validate_structured_content(&invalid_content, &schema);
492        assert!(result.is_err());
493
494        // Invalid schema - this should be a basic validation before attempting to compile
495        let invalid_schema = json!({
496            "type": "invalid_type"
497        });
498        let result = Validator::validate_structured_content(&content, &invalid_schema);
499        assert!(result.is_err());
500        // The error message can vary, but it should indicate schema validation failure
501        let error_msg = result.unwrap_err().message;
502        assert!(error_msg.contains("JSON schema") || error_msg.contains("Invalid"));
503    }
504
505    #[test]
506    fn test_validate_tool_output_schema() {
507        // Valid object schema
508        let valid_object_schema = json!({
509            "type": "object",
510            "properties": {
511                "result": {"type": "string"},
512                "metadata": {"type": "object"}
513            }
514        });
515        assert!(Validator::validate_tool_output_schema(&valid_object_schema).is_ok());
516
517        // Valid array schema
518        let valid_array_schema = json!({
519            "type": "array",
520            "items": {"type": "string"}
521        });
522        assert!(Validator::validate_tool_output_schema(&valid_array_schema).is_ok());
523
524        // Invalid - primitive type
525        let invalid_primitive_schema = json!({
526            "type": "string"
527        });
528        let result = Validator::validate_tool_output_schema(&invalid_primitive_schema);
529        assert!(result.is_err());
530        assert!(
531            result
532                .unwrap_err()
533                .message
534                .contains("should define structured data")
535        );
536
537        // Invalid - object without properties
538        let invalid_object_schema = json!({
539            "type": "object"
540        });
541        let result = Validator::validate_tool_output_schema(&invalid_object_schema);
542        assert!(result.is_err());
543        assert!(
544            result
545                .unwrap_err()
546                .message
547                .contains("must define properties")
548        );
549
550        // Invalid - object with invalid properties
551        let invalid_props_schema = json!({
552            "type": "object",
553            "properties": "not an object"
554        });
555        let result = Validator::validate_tool_output_schema(&invalid_props_schema);
556        assert!(result.is_err());
557        assert!(
558            result
559                .unwrap_err()
560                .message
561                .contains("properties must be an object")
562        );
563
564        // Invalid - missing type field
565        let no_type_schema = json!({
566            "properties": {}
567        });
568        let result = Validator::validate_tool_output_schema(&no_type_schema);
569        assert!(result.is_err());
570        assert!(
571            result
572                .unwrap_err()
573                .message
574                .contains("JSON schema must have a 'type' field")
575        );
576    }
577
578    #[test]
579    fn test_structured_content_with_arrays() {
580        // Array content validation
581        let content = json!([
582            {"id": 1, "name": "Item 1"},
583            {"id": 2, "name": "Item 2"}
584        ]);
585        let schema = json!({
586            "type": "array",
587            "items": {
588                "type": "object",
589                "properties": {
590                    "id": {"type": "integer"},
591                    "name": {"type": "string"}
592                },
593                "required": ["id", "name"]
594            }
595        });
596
597        assert!(Validator::validate_structured_content(&content, &schema).is_ok());
598
599        // Invalid array content
600        let invalid_content = json!([
601            {"id": 1, "name": "Item 1"},
602            {"id": "not a number", "name": "Item 2"}
603        ]);
604        let result = Validator::validate_structured_content(&invalid_content, &schema);
605        assert!(result.is_err());
606    }
607
608    #[test]
609    fn test_nested_structured_content() {
610        // Nested object validation
611        let content = json!({
612            "user": {
613                "name": "John",
614                "profile": {
615                    "age": 30,
616                    "preferences": ["reading", "coding"]
617                }
618            },
619            "timestamp": "2023-01-01T00:00:00Z"
620        });
621
622        let schema = json!({
623            "type": "object",
624            "properties": {
625                "user": {
626                    "type": "object",
627                    "properties": {
628                        "name": {"type": "string"},
629                        "profile": {
630                            "type": "object",
631                            "properties": {
632                                "age": {"type": "integer"},
633                                "preferences": {
634                                    "type": "array",
635                                    "items": {"type": "string"}
636                                }
637                            },
638                            "required": ["age"]
639                        }
640                    },
641                    "required": ["name", "profile"]
642                },
643                "timestamp": {"type": "string"}
644            },
645            "required": ["user"]
646        });
647
648        assert!(Validator::validate_structured_content(&content, &schema).is_ok());
649
650        // Invalid nested content
651        let invalid_content = json!({
652            "user": {
653                "name": "John",
654                "profile": {
655                    "preferences": ["reading", "coding"]
656                    // Missing required "age" field
657                }
658            }
659        });
660        let result = Validator::validate_structured_content(&invalid_content, &schema);
661        assert!(result.is_err());
662    }
663
664    #[test]
665    fn test_format_validation_errors() {
666        // This is a basic test since we can't easily create ValidationError instances
667        // The function is mainly for internal use
668        let empty_errors = std::iter::empty();
669        let result = Validator::format_validation_errors(empty_errors);
670        assert_eq!(result, "Unknown validation error");
671    }
672
673    #[test]
674    fn test_call_tool_result_structured_validation() {
675        use crate::model::{CallToolResult, Content};
676
677        // Valid structured content
678        let structured_data = json!({
679            "result": "success",
680            "data": {"count": 42}
681        });
682        let schema = json!({
683            "type": "object",
684            "properties": {
685                "result": {"type": "string"},
686                "data": {"type": "object"}
687            },
688            "required": ["result"]
689        });
690
691        let result =
692            CallToolResult::structured(vec![Content::text("Operation completed")], structured_data);
693
694        assert!(result.validate_structured_content(&schema).is_ok());
695
696        // Invalid structured content
697        let invalid_data = json!({
698            "result": 123 // Should be string
699        });
700        let invalid_result =
701            CallToolResult::structured(vec![Content::text("Operation completed")], invalid_data);
702
703        assert!(invalid_result.validate_structured_content(&schema).is_err());
704
705        // Result without structured content should pass validation
706        let simple_result = CallToolResult::text("Simple result");
707        assert!(simple_result.validate_structured_content(&schema).is_ok());
708    }
709
710    #[test]
711    fn test_validate_uuid_edge_cases() {
712        // Valid UUID formats
713        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-446655440000").is_ok());
714        assert!(Validator::validate_uuid("6ba7b810-9dad-11d1-80b4-00c04fd430c8").is_ok());
715        assert!(Validator::validate_uuid("123e4567-e89b-12d3-a456-426614174000").is_ok());
716
717        // Invalid UUID formats
718        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-44665544000").is_err()); // Too short
719        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-4466554400000").is_err()); // Too long
720        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-44665544000g").is_err()); // Invalid character
721        assert!(Validator::validate_uuid("550e8400e29b41d4a716446655440000").is_ok()); // No dashes (valid)
722        assert!(Validator::validate_uuid("").is_err()); // Empty string
723        assert!(Validator::validate_uuid("not-a-uuid-at-all").is_err()); // Random string
724    }
725
726    #[test]
727    fn test_validate_non_empty_edge_cases() {
728        // Valid non-empty strings
729        assert!(Validator::validate_non_empty("valid", "field").is_ok());
730        assert!(Validator::validate_non_empty("a", "field").is_ok());
731        assert!(Validator::validate_non_empty("123", "field").is_ok());
732        assert!(Validator::validate_non_empty("special!@#$%^&*()", "field").is_ok());
733        assert!(Validator::validate_non_empty("  text  ", "field").is_ok()); // Whitespace around text is OK
734
735        // Invalid empty strings
736        let result = Validator::validate_non_empty("", "field");
737        assert!(result.is_err());
738        assert!(
739            result
740                .unwrap_err()
741                .message
742                .contains("field cannot be empty")
743        );
744
745        let result = Validator::validate_non_empty("   ", "field");
746        assert!(result.is_err());
747        assert!(
748            result
749                .unwrap_err()
750                .message
751                .contains("field cannot be empty")
752        );
753
754        let result = Validator::validate_non_empty("\t\n\r", "field");
755        assert!(result.is_err());
756        assert!(
757            result
758                .unwrap_err()
759                .message
760                .contains("field cannot be empty")
761        );
762
763        // Test with different field names
764        let result = Validator::validate_non_empty("", "tool_name");
765        assert!(result.is_err());
766        assert!(
767            result
768                .unwrap_err()
769                .message
770                .contains("tool_name cannot be empty")
771        );
772    }
773
774    #[test]
775    fn test_validate_tool_name_edge_cases() {
776        // Valid tool names
777        assert!(Validator::validate_tool_name("a").is_ok());
778        assert!(Validator::validate_tool_name("tool").is_ok());
779        assert!(Validator::validate_tool_name("tool_name").is_ok());
780        assert!(Validator::validate_tool_name("tool-name").is_ok());
781        assert!(Validator::validate_tool_name("tool123").is_ok());
782        assert!(Validator::validate_tool_name("123tool").is_ok());
783        assert!(Validator::validate_tool_name("Tool_Name-123").is_ok());
784        assert!(Validator::validate_tool_name("_tool").is_ok());
785        assert!(Validator::validate_tool_name("tool_").is_ok());
786        assert!(Validator::validate_tool_name("-tool").is_ok());
787        assert!(Validator::validate_tool_name("tool-").is_ok());
788
789        // Invalid tool names
790        let result = Validator::validate_tool_name("");
791        assert!(result.is_err());
792        assert!(
793            result
794                .unwrap_err()
795                .message
796                .contains("Tool name cannot be empty")
797        );
798
799        let result = Validator::validate_tool_name("   ");
800        assert!(result.is_err());
801        assert!(
802            result
803                .unwrap_err()
804                .message
805                .contains("Tool name cannot be empty")
806        );
807
808        let result = Validator::validate_tool_name("tool name");
809        assert!(result.is_err());
810        assert!(result.unwrap_err().message.contains(
811            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
812        ));
813
814        let result = Validator::validate_tool_name("tool.name");
815        assert!(result.is_err());
816        assert!(result.unwrap_err().message.contains(
817            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
818        ));
819
820        let result = Validator::validate_tool_name("tool@name");
821        assert!(result.is_err());
822        assert!(result.unwrap_err().message.contains(
823            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
824        ));
825
826        let result = Validator::validate_tool_name("tool/name");
827        assert!(result.is_err());
828        assert!(result.unwrap_err().message.contains(
829            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
830        ));
831    }
832
833    #[test]
834    fn test_validate_json_schema_edge_cases() {
835        // Valid schemas
836        let valid_schema = json!({"type": "object"});
837        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
838
839        let valid_schema = json!({
840            "type": "object",
841            "properties": {
842                "name": {"type": "string"}
843            }
844        });
845        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
846
847        let valid_schema = json!({
848            "type": "string",
849            "minLength": 1
850        });
851        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
852
853        // Invalid schemas
854        let result = Validator::validate_json_schema(&json!("not an object"));
855        assert!(result.is_err());
856        assert!(
857            result
858                .unwrap_err()
859                .message
860                .contains("JSON schema must be an object")
861        );
862
863        let result = Validator::validate_json_schema(&json!(123));
864        assert!(result.is_err());
865        assert!(
866            result
867                .unwrap_err()
868                .message
869                .contains("JSON schema must be an object")
870        );
871
872        let result = Validator::validate_json_schema(&json!([]));
873        assert!(result.is_err());
874        assert!(
875            result
876                .unwrap_err()
877                .message
878                .contains("JSON schema must be an object")
879        );
880
881        let result = Validator::validate_json_schema(&json!(null));
882        assert!(result.is_err());
883        assert!(
884            result
885                .unwrap_err()
886                .message
887                .contains("JSON schema must be an object")
888        );
889
890        let result = Validator::validate_json_schema(&json!({"properties": {}}));
891        assert!(result.is_err());
892        assert!(
893            result
894                .unwrap_err()
895                .message
896                .contains("JSON schema must have a 'type' field")
897        );
898
899        let result = Validator::validate_json_schema(&json!({}));
900        assert!(result.is_err());
901        assert!(
902            result
903                .unwrap_err()
904                .message
905                .contains("JSON schema must have a 'type' field")
906        );
907    }
908
909    #[test]
910    fn test_validate_pagination_edge_cases() {
911        // Valid pagination parameters
912        assert!(Validator::validate_pagination(None, None).is_ok());
913        assert!(Validator::validate_pagination(Some("cursor"), None).is_ok());
914        assert!(Validator::validate_pagination(None, Some(1)).is_ok());
915        assert!(Validator::validate_pagination(Some("cursor"), Some(1)).is_ok());
916        assert!(Validator::validate_pagination(Some("cursor"), Some(1000)).is_ok());
917        assert!(
918            Validator::validate_pagination(
919                Some("very-long-cursor-value-that-should-still-be-valid"),
920                Some(500)
921            )
922            .is_ok()
923        );
924
925        // Invalid cursor values
926        let result = Validator::validate_pagination(Some(""), None);
927        assert!(result.is_err());
928        assert!(
929            result
930                .unwrap_err()
931                .message
932                .contains("Cursor cannot be empty")
933        );
934
935        let result = Validator::validate_pagination(Some("   "), None);
936        assert!(result.is_err());
937        assert!(
938            result
939                .unwrap_err()
940                .message
941                .contains("Cursor cannot be empty")
942        );
943
944        let result = Validator::validate_pagination(Some("\t\n\r"), None);
945        assert!(result.is_err());
946        assert!(
947            result
948                .unwrap_err()
949                .message
950                .contains("Cursor cannot be empty")
951        );
952
953        // Invalid limit values
954        let result = Validator::validate_pagination(None, Some(0));
955        assert!(result.is_err());
956        assert!(
957            result
958                .unwrap_err()
959                .message
960                .contains("Limit must be greater than 0")
961        );
962
963        let result = Validator::validate_pagination(None, Some(1001));
964        assert!(result.is_err());
965        assert!(
966            result
967                .unwrap_err()
968                .message
969                .contains("Limit cannot exceed 1000")
970        );
971
972        let result = Validator::validate_pagination(None, Some(u32::MAX));
973        assert!(result.is_err());
974        assert!(
975            result
976                .unwrap_err()
977                .message
978                .contains("Limit cannot exceed 1000")
979        );
980
981        // Test with both invalid cursor and limit
982        let result = Validator::validate_pagination(Some(""), Some(0));
983        assert!(result.is_err());
984        // Should fail on cursor first
985        assert!(
986            result
987                .unwrap_err()
988                .message
989                .contains("Cursor cannot be empty")
990        );
991
992        let result = Validator::validate_pagination(Some("valid-cursor"), Some(0));
993        assert!(result.is_err());
994        assert!(
995            result
996                .unwrap_err()
997                .message
998                .contains("Limit must be greater than 0")
999        );
1000    }
1001}