pulseengine_mcp_protocol/
validation.rs1use crate::{Error, Result};
4use jsonschema::{JSONSchema, ValidationError};
5use serde_json::Value;
6use std::collections::HashMap;
7use uuid::Uuid;
8use validator::Validate;
9
10pub struct Validator;
12
13impl Validator {
14 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 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 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 pub fn validate_resource_uri(uri: &str) -> Result<()> {
66 Self::validate_non_empty(uri, "Resource URI")?;
67
68 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 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 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 pub fn is_ui_resource_uri(uri: &str) -> bool {
106 uri.starts_with("ui://")
107 }
108
109 pub fn validate_json_schema(schema: &Value) -> Result<()> {
115 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 pub fn validate_tool_arguments(args: &HashMap<String, Value>, schema: &Value) -> Result<()> {
135 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 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 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 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 pub fn validate_structured_content(content: &Value, output_schema: &Value) -> Result<()> {
213 Self::validate_json_schema(output_schema)?;
215
216 let schema = JSONSchema::compile(output_schema)
218 .map_err(|e| Error::validation_error(format!("Invalid JSON schema: {e}")))?;
219
220 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 pub fn validate_tool_output_schema(output_schema: &Value) -> Result<()> {
240 Self::validate_json_schema(output_schema)?;
242
243 if let Some(obj) = output_schema.as_object() {
245 if let Some(schema_type) = obj.get("type").and_then(|t| t.as_str()) {
247 match schema_type {
248 "object" | "array" => {
249 }
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 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 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 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 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 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 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 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 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 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 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 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 let schema = json!({
461 "type": "object"
462 });
463 let args = HashMap::new();
464 assert!(Validator::validate_tool_arguments(&args, &schema).is_ok());
465
466 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let invalid_content = json!({
683 "user": {
684 "name": "John",
685 "profile": {
686 "preferences": ["reading", "coding"]
687 }
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 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 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 let invalid_data = json!({
729 "result": 123 });
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 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 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 assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-44665544000").is_err()); assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-4466554400000").is_err()); assert!(Validator::validate_uuid("550e8400-e29b-41d4-a716-44665544000g").is_err()); assert!(Validator::validate_uuid("550e8400e29b41d4a716446655440000").is_ok()); assert!(Validator::validate_uuid("").is_err()); assert!(Validator::validate_uuid("not-a-uuid-at-all").is_err()); }
756
757 #[test]
758 fn test_validate_non_empty_edge_cases() {
759 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()); 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 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 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 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 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 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 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 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 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 let result = Validator::validate_pagination(Some(""), Some(0));
1014 assert!(result.is_err());
1015 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}