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!(result
395            .unwrap_err()
396            .message
397            .contains("Required argument 'name' is missing"));
398
399        // Valid schema with multiple required fields
400        let schema = json!({
401            "type": "object",
402            "properties": {
403                "name": {"type": "string"},
404                "age": {"type": "number"},
405                "email": {"type": "string"}
406            },
407            "required": ["name", "email"]
408        });
409        let mut args = HashMap::new();
410        args.insert("name".to_string(), json!("John"));
411        args.insert("email".to_string(), json!("john@example.com"));
412        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
413
414        // Invalid - missing one required field
415        let mut args = HashMap::new();
416        args.insert("name".to_string(), json!("John"));
417        let result = Validator::validate_tool_arguments(&args, &schema);
418        assert!(result.is_err());
419        assert!(result
420            .unwrap_err()
421            .message
422            .contains("Required argument 'email' is missing"));
423
424        // Schema without properties
425        let schema = json!({
426            "type": "object"
427        });
428        let args = HashMap::new();
429        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
430
431        // Schema with empty required array
432        let schema = json!({
433            "type": "object",
434            "properties": {
435                "name": {"type": "string"}
436            },
437            "required": []
438        });
439        let args = HashMap::new();
440        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
441
442        // Schema with invalid required field (not a string)
443        let schema = json!({
444            "type": "object",
445            "properties": {
446                "name": {"type": "string"}
447            },
448            "required": [123]
449        });
450        let args = HashMap::new();
451        assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
452    }
453
454    #[test]
455    fn test_validate_structured_content() {
456        // Valid structured content
457        let content = json!({
458            "name": "John Doe",
459            "age": 30,
460            "email": "john@example.com"
461        });
462        let schema = json!({
463            "type": "object",
464            "properties": {
465                "name": {"type": "string"},
466                "age": {"type": "integer", "minimum": 0},
467                "email": {"type": "string", "format": "email"}
468            },
469            "required": ["name", "age"]
470        });
471
472        assert!(Validator::validate_structured_content(&content, &schema).is_ok());
473
474        // Invalid content - missing required field
475        let invalid_content = json!({
476            "name": "John Doe"
477        });
478        let result = Validator::validate_structured_content(&invalid_content, &schema);
479        assert!(result.is_err());
480        assert!(result.unwrap_err().message.contains("validation failed"));
481
482        // Invalid content - wrong type
483        let invalid_content = json!({
484            "name": "John Doe",
485            "age": "thirty"
486        });
487        let result = Validator::validate_structured_content(&invalid_content, &schema);
488        assert!(result.is_err());
489
490        // Invalid schema - this should be a basic validation before attempting to compile
491        let invalid_schema = json!({
492            "type": "invalid_type"
493        });
494        let result = Validator::validate_structured_content(&content, &invalid_schema);
495        assert!(result.is_err());
496        // The error message can vary, but it should indicate schema validation failure
497        let error_msg = result.unwrap_err().message;
498        assert!(error_msg.contains("JSON schema") || error_msg.contains("Invalid"));
499    }
500
501    #[test]
502    fn test_validate_tool_output_schema() {
503        // Valid object schema
504        let valid_object_schema = json!({
505            "type": "object",
506            "properties": {
507                "result": {"type": "string"},
508                "metadata": {"type": "object"}
509            }
510        });
511        assert!(Validator::validate_tool_output_schema(&valid_object_schema).is_ok());
512
513        // Valid array schema
514        let valid_array_schema = json!({
515            "type": "array",
516            "items": {"type": "string"}
517        });
518        assert!(Validator::validate_tool_output_schema(&valid_array_schema).is_ok());
519
520        // Invalid - primitive type
521        let invalid_primitive_schema = json!({
522            "type": "string"
523        });
524        let result = Validator::validate_tool_output_schema(&invalid_primitive_schema);
525        assert!(result.is_err());
526        assert!(result
527            .unwrap_err()
528            .message
529            .contains("should define structured data"));
530
531        // Invalid - object without properties
532        let invalid_object_schema = json!({
533            "type": "object"
534        });
535        let result = Validator::validate_tool_output_schema(&invalid_object_schema);
536        assert!(result.is_err());
537        assert!(result
538            .unwrap_err()
539            .message
540            .contains("must define properties"));
541
542        // Invalid - object with invalid properties
543        let invalid_props_schema = json!({
544            "type": "object",
545            "properties": "not an object"
546        });
547        let result = Validator::validate_tool_output_schema(&invalid_props_schema);
548        assert!(result.is_err());
549        assert!(result
550            .unwrap_err()
551            .message
552            .contains("properties must be an object"));
553
554        // Invalid - missing type field
555        let no_type_schema = json!({
556            "properties": {}
557        });
558        let result = Validator::validate_tool_output_schema(&no_type_schema);
559        assert!(result.is_err());
560        assert!(result
561            .unwrap_err()
562            .message
563            .contains("JSON schema must have a 'type' field"));
564    }
565
566    #[test]
567    fn test_structured_content_with_arrays() {
568        // Array content validation
569        let content = json!([
570            {"id": 1, "name": "Item 1"},
571            {"id": 2, "name": "Item 2"}
572        ]);
573        let schema = json!({
574            "type": "array",
575            "items": {
576                "type": "object",
577                "properties": {
578                    "id": {"type": "integer"},
579                    "name": {"type": "string"}
580                },
581                "required": ["id", "name"]
582            }
583        });
584
585        assert!(Validator::validate_structured_content(&content, &schema).is_ok());
586
587        // Invalid array content
588        let invalid_content = json!([
589            {"id": 1, "name": "Item 1"},
590            {"id": "not a number", "name": "Item 2"}
591        ]);
592        let result = Validator::validate_structured_content(&invalid_content, &schema);
593        assert!(result.is_err());
594    }
595
596    #[test]
597    fn test_nested_structured_content() {
598        // Nested object validation
599        let content = json!({
600            "user": {
601                "name": "John",
602                "profile": {
603                    "age": 30,
604                    "preferences": ["reading", "coding"]
605                }
606            },
607            "timestamp": "2023-01-01T00:00:00Z"
608        });
609
610        let schema = json!({
611            "type": "object",
612            "properties": {
613                "user": {
614                    "type": "object",
615                    "properties": {
616                        "name": {"type": "string"},
617                        "profile": {
618                            "type": "object",
619                            "properties": {
620                                "age": {"type": "integer"},
621                                "preferences": {
622                                    "type": "array",
623                                    "items": {"type": "string"}
624                                }
625                            },
626                            "required": ["age"]
627                        }
628                    },
629                    "required": ["name", "profile"]
630                },
631                "timestamp": {"type": "string"}
632            },
633            "required": ["user"]
634        });
635
636        assert!(Validator::validate_structured_content(&content, &schema).is_ok());
637
638        // Invalid nested content
639        let invalid_content = json!({
640            "user": {
641                "name": "John",
642                "profile": {
643                    "preferences": ["reading", "coding"]
644                    // Missing required "age" field
645                }
646            }
647        });
648        let result = Validator::validate_structured_content(&invalid_content, &schema);
649        assert!(result.is_err());
650    }
651
652    #[test]
653    fn test_format_validation_errors() {
654        // This is a basic test since we can't easily create ValidationError instances
655        // The function is mainly for internal use
656        let empty_errors = std::iter::empty();
657        let result = Validator::format_validation_errors(empty_errors);
658        assert_eq!(result, "Unknown validation error");
659    }
660
661    #[test]
662    fn test_call_tool_result_structured_validation() {
663        use crate::model::{CallToolResult, Content};
664
665        // Valid structured content
666        let structured_data = json!({
667            "result": "success",
668            "data": {"count": 42}
669        });
670        let schema = json!({
671            "type": "object",
672            "properties": {
673                "result": {"type": "string"},
674                "data": {"type": "object"}
675            },
676            "required": ["result"]
677        });
678
679        let result =
680            CallToolResult::structured(vec![Content::text("Operation completed")], structured_data);
681
682        assert!(result.validate_structured_content(&schema).is_ok());
683
684        // Invalid structured content
685        let invalid_data = json!({
686            "result": 123 // Should be string
687        });
688        let invalid_result =
689            CallToolResult::structured(vec![Content::text("Operation completed")], invalid_data);
690
691        assert!(invalid_result.validate_structured_content(&schema).is_err());
692
693        // Result without structured content should pass validation
694        let simple_result = CallToolResult::text("Simple result");
695        assert!(simple_result.validate_structured_content(&schema).is_ok());
696    }
697
698    #[test]
699    fn test_validate_uuid_edge_cases() {
700        // Valid UUID formats
701        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-446655440000").is_ok());
702        assert!(Validator::validate_uuid("6ba7b810-9dad-11d1-80b4-00c04fd430c8").is_ok());
703        assert!(Validator::validate_uuid("123e4567-e89b-12d3-a456-426614174000").is_ok());
704
705        // Invalid UUID formats
706        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-44665544000").is_err()); // Too short
707        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-4466554400000").is_err()); // Too long
708        assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-44665544000g").is_err()); // Invalid character
709        assert!(Validator::validate_uuid("550e8400e29b41d4a716446655440000").is_ok()); // No dashes (valid)
710        assert!(Validator::validate_uuid("").is_err()); // Empty string
711        assert!(Validator::validate_uuid("not-a-uuid-at-all").is_err()); // Random string
712    }
713
714    #[test]
715    fn test_validate_non_empty_edge_cases() {
716        // Valid non-empty strings
717        assert!(Validator::validate_non_empty("valid", "field").is_ok());
718        assert!(Validator::validate_non_empty("a", "field").is_ok());
719        assert!(Validator::validate_non_empty("123", "field").is_ok());
720        assert!(Validator::validate_non_empty("special!@#$%^&*()", "field").is_ok());
721        assert!(Validator::validate_non_empty("  text  ", "field").is_ok()); // Whitespace around text is OK
722
723        // Invalid empty strings
724        let result = Validator::validate_non_empty("", "field");
725        assert!(result.is_err());
726        assert!(result
727            .unwrap_err()
728            .message
729            .contains("field cannot be empty"));
730
731        let result = Validator::validate_non_empty("   ", "field");
732        assert!(result.is_err());
733        assert!(result
734            .unwrap_err()
735            .message
736            .contains("field cannot be empty"));
737
738        let result = Validator::validate_non_empty("\t\n\r", "field");
739        assert!(result.is_err());
740        assert!(result
741            .unwrap_err()
742            .message
743            .contains("field cannot be empty"));
744
745        // Test with different field names
746        let result = Validator::validate_non_empty("", "tool_name");
747        assert!(result.is_err());
748        assert!(result
749            .unwrap_err()
750            .message
751            .contains("tool_name cannot be empty"));
752    }
753
754    #[test]
755    fn test_validate_tool_name_edge_cases() {
756        // Valid tool names
757        assert!(Validator::validate_tool_name("a").is_ok());
758        assert!(Validator::validate_tool_name("tool").is_ok());
759        assert!(Validator::validate_tool_name("tool_name").is_ok());
760        assert!(Validator::validate_tool_name("tool-name").is_ok());
761        assert!(Validator::validate_tool_name("tool123").is_ok());
762        assert!(Validator::validate_tool_name("123tool").is_ok());
763        assert!(Validator::validate_tool_name("Tool_Name-123").is_ok());
764        assert!(Validator::validate_tool_name("_tool").is_ok());
765        assert!(Validator::validate_tool_name("tool_").is_ok());
766        assert!(Validator::validate_tool_name("-tool").is_ok());
767        assert!(Validator::validate_tool_name("tool-").is_ok());
768
769        // Invalid tool names
770        let result = Validator::validate_tool_name("");
771        assert!(result.is_err());
772        assert!(result
773            .unwrap_err()
774            .message
775            .contains("Tool name cannot be empty"));
776
777        let result = Validator::validate_tool_name("   ");
778        assert!(result.is_err());
779        assert!(result
780            .unwrap_err()
781            .message
782            .contains("Tool name cannot be empty"));
783
784        let result = Validator::validate_tool_name("tool name");
785        assert!(result.is_err());
786        assert!(result.unwrap_err().message.contains(
787            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
788        ));
789
790        let result = Validator::validate_tool_name("tool.name");
791        assert!(result.is_err());
792        assert!(result.unwrap_err().message.contains(
793            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
794        ));
795
796        let result = Validator::validate_tool_name("tool@name");
797        assert!(result.is_err());
798        assert!(result.unwrap_err().message.contains(
799            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
800        ));
801
802        let result = Validator::validate_tool_name("tool/name");
803        assert!(result.is_err());
804        assert!(result.unwrap_err().message.contains(
805            "Tool name must contain only alphanumeric characters, underscores, and hyphens"
806        ));
807    }
808
809    #[test]
810    fn test_validate_json_schema_edge_cases() {
811        // Valid schemas
812        let valid_schema = json!({"type": "object"});
813        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
814
815        let valid_schema = json!({
816            "type": "object",
817            "properties": {
818                "name": {"type": "string"}
819            }
820        });
821        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
822
823        let valid_schema = json!({
824            "type": "string",
825            "minLength": 1
826        });
827        assert!(Validator::validate_json_schema(&valid_schema).is_ok());
828
829        // Invalid schemas
830        let result = Validator::validate_json_schema(&json!("not an object"));
831        assert!(result.is_err());
832        assert!(result
833            .unwrap_err()
834            .message
835            .contains("JSON schema must be an object"));
836
837        let result = Validator::validate_json_schema(&json!(123));
838        assert!(result.is_err());
839        assert!(result
840            .unwrap_err()
841            .message
842            .contains("JSON schema must be an object"));
843
844        let result = Validator::validate_json_schema(&json!([]));
845        assert!(result.is_err());
846        assert!(result
847            .unwrap_err()
848            .message
849            .contains("JSON schema must be an object"));
850
851        let result = Validator::validate_json_schema(&json!(null));
852        assert!(result.is_err());
853        assert!(result
854            .unwrap_err()
855            .message
856            .contains("JSON schema must be an object"));
857
858        let result = Validator::validate_json_schema(&json!({"properties": {}}));
859        assert!(result.is_err());
860        assert!(result
861            .unwrap_err()
862            .message
863            .contains("JSON schema must have a 'type' field"));
864
865        let result = Validator::validate_json_schema(&json!({}));
866        assert!(result.is_err());
867        assert!(result
868            .unwrap_err()
869            .message
870            .contains("JSON schema must have a 'type' field"));
871    }
872
873    #[test]
874    fn test_validate_pagination_edge_cases() {
875        // Valid pagination parameters
876        assert!(Validator::validate_pagination(None, None).is_ok());
877        assert!(Validator::validate_pagination(Some("cursor"), None).is_ok());
878        assert!(Validator::validate_pagination(None, Some(1)).is_ok());
879        assert!(Validator::validate_pagination(Some("cursor"), Some(1)).is_ok());
880        assert!(Validator::validate_pagination(Some("cursor"), Some(1000)).is_ok());
881        assert!(Validator::validate_pagination(
882            Some("very-long-cursor-value-that-should-still-be-valid"),
883            Some(500)
884        )
885        .is_ok());
886
887        // Invalid cursor values
888        let result = Validator::validate_pagination(Some(""), None);
889        assert!(result.is_err());
890        assert!(result
891            .unwrap_err()
892            .message
893            .contains("Cursor cannot be empty"));
894
895        let result = Validator::validate_pagination(Some("   "), None);
896        assert!(result.is_err());
897        assert!(result
898            .unwrap_err()
899            .message
900            .contains("Cursor cannot be empty"));
901
902        let result = Validator::validate_pagination(Some("\t\n\r"), None);
903        assert!(result.is_err());
904        assert!(result
905            .unwrap_err()
906            .message
907            .contains("Cursor cannot be empty"));
908
909        // Invalid limit values
910        let result = Validator::validate_pagination(None, Some(0));
911        assert!(result.is_err());
912        assert!(result
913            .unwrap_err()
914            .message
915            .contains("Limit must be greater than 0"));
916
917        let result = Validator::validate_pagination(None, Some(1001));
918        assert!(result.is_err());
919        assert!(result
920            .unwrap_err()
921            .message
922            .contains("Limit cannot exceed 1000"));
923
924        let result = Validator::validate_pagination(None, Some(u32::MAX));
925        assert!(result.is_err());
926        assert!(result
927            .unwrap_err()
928            .message
929            .contains("Limit cannot exceed 1000"));
930
931        // Test with both invalid cursor and limit
932        let result = Validator::validate_pagination(Some(""), Some(0));
933        assert!(result.is_err());
934        // Should fail on cursor first
935        assert!(result
936            .unwrap_err()
937            .message
938            .contains("Cursor cannot be empty"));
939
940        let result = Validator::validate_pagination(Some("valid-cursor"), Some(0));
941        assert!(result.is_err());
942        assert!(result
943            .unwrap_err()
944            .message
945            .contains("Limit must be greater than 0"));
946    }
947}