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