contextvm_sdk/core/
validation.rs1use crate::core::constants::MAX_MESSAGE_SIZE;
4use crate::core::types::JsonRpcMessage;
5
6pub fn validate_message_size(content: &str) -> bool {
8 content.len() <= MAX_MESSAGE_SIZE
9}
10
11pub fn validate_and_parse(content: &str) -> Option<JsonRpcMessage> {
13 if !validate_message_size(content) {
14 tracing::warn!("Message size validation failed: {} bytes", content.len());
15 return None;
16 }
17
18 let value: serde_json::Value = serde_json::from_str(content).ok()?;
19 validate_message(&value)
20}
21
22pub fn validate_and_parse_oversized(content: &str, max_bytes: usize) -> Option<JsonRpcMessage> {
32 if content.len() > max_bytes {
33 tracing::warn!(
34 "Oversized message exceeds max transfer bytes: {} > {}",
35 content.len(),
36 max_bytes
37 );
38 return None;
39 }
40
41 let value: serde_json::Value = serde_json::from_str(content).ok()?;
42 validate_message(&value)
43}
44
45pub fn validate_message(value: &serde_json::Value) -> Option<JsonRpcMessage> {
51 let jsonrpc = value.get("jsonrpc")?.as_str()?;
53 if jsonrpc != "2.0" {
54 return None;
55 }
56
57 serde_json::from_value(value.clone()).ok()
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use serde_json::json;
64
65 #[test]
66 fn test_valid_request() {
67 let msg = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/list"});
68 let parsed = validate_message(&msg).unwrap();
69 assert!(parsed.is_request());
70 }
71
72 #[test]
73 fn test_valid_notification() {
74 let msg = json!({"jsonrpc": "2.0", "method": "notifications/initialized"});
75 let parsed = validate_message(&msg).unwrap();
76 assert!(parsed.is_notification());
77 }
78
79 #[test]
80 fn test_valid_response() {
81 let msg = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
82 let parsed = validate_message(&msg).unwrap();
83 assert!(parsed.is_response());
84 }
85
86 #[test]
87 fn test_invalid_version() {
88 let msg = json!({"jsonrpc": "1.0", "id": 1, "method": "test"});
89 assert!(validate_message(&msg).is_none());
90 }
91
92 #[test]
93 fn test_size_validation() {
94 assert!(validate_message_size("hello"));
95 let big = "x".repeat(MAX_MESSAGE_SIZE + 1);
96 assert!(!validate_message_size(&big));
97 }
98
99 #[test]
100 fn test_validate_and_parse_valid_request() {
101 let content = r#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
102 let msg = validate_and_parse(content).unwrap();
103 assert!(msg.is_request());
104 assert_eq!(msg.method(), Some("tools/list"));
105 }
106
107 #[test]
108 fn test_validate_and_parse_rejects_oversized() {
109 let padding = "x".repeat(MAX_MESSAGE_SIZE);
110 let content = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"{}"}}"#, padding);
111 assert!(validate_and_parse(&content).is_none());
112 }
113
114 #[test]
115 fn test_validate_and_parse_rejects_invalid_version() {
116 let content = r#"{"jsonrpc":"1.0","id":1,"method":"test"}"#;
117 assert!(validate_and_parse(content).is_none());
118 }
119
120 #[test]
121 fn test_validate_and_parse_rejects_invalid_json() {
122 assert!(validate_and_parse("not json").is_none());
123 }
124
125 #[test]
126 fn test_validate_and_parse_oversized_bypasses_1mb_cap() {
127 let big_value = "m".repeat(MAX_MESSAGE_SIZE + 1024);
129 let content = format!(r#"{{"jsonrpc":"2.0","id":1,"result":{{"value":"{big_value}"}}}}"#);
130 assert!(content.len() > MAX_MESSAGE_SIZE);
131
132 assert!(validate_and_parse(&content).is_none());
134 let parsed = validate_and_parse_oversized(&content, 100 * 1024 * 1024).unwrap();
136 assert!(parsed.is_response());
137 }
138
139 #[test]
140 fn test_validate_and_parse_oversized_enforces_its_own_bound() {
141 let content = r#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
142 assert!(validate_and_parse_oversized(content, 8).is_none());
144 assert!(validate_and_parse_oversized(content, 1024)
146 .unwrap()
147 .is_request());
148 }
149
150 #[test]
151 fn test_validate_and_parse_oversized_rejects_invalid_version() {
152 let content = r#"{"jsonrpc":"1.0","id":1,"method":"test"}"#;
153 assert!(validate_and_parse_oversized(content, 1024).is_none());
154 }
155}