1use std::borrow::Cow;
4use std::collections::BTreeSet;
5
6use serde::de::Error as _;
7use serde::ser::Error as _;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use serde_json::Value;
10use serde_json::value::RawValue;
11
12use crate::common_types::JsonInteger;
13
14pub const JSONRPC_VERSION: &str = "2.0";
16
17pub const MAX_JSONRPC_STRING_ID_ENCODED_BYTES: usize = 256;
19
20pub const MAX_RAW_JSON_NESTING_DEPTH: usize = 64;
22pub const MAX_RAW_JSON_CONTAINER_ENTRIES: usize = 100_000;
24pub const MAX_RAW_JSON_NUMBER_BYTES: usize = 4 * 1024;
26pub const MAX_RAW_JSON_AGGREGATE_NUMBER_BYTES: usize = 256 * 1024;
28pub const MAX_RAW_JSON_EXPONENT: usize = 10_000;
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum RawJsonAdmissionError {
34 DocumentTooLarge,
35 InvalidUtf8,
36 ByteOrderMark,
37 InvalidSyntax,
38 TopLevelBatch,
39 TopLevelNotObject,
40 DuplicateObjectMember,
41 NestingTooDeep,
42 TooManyContainerEntries,
43 NumberTooLong,
44 TooManyNumberBytes,
45 ExponentTooLarge,
46 TooManyDecodedStringBytes,
47}
48
49impl std::fmt::Display for RawJsonAdmissionError {
50 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 let message = match self {
52 Self::DocumentTooLarge => "JSON document exceeds the configured byte limit",
53 Self::InvalidUtf8 => "JSON document is not strict UTF-8",
54 Self::ByteOrderMark => "JSON document contains a UTF-8 byte-order mark",
55 Self::InvalidSyntax => "invalid JSON syntax during raw admission",
56 Self::TopLevelBatch => "JSON-RPC batch arrays are not supported",
57 Self::TopLevelNotObject => "JSON-RPC top-level value must be an object",
58 Self::DuplicateObjectMember => "duplicate JSON object member",
59 Self::NestingTooDeep => "JSON nesting limit exceeded",
60 Self::TooManyContainerEntries => "JSON container-entry limit exceeded",
61 Self::NumberTooLong => "JSON number-token limit exceeded",
62 Self::TooManyNumberBytes => "aggregate JSON number-byte limit exceeded",
63 Self::ExponentTooLarge => "JSON exponent limit exceeded",
64 Self::TooManyDecodedStringBytes => "decoded JSON string-byte limit exceeded",
65 };
66 formatter.write_str(message)
67 }
68}
69
70impl std::error::Error for RawJsonAdmissionError {}
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub enum JsonRpcAdmissionError {
75 Raw(RawJsonAdmissionError),
77 InvalidEnvelope,
79}
80
81impl std::fmt::Display for JsonRpcAdmissionError {
82 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 Self::Raw(error) => error.fmt(formatter),
85 Self::InvalidEnvelope => formatter.write_str("invalid JSON-RPC envelope"),
86 }
87 }
88}
89
90impl std::error::Error for JsonRpcAdmissionError {}
91
92pub fn admit_raw_jsonrpc_document(
100 bytes: &[u8],
101 document_byte_limit: usize,
102) -> Result<(), RawJsonAdmissionError> {
103 if bytes.len() > document_byte_limit {
104 return Err(RawJsonAdmissionError::DocumentTooLarge);
105 }
106 if bytes.windows(3).any(|window| window == [0xef, 0xbb, 0xbf]) {
107 return Err(RawJsonAdmissionError::ByteOrderMark);
108 }
109 let input = std::str::from_utf8(bytes).map_err(|_| RawJsonAdmissionError::InvalidUtf8)?;
110 let mut scanner = RawJsonScanner::new(input, document_byte_limit);
111 scanner.skip_whitespace();
112 match scanner.peek() {
113 Some(b'{') => scanner.parse_object(0)?,
114 Some(b'[') => return Err(RawJsonAdmissionError::TopLevelBatch),
115 _ => return Err(RawJsonAdmissionError::TopLevelNotObject),
116 }
117 scanner.skip_whitespace();
118 if scanner.position != scanner.bytes.len() {
119 return Err(RawJsonAdmissionError::InvalidSyntax);
120 }
121 Ok(())
122}
123
124pub fn decode_strict_jsonrpc_message(
126 bytes: &[u8],
127 document_byte_limit: usize,
128) -> Result<JsonRpcMessage, JsonRpcAdmissionError> {
129 admit_raw_jsonrpc_document(bytes, document_byte_limit).map_err(JsonRpcAdmissionError::Raw)?;
130 match serde_json::from_slice::<JsonRpcRequest>(bytes) {
131 Ok(request) => Ok(JsonRpcMessage::Request(request)),
132 Err(_) => serde_json::from_slice::<JsonRpcResponse>(bytes)
133 .map(JsonRpcMessage::Response)
134 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope),
135 }
136}
137
138impl JsonRpcRequest {
139 pub fn decode_strict_with_raw_params(
154 bytes: &[u8],
155 document_byte_limit: usize,
156 ) -> Result<(Self, Option<String>), JsonRpcAdmissionError> {
157 admit_raw_jsonrpc_document(bytes, document_byte_limit)
158 .map_err(JsonRpcAdmissionError::Raw)?;
159 let wire =
160 JsonRpcRequestRawWire::deserialize(&mut serde_json::Deserializer::from_slice(bytes))
161 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)?;
162 let raw_params = wire.params.as_deref().map(RawValue::get).map(str::to_owned);
163 let params = raw_params
164 .as_deref()
165 .map(|source| {
166 crate::messages::validate_raw_final_completion_params(&wire.method, source)
167 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)?;
168 serde_json::from_str(source).map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)
169 })
170 .transpose()?;
171 let request = Self {
172 jsonrpc: wire.jsonrpc,
173 method: wire.method,
174 params,
175 id: wire.id,
176 };
177 request
178 .validate()
179 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)?;
180 Ok((request, raw_params))
181 }
182}
183
184#[derive(Debug, Clone, PartialEq)]
192pub struct JsonRpcResponseAdmission {
193 response: JsonRpcResponse,
194 raw_result: Option<String>,
195}
196
197impl JsonRpcResponseAdmission {
198 #[must_use]
200 pub const fn response(&self) -> &JsonRpcResponse {
201 &self.response
202 }
203
204 #[must_use]
207 pub fn raw_result(&self) -> Option<&str> {
208 self.raw_result.as_deref()
209 }
210
211 #[must_use]
213 pub fn into_parts(self) -> (JsonRpcResponse, Option<String>) {
214 (self.response, self.raw_result)
215 }
216}
217
218pub fn decode_strict_jsonrpc_response(
226 bytes: &[u8],
227 document_byte_limit: usize,
228) -> Result<JsonRpcResponseAdmission, JsonRpcAdmissionError> {
229 admit_raw_jsonrpc_document(bytes, document_byte_limit).map_err(JsonRpcAdmissionError::Raw)?;
230 let wire =
231 JsonRpcResponseRawWire::deserialize(&mut serde_json::Deserializer::from_slice(bytes))
232 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)?;
233 let raw_result = wire.result.map(|result| result.get().to_owned());
234 let result = raw_result
235 .as_deref()
236 .map(serde_json::from_str)
237 .transpose()
238 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)?;
239 let response = JsonRpcResponse {
240 jsonrpc: wire.jsonrpc,
241 result,
242 error: wire.error,
243 id: wire.id,
244 };
245 response
246 .validate()
247 .map_err(|_| JsonRpcAdmissionError::InvalidEnvelope)?;
248 Ok(JsonRpcResponseAdmission {
249 response,
250 raw_result,
251 })
252}
253
254struct RawJsonScanner<'a> {
255 input: &'a str,
256 bytes: &'a [u8],
257 position: usize,
258 container_entries: usize,
259 number_bytes: usize,
260 decoded_string_bytes: usize,
261 decoded_string_byte_limit: usize,
262}
263
264impl<'a> RawJsonScanner<'a> {
265 fn new(input: &'a str, decoded_string_byte_limit: usize) -> Self {
266 Self {
267 input,
268 bytes: input.as_bytes(),
269 position: 0,
270 container_entries: 0,
271 number_bytes: 0,
272 decoded_string_bytes: 0,
273 decoded_string_byte_limit,
274 }
275 }
276
277 fn parse_value(&mut self, depth: usize) -> Result<(), RawJsonAdmissionError> {
278 match self.peek() {
279 Some(b'{') => self.parse_object(depth),
280 Some(b'[') => self.parse_array(depth),
281 Some(b'"') => self.parse_string(false).map(|_| ()),
282 Some(b't') => self.parse_literal(b"true"),
283 Some(b'f') => self.parse_literal(b"false"),
284 Some(b'n') => self.parse_literal(b"null"),
285 Some(b'-' | b'0'..=b'9') => self.parse_number(),
286 _ => Err(RawJsonAdmissionError::InvalidSyntax),
287 }
288 }
289
290 fn parse_object(&mut self, depth: usize) -> Result<(), RawJsonAdmissionError> {
291 let nested_depth = self.enter_container(depth)?;
292 self.position += 1;
293 self.skip_whitespace();
294 if self.consume(b'}') {
295 return Ok(());
296 }
297
298 let mut names = BTreeSet::new();
299 loop {
300 self.charge_container_entry()?;
301 let name = self
302 .parse_string(true)?
303 .ok_or(RawJsonAdmissionError::InvalidSyntax)?;
304 if !names.insert(name) {
305 return Err(RawJsonAdmissionError::DuplicateObjectMember);
306 }
307 self.skip_whitespace();
308 if !self.consume(b':') {
309 return Err(RawJsonAdmissionError::InvalidSyntax);
310 }
311 self.skip_whitespace();
312 self.parse_value(nested_depth)?;
313 self.skip_whitespace();
314 if self.consume(b'}') {
315 return Ok(());
316 }
317 if !self.consume(b',') {
318 return Err(RawJsonAdmissionError::InvalidSyntax);
319 }
320 self.skip_whitespace();
321 }
322 }
323
324 fn parse_array(&mut self, depth: usize) -> Result<(), RawJsonAdmissionError> {
325 let nested_depth = self.enter_container(depth)?;
326 self.position += 1;
327 self.skip_whitespace();
328 if self.consume(b']') {
329 return Ok(());
330 }
331 loop {
332 self.charge_container_entry()?;
333 self.parse_value(nested_depth)?;
334 self.skip_whitespace();
335 if self.consume(b']') {
336 return Ok(());
337 }
338 if !self.consume(b',') {
339 return Err(RawJsonAdmissionError::InvalidSyntax);
340 }
341 self.skip_whitespace();
342 }
343 }
344
345 fn enter_container(&self, depth: usize) -> Result<usize, RawJsonAdmissionError> {
346 let nested_depth = depth
347 .checked_add(1)
348 .ok_or(RawJsonAdmissionError::NestingTooDeep)?;
349 if nested_depth > MAX_RAW_JSON_NESTING_DEPTH {
350 Err(RawJsonAdmissionError::NestingTooDeep)
351 } else {
352 Ok(nested_depth)
353 }
354 }
355
356 fn charge_container_entry(&mut self) -> Result<(), RawJsonAdmissionError> {
357 self.container_entries = self
358 .container_entries
359 .checked_add(1)
360 .ok_or(RawJsonAdmissionError::TooManyContainerEntries)?;
361 if self.container_entries > MAX_RAW_JSON_CONTAINER_ENTRIES {
362 Err(RawJsonAdmissionError::TooManyContainerEntries)
363 } else {
364 Ok(())
365 }
366 }
367
368 fn parse_string(&mut self, capture: bool) -> Result<Option<String>, RawJsonAdmissionError> {
369 if !self.consume(b'"') {
370 return Err(RawJsonAdmissionError::InvalidSyntax);
371 }
372 let mut decoded = capture.then(String::new);
373 loop {
374 let byte = self.peek().ok_or(RawJsonAdmissionError::InvalidSyntax)?;
375 match byte {
376 b'"' => {
377 self.position += 1;
378 return Ok(decoded);
379 }
380 b'\\' => {
381 self.position += 1;
382 let character = self.parse_escape()?;
383 self.charge_string_bytes(character.len_utf8())?;
384 if let Some(value) = decoded.as_mut() {
385 value.push(character);
386 }
387 }
388 0x00..=0x1f => return Err(RawJsonAdmissionError::InvalidSyntax),
389 0x20..=0x7f => {
390 self.position += 1;
391 self.charge_string_bytes(1)?;
392 if let Some(value) = decoded.as_mut() {
393 value.push(char::from(byte));
394 }
395 }
396 _ => {
397 let character = self.input[self.position..]
398 .chars()
399 .next()
400 .ok_or(RawJsonAdmissionError::InvalidSyntax)?;
401 self.position += character.len_utf8();
402 self.charge_string_bytes(character.len_utf8())?;
403 if let Some(value) = decoded.as_mut() {
404 value.push(character);
405 }
406 }
407 }
408 }
409 }
410
411 fn parse_escape(&mut self) -> Result<char, RawJsonAdmissionError> {
412 let escape = self.peek().ok_or(RawJsonAdmissionError::InvalidSyntax)?;
413 self.position += 1;
414 match escape {
415 b'"' => Ok('"'),
416 b'\\' => Ok('\\'),
417 b'/' => Ok('/'),
418 b'b' => Ok('\u{0008}'),
419 b'f' => Ok('\u{000c}'),
420 b'n' => Ok('\n'),
421 b'r' => Ok('\r'),
422 b't' => Ok('\t'),
423 b'u' => self.parse_unicode_escape(),
424 _ => Err(RawJsonAdmissionError::InvalidSyntax),
425 }
426 }
427
428 fn parse_unicode_escape(&mut self) -> Result<char, RawJsonAdmissionError> {
429 let first = self.parse_hex_quad()?;
430 let scalar = if (0xd800..=0xdbff).contains(&first) {
431 if !self.consume(b'\\') || !self.consume(b'u') {
432 return Err(RawJsonAdmissionError::InvalidSyntax);
433 }
434 let second = self.parse_hex_quad()?;
435 if !(0xdc00..=0xdfff).contains(&second) {
436 return Err(RawJsonAdmissionError::InvalidSyntax);
437 }
438 0x1_0000 + ((u32::from(first) - 0xd800) << 10) + u32::from(second) - 0xdc00
439 } else if (0xdc00..=0xdfff).contains(&first) {
440 return Err(RawJsonAdmissionError::InvalidSyntax);
441 } else {
442 u32::from(first)
443 };
444 char::from_u32(scalar).ok_or(RawJsonAdmissionError::InvalidSyntax)
445 }
446
447 fn parse_hex_quad(&mut self) -> Result<u16, RawJsonAdmissionError> {
448 let end = self
449 .position
450 .checked_add(4)
451 .ok_or(RawJsonAdmissionError::InvalidSyntax)?;
452 let digits = self
453 .bytes
454 .get(self.position..end)
455 .ok_or(RawJsonAdmissionError::InvalidSyntax)?;
456 let mut value = 0_u16;
457 for digit in digits {
458 let nibble = match digit {
459 b'0'..=b'9' => u16::from(*digit - b'0'),
460 b'a'..=b'f' => u16::from(*digit - b'a' + 10),
461 b'A'..=b'F' => u16::from(*digit - b'A' + 10),
462 _ => return Err(RawJsonAdmissionError::InvalidSyntax),
463 };
464 value = (value << 4) | nibble;
465 }
466 self.position = end;
467 Ok(value)
468 }
469
470 fn parse_literal(&mut self, literal: &[u8]) -> Result<(), RawJsonAdmissionError> {
471 let end = self
472 .position
473 .checked_add(literal.len())
474 .ok_or(RawJsonAdmissionError::InvalidSyntax)?;
475 if self.bytes.get(self.position..end) == Some(literal) {
476 self.position = end;
477 Ok(())
478 } else {
479 Err(RawJsonAdmissionError::InvalidSyntax)
480 }
481 }
482
483 fn parse_number(&mut self) -> Result<(), RawJsonAdmissionError> {
484 let start = self.position;
485 self.consume(b'-');
486 match self.peek() {
487 Some(b'0') => {
488 self.position += 1;
489 if matches!(self.peek(), Some(b'0'..=b'9')) {
490 return Err(RawJsonAdmissionError::InvalidSyntax);
491 }
492 }
493 Some(b'1'..=b'9') => {
494 self.position += 1;
495 self.consume_digits();
496 }
497 _ => return Err(RawJsonAdmissionError::InvalidSyntax),
498 }
499 if self.consume(b'.') {
500 if !matches!(self.peek(), Some(b'0'..=b'9')) {
501 return Err(RawJsonAdmissionError::InvalidSyntax);
502 }
503 self.consume_digits();
504 }
505 if matches!(self.peek(), Some(b'e' | b'E')) {
506 self.position += 1;
507 if matches!(self.peek(), Some(b'+' | b'-')) {
508 self.position += 1;
509 }
510 let exponent_start = self.position;
511 if !matches!(self.peek(), Some(b'0'..=b'9')) {
512 return Err(RawJsonAdmissionError::InvalidSyntax);
513 }
514 self.consume_digits();
515 if exponent_exceeds_raw_limit(&self.bytes[exponent_start..self.position]) {
516 return Err(RawJsonAdmissionError::ExponentTooLarge);
517 }
518 }
519 let length = self.position - start;
520 if length > MAX_RAW_JSON_NUMBER_BYTES {
521 return Err(RawJsonAdmissionError::NumberTooLong);
522 }
523 self.number_bytes = self
524 .number_bytes
525 .checked_add(length)
526 .ok_or(RawJsonAdmissionError::TooManyNumberBytes)?;
527 if self.number_bytes > MAX_RAW_JSON_AGGREGATE_NUMBER_BYTES {
528 Err(RawJsonAdmissionError::TooManyNumberBytes)
529 } else {
530 Ok(())
531 }
532 }
533
534 fn consume_digits(&mut self) {
535 while matches!(self.peek(), Some(b'0'..=b'9')) {
536 self.position += 1;
537 }
538 }
539 fn charge_string_bytes(&mut self, bytes: usize) -> Result<(), RawJsonAdmissionError> {
540 self.decoded_string_bytes = self
541 .decoded_string_bytes
542 .checked_add(bytes)
543 .ok_or(RawJsonAdmissionError::TooManyDecodedStringBytes)?;
544 if self.decoded_string_bytes > self.decoded_string_byte_limit {
545 Err(RawJsonAdmissionError::TooManyDecodedStringBytes)
546 } else {
547 Ok(())
548 }
549 }
550 fn skip_whitespace(&mut self) {
551 while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
552 self.position += 1;
553 }
554 }
555 fn consume(&mut self, expected: u8) -> bool {
556 if self.peek() == Some(expected) {
557 self.position += 1;
558 true
559 } else {
560 false
561 }
562 }
563 fn peek(&self) -> Option<u8> {
564 self.bytes.get(self.position).copied()
565 }
566}
567
568fn exponent_exceeds_raw_limit(digits: &[u8]) -> bool {
569 let first_significant = digits
570 .iter()
571 .position(|digit| *digit != b'0')
572 .unwrap_or(digits.len());
573 let significant = &digits[first_significant..];
574 significant.len() > 5
575 || significant.iter().fold(0_usize, |value, digit| {
576 value * 10 + usize::from(*digit - b'0')
577 }) > MAX_RAW_JSON_EXPONENT
578}
579
580fn serialize_jsonrpc_version<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
582where
583 S: Serializer,
584{
585 if value == JSONRPC_VERSION {
586 serializer.serialize_str(JSONRPC_VERSION)
587 } else {
588 Err(S::Error::custom("jsonrpc must be exactly \"2.0\""))
589 }
590}
591
592fn deserialize_jsonrpc_version<'de, D>(deserializer: D) -> Result<Cow<'static, str>, D::Error>
595where
596 D: Deserializer<'de>,
597{
598 let s: Cow<'de, str> = Cow::deserialize(deserializer)?;
599 if s == JSONRPC_VERSION {
600 Ok(Cow::Borrowed(JSONRPC_VERSION))
601 } else {
602 Err(D::Error::custom("jsonrpc must be exactly \"2.0\""))
603 }
604}
605
606#[derive(Debug, Clone, PartialEq, Eq, Hash)]
608pub enum RequestId {
609 Number(i64),
611 Integer(String),
614 String(String),
616}
617
618#[derive(Debug, Clone, PartialEq, Eq, Hash)]
623pub enum CorrelationKey {
624 String(String),
626 Integer(String),
628}
629
630impl RequestId {
631 pub fn validate(&self) -> Result<(), &'static str> {
640 match self {
641 Self::String(value)
642 if encoded_json_string_len(value) > MAX_JSONRPC_STRING_ID_ENCODED_BYTES =>
643 {
644 return Err("JSON-RPC string id exceeds byte limit");
645 }
646 Self::Integer(lexeme) if !is_mathematical_integer(lexeme) => {
647 return Err("JSON-RPC numeric id must be a mathematical integer");
648 }
649 _ => {}
650 }
651 Ok(())
652 }
653
654 pub fn correlation_key(&self) -> Result<CorrelationKey, &'static str> {
656 self.validate()?;
657 match self {
658 Self::Number(value) => Ok(CorrelationKey::Integer(value.to_string())),
659 Self::Integer(lexeme) => Ok(CorrelationKey::Integer(canonical_integer_lexeme(lexeme))),
660 Self::String(value) => Ok(CorrelationKey::String(value.clone())),
661 }
662 }
663
664 #[must_use]
669 pub fn correlates_with(&self, other: &Self) -> bool {
670 matches!(
671 (self.correlation_key(), other.correlation_key()),
672 (Ok(left), Ok(right)) if left == right
673 )
674 }
675}
676
677impl Serialize for RequestId {
678 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
679 where
680 S: Serializer,
681 {
682 self.validate().map_err(S::Error::custom)?;
683 match self {
684 Self::Number(number) => serializer.serialize_i64(*number),
685 Self::Integer(lexeme) => serde_json::from_str::<serde_json::Number>(lexeme)
686 .map_err(S::Error::custom)?
687 .serialize(serializer),
688 Self::String(value) => serializer.serialize_str(value),
689 }
690 }
691}
692
693impl<'de> Deserialize<'de> for RequestId {
694 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
695 where
696 D: Deserializer<'de>,
697 {
698 match Value::deserialize(deserializer)? {
699 Value::Number(number) => {
700 let lexeme = number.to_string();
701 if !lexeme.contains(['.', 'e', 'E'])
702 && lexeme != "-0"
703 && let Ok(value) = lexeme.parse::<i64>()
704 {
705 Ok(RequestId::Number(value))
706 } else if is_mathematical_integer(&lexeme) {
707 Ok(RequestId::Integer(lexeme))
708 } else {
709 Err(D::Error::custom(
710 "JSON-RPC numeric id must be a mathematical integer",
711 ))
712 }
713 }
714 Value::String(value) => {
715 if encoded_json_string_len(&value) > MAX_JSONRPC_STRING_ID_ENCODED_BYTES {
716 return Err(D::Error::custom("JSON-RPC string id exceeds byte limit"));
717 }
718 Ok(RequestId::String(value))
719 }
720 _ => Err(D::Error::custom(
721 "JSON-RPC id must be a string or mathematical integer",
722 )),
723 }
724 }
725}
726
727fn is_mathematical_integer(lexeme: &str) -> bool {
728 if lexeme.len() > MAX_RAW_JSON_NUMBER_BYTES {
729 return false;
730 }
731 let bytes = lexeme.as_bytes();
732 let mut index = usize::from(matches!(bytes.first(), Some(b'-')));
733 if index == bytes.len() {
734 return false;
735 }
736 let integer_start = index;
737 if bytes.get(index) == Some(&b'0') {
738 index += 1;
739 if matches!(bytes.get(index), Some(b'0'..=b'9')) {
740 return false;
741 }
742 } else if matches!(bytes.get(index), Some(b'1'..=b'9')) {
743 index += 1;
744 while matches!(bytes.get(index), Some(b'0'..=b'9')) {
745 index += 1;
746 }
747 } else {
748 return false;
749 }
750 let mut fraction_digits = 0_usize;
751 let mut trailing_zeroes = 0_usize;
752 if bytes.get(index) == Some(&b'.') {
753 index += 1;
754 let fraction_start = index;
755 while matches!(bytes.get(index), Some(b'0'..=b'9')) {
756 index += 1;
757 }
758 fraction_digits = index - fraction_start;
759 if fraction_digits == 0 {
760 return false;
761 }
762 }
763 let coefficient_end = index;
764 let coefficient = &bytes[integer_start..coefficient_end];
765 for digit in coefficient.iter().rev() {
766 if *digit == b'0' {
767 trailing_zeroes += 1;
768 } else if *digit != b'.' {
769 break;
770 }
771 }
772 let exponent = if matches!(bytes.get(index), Some(b'e' | b'E')) {
773 index += 1;
774 let negative = if bytes.get(index) == Some(&b'-') {
775 index += 1;
776 true
777 } else {
778 if bytes.get(index) == Some(&b'+') {
779 index += 1;
780 }
781 false
782 };
783 let exponent_start = index;
784 while matches!(bytes.get(index), Some(b'0'..=b'9')) {
785 index += 1;
786 }
787 if index == exponent_start || index != bytes.len() {
788 return false;
789 }
790 let magnitude = std::str::from_utf8(&bytes[exponent_start..index])
791 .ok()
792 .and_then(|value| value.parse::<i64>().ok());
793 match magnitude {
794 Some(value) if value <= MAX_RAW_JSON_EXPONENT as i64 && negative => -value,
795 Some(value) if value <= MAX_RAW_JSON_EXPONENT as i64 => value,
796 _ => return false,
797 }
798 } else {
799 if index != bytes.len() {
800 return false;
801 }
802 0
803 };
804 let scale = i64::try_from(fraction_digits).unwrap_or(i64::MAX) - exponent;
805 scale <= 0
806 || usize::try_from(scale).is_ok_and(|required_zeroes| trailing_zeroes >= required_zeroes)
807}
808
809fn canonical_integer_lexeme(lexeme: &str) -> String {
810 debug_assert!(is_mathematical_integer(lexeme));
811 let bytes = lexeme.as_bytes();
812 let negative = bytes.first() == Some(&b'-');
813 let unsigned = if negative { &lexeme[1..] } else { lexeme };
814 let (coefficient, exponent) = match unsigned.find(['e', 'E']) {
815 Some(index) => (
816 &unsigned[..index],
817 unsigned[index + 1..].parse::<i64>().unwrap_or(0),
818 ),
819 None => (unsigned, 0),
820 };
821 let (whole, fraction) = coefficient.split_once('.').unwrap_or((coefficient, ""));
822 let mut digits = format!("{whole}{fraction}");
823 let leading = digits.bytes().take_while(|digit| *digit == b'0').count();
824 digits.drain(..leading);
825 if digits.is_empty() {
826 return "0".to_owned();
827 }
828 let scale = i64::try_from(fraction.len()).unwrap_or(i64::MAX) - exponent;
829 if scale > 0 {
830 let removable = usize::try_from(scale).unwrap_or(usize::MAX);
831 let retained = digits.len().saturating_sub(removable);
832 digits.truncate(retained);
833 } else {
834 let zeroes = usize::try_from(scale.unsigned_abs()).unwrap_or(usize::MAX);
835 digits.extend(std::iter::repeat_n('0', zeroes));
836 }
837 if negative {
838 format!("-{digits}")
839 } else {
840 digits
841 }
842}
843
844fn encoded_json_string_len(value: &str) -> usize {
845 value.chars().fold(2_usize, |length, character| {
846 let encoded = match character {
847 '"' | '\\' | '\u{0008}' | '\u{000c}' | '\n' | '\r' | '\t' => 2,
848 '\u{0000}'..='\u{001f}' => 6,
849 _ => character.len_utf8(),
850 };
851 length.saturating_add(encoded)
852 })
853}
854
855fn deserialize_request_id<'de, D>(deserializer: D) -> Result<Option<RequestId>, D::Error>
856where
857 D: Deserializer<'de>,
858{
859 RequestId::deserialize(deserializer).map(Some)
860}
861
862impl From<i64> for RequestId {
863 fn from(id: i64) -> Self {
864 RequestId::Number(id)
865 }
866}
867
868impl From<String> for RequestId {
869 fn from(id: String) -> Self {
870 RequestId::String(id)
871 }
872}
873
874impl From<&str> for RequestId {
875 fn from(id: &str) -> Self {
876 RequestId::String(id.to_owned())
877 }
878}
879
880impl std::fmt::Display for RequestId {
881 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
882 match self {
883 RequestId::Number(n) => write!(f, "{n}"),
884 RequestId::Integer(lexeme) => f.write_str(lexeme),
885 RequestId::String(s) => write!(f, "{s}"),
886 }
887 }
888}
889
890#[derive(Debug, Clone, Serialize)]
892#[serde(deny_unknown_fields)]
893pub struct JsonRpcRequest {
894 #[serde(
896 serialize_with = "serialize_jsonrpc_version",
897 deserialize_with = "deserialize_jsonrpc_version"
898 )]
899 pub jsonrpc: Cow<'static, str>,
900 pub method: String,
902 #[serde(skip_serializing_if = "Option::is_none")]
904 pub params: Option<Value>,
905 #[serde(
910 default,
911 deserialize_with = "deserialize_request_id",
912 skip_serializing_if = "Option::is_none"
913 )]
914 pub id: Option<RequestId>,
915}
916
917#[derive(Deserialize)]
918#[serde(deny_unknown_fields)]
919struct JsonRpcRequestRawWire<'a> {
920 #[serde(deserialize_with = "deserialize_jsonrpc_version")]
921 jsonrpc: Cow<'static, str>,
922 method: String,
923 #[serde(borrow, default)]
924 params: Option<Cow<'a, RawValue>>,
925 #[serde(default, deserialize_with = "deserialize_request_id")]
926 id: Option<RequestId>,
927}
928
929impl<'de> Deserialize<'de> for JsonRpcRequest {
930 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
931 where
932 D: Deserializer<'de>,
933 {
934 let wire = JsonRpcRequestRawWire::deserialize(deserializer)?;
935 let params = wire
936 .params
937 .as_deref()
938 .map(RawValue::get)
939 .map(|source| {
940 crate::messages::validate_raw_final_completion_params(&wire.method, source)
941 .map_err(D::Error::custom)?;
942 serde_json::from_str(source).map_err(D::Error::custom)
943 })
944 .transpose()?;
945
946 Ok(Self {
947 jsonrpc: wire.jsonrpc,
948 method: wire.method,
949 params,
950 id: wire.id,
951 })
952 }
953}
954
955impl JsonRpcRequest {
956 #[must_use]
958 pub fn new(method: impl Into<String>, params: Option<Value>, id: impl Into<RequestId>) -> Self {
959 Self {
960 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
961 method: method.into(),
962 params,
963 id: Some(id.into()),
964 }
965 }
966
967 #[must_use]
969 pub fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
970 Self {
971 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
972 method: method.into(),
973 params,
974 id: None,
975 }
976 }
977
978 #[must_use]
984 pub fn initialized_notification() -> Self {
985 Self::notification(crate::methods::NOTIFICATIONS_INITIALIZED, None)
986 }
987
988 #[must_use]
990 pub fn is_notification(&self) -> bool {
991 self.id.is_none()
992 }
993
994 pub fn validate(&self) -> Result<(), &'static str> {
1001 if self.jsonrpc != JSONRPC_VERSION {
1002 return Err("jsonrpc must be exactly \"2.0\"");
1003 }
1004 if let Some(id) = &self.id {
1005 id.validate()?;
1006 }
1007 Ok(())
1008 }
1009}
1010
1011#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1013pub struct JsonRpcError {
1014 pub code: JsonInteger,
1016 pub message: String,
1018 #[serde(skip_serializing_if = "Option::is_none")]
1020 pub data: Option<Value>,
1021}
1022
1023#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1028pub enum JsonRpcEndpointRole {
1029 ServerIngress,
1031 ClientIngress,
1033}
1034
1035#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1037pub enum JsonRpcMessageDirection {
1038 ClientToServer,
1040 ServerToClient,
1042}
1043
1044#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1046pub enum ClientIngressFailureScope {
1047 OwningExchange,
1049 SharedChannel,
1051}
1052
1053#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1058#[serde(deny_unknown_fields)]
1059pub struct UncorrelatedJsonRpcErrorResponse {
1060 #[serde(
1061 serialize_with = "serialize_jsonrpc_version",
1062 deserialize_with = "deserialize_jsonrpc_version"
1063 )]
1064 jsonrpc: Cow<'static, str>,
1065 error: JsonRpcError,
1066}
1067
1068impl UncorrelatedJsonRpcErrorResponse {
1069 fn parse_or_invalid_request(message: impl Into<String>, code: i32) -> Self {
1070 Self {
1071 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
1072 error: JsonRpcError {
1073 code: code.into(),
1074 message: message.into(),
1075 data: None,
1076 },
1077 }
1078 }
1079
1080 #[must_use]
1082 pub fn error(&self) -> &JsonRpcError {
1083 &self.error
1084 }
1085}
1086
1087#[derive(Debug, Clone, PartialEq)]
1089pub enum RawJsonRpcDisposition {
1090 CorrelatedError(JsonRpcResponse),
1092 UncorrelatedError(UncorrelatedJsonRpcErrorResponse),
1094 ClientOwningFailure,
1096 ClientSharedChannelFailure,
1098 NoAction,
1100}
1101
1102#[must_use]
1107pub fn dispose_raw_jsonrpc_failure(
1108 role: JsonRpcEndpointRole,
1109 direction: JsonRpcMessageDirection,
1110 readable_id: Option<RequestId>,
1111 failure_scope: ClientIngressFailureScope,
1112) -> RawJsonRpcDisposition {
1113 match (role, direction) {
1114 (JsonRpcEndpointRole::ServerIngress, JsonRpcMessageDirection::ClientToServer) => {
1115 if let Some(id) = readable_id {
1116 RawJsonRpcDisposition::CorrelatedError(JsonRpcResponse::error(
1117 Some(id),
1118 JsonRpcError {
1119 code: (-32600).into(),
1120 message: "Invalid Request".to_owned(),
1121 data: None,
1122 },
1123 ))
1124 } else {
1125 RawJsonRpcDisposition::UncorrelatedError(
1126 UncorrelatedJsonRpcErrorResponse::parse_or_invalid_request(
1127 "Parse error",
1128 -32700,
1129 ),
1130 )
1131 }
1132 }
1133 (JsonRpcEndpointRole::ClientIngress, JsonRpcMessageDirection::ServerToClient) => {
1134 match failure_scope {
1135 ClientIngressFailureScope::OwningExchange => {
1136 RawJsonRpcDisposition::ClientOwningFailure
1137 }
1138 ClientIngressFailureScope::SharedChannel => {
1139 RawJsonRpcDisposition::ClientSharedChannelFailure
1140 }
1141 }
1142 }
1143 _ => RawJsonRpcDisposition::NoAction,
1144 }
1145}
1146
1147impl From<fastmcp_core::McpError> for JsonRpcError {
1148 fn from(err: fastmcp_core::McpError) -> Self {
1149 Self {
1150 code: err.code.into(),
1151 message: err.message,
1152 data: err.data,
1153 }
1154 }
1155}
1156
1157fn deserialize_response_result<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
1158where
1159 D: Deserializer<'de>,
1160{
1161 Value::deserialize(deserializer).map(Some)
1162}
1163
1164fn deserialize_response_error<'de, D>(deserializer: D) -> Result<Option<JsonRpcError>, D::Error>
1165where
1166 D: Deserializer<'de>,
1167{
1168 JsonRpcError::deserialize(deserializer).map(Some)
1169}
1170
1171fn deserialize_raw_response_result<'de, D>(
1172 deserializer: D,
1173) -> Result<Option<Box<RawValue>>, D::Error>
1174where
1175 D: Deserializer<'de>,
1176{
1177 Box::<RawValue>::deserialize(deserializer).map(Some)
1178}
1179
1180#[derive(Deserialize)]
1181#[serde(deny_unknown_fields)]
1182struct JsonRpcResponseRawWire {
1183 #[serde(deserialize_with = "deserialize_jsonrpc_version")]
1184 jsonrpc: Cow<'static, str>,
1185 #[serde(default, deserialize_with = "deserialize_raw_response_result")]
1186 result: Option<Box<RawValue>>,
1187 #[serde(default, deserialize_with = "deserialize_response_error")]
1188 error: Option<JsonRpcError>,
1189 #[serde(default, deserialize_with = "deserialize_request_id")]
1190 id: Option<RequestId>,
1191}
1192
1193#[derive(Serialize, Deserialize)]
1194#[serde(deny_unknown_fields)]
1195struct JsonRpcResponseWire {
1196 #[serde(
1198 serialize_with = "serialize_jsonrpc_version",
1199 deserialize_with = "deserialize_jsonrpc_version"
1200 )]
1201 jsonrpc: Cow<'static, str>,
1202 #[serde(
1203 default,
1204 deserialize_with = "deserialize_response_result",
1205 skip_serializing_if = "Option::is_none"
1206 )]
1207 result: Option<Value>,
1208 #[serde(
1209 default,
1210 deserialize_with = "deserialize_response_error",
1211 skip_serializing_if = "Option::is_none"
1212 )]
1213 error: Option<JsonRpcError>,
1214 #[serde(
1215 default,
1216 deserialize_with = "deserialize_request_id",
1217 skip_serializing_if = "Option::is_none"
1218 )]
1219 id: Option<RequestId>,
1220}
1221
1222#[derive(Debug, Clone, PartialEq)]
1224pub struct JsonRpcResponse {
1225 pub jsonrpc: Cow<'static, str>,
1227 pub result: Option<Value>,
1229 pub error: Option<JsonRpcError>,
1231 pub id: Option<RequestId>,
1233}
1234
1235impl Serialize for JsonRpcResponse {
1236 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1237 where
1238 S: Serializer,
1239 {
1240 self.validate().map_err(S::Error::custom)?;
1241
1242 JsonRpcResponseWire {
1243 jsonrpc: self.jsonrpc.clone(),
1244 result: self.result.clone(),
1245 error: self.error.clone(),
1246 id: self.id.clone(),
1247 }
1248 .serialize(serializer)
1249 }
1250}
1251
1252impl<'de> Deserialize<'de> for JsonRpcResponse {
1253 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1254 where
1255 D: Deserializer<'de>,
1256 {
1257 let wire = JsonRpcResponseWire::deserialize(deserializer)?;
1258 let response = Self {
1259 jsonrpc: wire.jsonrpc,
1260 result: wire.result,
1261 error: wire.error,
1262 id: wire.id,
1263 };
1264 response.validate().map_err(D::Error::custom)?;
1265 Ok(response)
1266 }
1267}
1268
1269impl JsonRpcResponse {
1270 #[must_use]
1272 pub fn success(id: RequestId, result: Value) -> Self {
1273 Self {
1274 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
1275 result: Some(result),
1276 error: None,
1277 id: Some(id),
1278 }
1279 }
1280
1281 #[must_use]
1283 pub fn error(id: Option<RequestId>, error: JsonRpcError) -> Self {
1284 Self {
1285 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
1286 result: None,
1287 error: Some(error),
1288 id,
1289 }
1290 }
1291
1292 #[must_use]
1294 pub fn is_error(&self) -> bool {
1295 self.error.is_some()
1296 }
1297
1298 pub fn validate(&self) -> Result<(), &'static str> {
1307 if self.jsonrpc != JSONRPC_VERSION {
1308 return Err("jsonrpc must be exactly \"2.0\"");
1309 }
1310 if self.result.is_some() == self.error.is_some() {
1311 return Err("JSON-RPC response must contain exactly one of result or error");
1312 }
1313 if self.result.is_some() && self.id.is_none() {
1314 return Err("JSON-RPC success response must contain an id");
1315 }
1316 if let Some(id) = &self.id {
1317 id.validate()?;
1318 }
1319 Ok(())
1320 }
1321}
1322
1323#[derive(Debug, Clone, Serialize, Deserialize)]
1325#[serde(untagged)]
1326pub enum JsonRpcMessage {
1327 Request(JsonRpcRequest),
1329 Response(JsonRpcResponse),
1331}
1332
1333impl JsonRpcMessage {
1334 pub fn validate(&self) -> Result<(), &'static str> {
1343 match self {
1344 Self::Request(request) => request.validate(),
1345 Self::Response(response) => response.validate(),
1346 }
1347 }
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352 use super::*;
1353 use serde_json::json;
1354
1355 #[derive(Clone, Debug, Default, PartialEq, Eq)]
1356 struct AdmittedFrames {
1357 bytes: Vec<Vec<u8>>,
1358 }
1359
1360 fn admit_frame(
1361 state: &mut AdmittedFrames,
1362 frame: &[u8],
1363 ) -> Result<JsonRpcMessage, JsonRpcAdmissionError> {
1364 let message = decode_strict_jsonrpc_message(frame, 4 * 1024)?;
1365 state.bytes.push(frame.to_vec());
1366 Ok(message)
1367 }
1368
1369 #[test]
1370 fn request_and_response_envelopes_decode() {
1371 let request = br#"{"jsonrpc":"2.0","method":"tools/list","id":42}"#;
1372 let notification = br#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
1373 let success = br#"{"jsonrpc":"2.0","result":null,"id":"request-42"}"#;
1374 let error = br#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"missing"},"id":42}"#;
1375
1376 assert!(matches!(
1377 decode_strict_jsonrpc_message(request, 4 * 1024),
1378 Ok(JsonRpcMessage::Request(JsonRpcRequest {
1379 id: Some(RequestId::Number(42)),
1380 ..
1381 }))
1382 ));
1383 assert!(matches!(
1384 decode_strict_jsonrpc_message(notification, 4 * 1024),
1385 Ok(JsonRpcMessage::Request(JsonRpcRequest { id: None, .. }))
1386 ));
1387 assert!(matches!(
1388 decode_strict_jsonrpc_message(success, 4 * 1024),
1389 Ok(JsonRpcMessage::Response(JsonRpcResponse {
1390 result: Some(Value::Null),
1391 error: None,
1392 ..
1393 }))
1394 ));
1395 assert!(matches!(
1396 decode_strict_jsonrpc_message(error, 4 * 1024),
1397 Ok(JsonRpcMessage::Response(JsonRpcResponse {
1398 result: None,
1399 error: Some(_),
1400 ..
1401 }))
1402 ));
1403
1404 assert!(matches!(
1405 dispose_raw_jsonrpc_failure(
1406 JsonRpcEndpointRole::ServerIngress,
1407 JsonRpcMessageDirection::ClientToServer,
1408 Some(RequestId::String("known".to_owned())),
1409 ClientIngressFailureScope::OwningExchange,
1410 ),
1411 RawJsonRpcDisposition::CorrelatedError(JsonRpcResponse {
1412 id: Some(RequestId::String(_)),
1413 ..
1414 })
1415 ));
1416 assert!(matches!(
1417 dispose_raw_jsonrpc_failure(
1418 JsonRpcEndpointRole::ClientIngress,
1419 JsonRpcMessageDirection::ServerToClient,
1420 None,
1421 ClientIngressFailureScope::SharedChannel,
1422 ),
1423 RawJsonRpcDisposition::ClientSharedChannelFailure
1424 ));
1425 }
1426
1427 #[test]
1428 fn strict_response_admission_retains_exact_result_source() {
1429 let frame = br#"{"jsonrpc":"2.0","result":{"zeta":1.20e+4,"alpha":{"second":2,"first":1},"middle":null},"id":73}"#;
1430 let admission = decode_strict_jsonrpc_response(frame, 4 * 1024)
1431 .expect("strict response admission accepts the bounded frame");
1432 assert_eq!(admission.response().id, Some(RequestId::Number(73)));
1433 assert_eq!(
1434 admission.raw_result(),
1435 Some(r#"{"zeta":1.20e+4,"alpha":{"second":2,"first":1},"middle":null}"#),
1436 "the exact result substring retains top-level and nested member order plus number lexemes"
1437 );
1438
1439 let explicit_null =
1440 decode_strict_jsonrpc_response(br#"{"jsonrpc":"2.0","result":null,"id":74}"#, 4 * 1024)
1441 .expect("an explicit null success remains present");
1442 assert_eq!(explicit_null.raw_result(), Some("null"));
1443
1444 let error = decode_strict_jsonrpc_response(
1445 br#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"missing"},"id":75}"#,
1446 4 * 1024,
1447 )
1448 .expect("an error response has no result source");
1449 assert!(error.raw_result().is_none());
1450 }
1451
1452 #[test]
1453 fn raw_admission_accepts_public_request_surface() {
1454 let frame = br#"{"jsonrpc":"2.0","method":"tools/list","id":"public-request"}"#;
1455 let mut state = AdmittedFrames::default();
1456 let admitted = admit_frame(&mut state, frame)
1457 .expect("the protocol-owned raw gate admits a strict public request envelope");
1458 assert!(matches!(admitted, JsonRpcMessage::Request(_)));
1459 assert_eq!(state.bytes, vec![frame.to_vec()]);
1460 assert!(matches!(
1461 dispose_raw_jsonrpc_failure(
1462 JsonRpcEndpointRole::ClientIngress,
1463 JsonRpcMessageDirection::ServerToClient,
1464 None,
1465 ClientIngressFailureScope::OwningExchange,
1466 ),
1467 RawJsonRpcDisposition::ClientOwningFailure
1468 ));
1469 }
1470
1471 #[test]
1472 fn duplicate_envelope_member_is_rejected_without_state_change() {
1473 let baseline = br#"{"jsonrpc":"2.0","method":"tools/list","id":42}"#;
1474 let planted = br#"{"jsonrpc":"2.0","method":"tools/list","id":42,"id":42}"#;
1475 let mut state = AdmittedFrames::default();
1476 admit_frame(&mut state, baseline).expect("the unmodified envelope is admitted");
1477 let state_before = state.clone();
1478
1479 assert!(
1480 matches!(
1481 admit_frame(&mut state, planted),
1482 Err(JsonRpcAdmissionError::Raw(
1483 RawJsonAdmissionError::DuplicateObjectMember
1484 ))
1485 ),
1486 "changing only the second id member must reach production raw admission"
1487 );
1488 assert_eq!(
1489 state, state_before,
1490 "rejected raw JSON cannot mutate admitted state"
1491 );
1492 }
1493
1494 #[test]
1495 fn bom_is_rejected_without_state_change() {
1496 let baseline = br#"{"jsonrpc":"2.0","method":"tools/list","id":"public-request"}"#;
1497 let mut planted = baseline.to_vec();
1498 planted.splice(0..0, [0xef, 0xbb, 0xbf]);
1499 let mut state = AdmittedFrames::default();
1500 admit_frame(&mut state, baseline).expect("the baseline is admitted");
1501 let state_before = state.clone();
1502
1503 assert!(
1504 matches!(
1505 admit_frame(&mut state, &planted),
1506 Err(JsonRpcAdmissionError::Raw(
1507 RawJsonAdmissionError::ByteOrderMark
1508 ))
1509 ),
1510 "inserting only a UTF-8 BOM must reach the typed raw-admission refusal"
1511 );
1512 assert_eq!(
1513 state, state_before,
1514 "rejected raw bytes leave admitted state unchanged"
1515 );
1516 }
1517
1518 #[test]
1519 fn request_id_correlation_key_normalizes_numeric_aliases() {
1520 let numeric = RequestId::Number(1);
1521 let string = RequestId::String("1".to_owned());
1522 assert_ne!(
1523 numeric, string,
1524 "string and numeric request IDs are disjoint"
1525 );
1526 assert_eq!(
1527 numeric.correlation_key().expect("valid numeric ID"),
1528 RequestId::Integer("1.0".to_owned())
1529 .correlation_key()
1530 .expect("valid mathematical integer ID"),
1531 "numeric aliases share one exact mathematical correlation key"
1532 );
1533 assert_eq!(
1534 numeric.correlation_key().expect("valid numeric ID"),
1535 RequestId::Integer("1e0".to_owned())
1536 .correlation_key()
1537 .expect("valid mathematical integer ID"),
1538 "exponent-form integer aliases share one exact mathematical correlation key"
1539 );
1540 assert_ne!(
1541 numeric.correlation_key().expect("valid numeric ID"),
1542 string.correlation_key().expect("valid string ID"),
1543 "a string ID never aliases its numeric spelling"
1544 );
1545 assert!(numeric.correlates_with(&RequestId::Integer("1.0".to_owned())));
1546 assert!(numeric.correlates_with(&RequestId::Integer("1e0".to_owned())));
1547 assert!(!numeric.correlates_with(&string));
1548 assert_eq!(
1549 JsonRpcResponse::success(numeric.clone(), Value::Null).id,
1550 Some(numeric),
1551 "a correlated success preserves its accepted request ID"
1552 );
1553 let large = "922337203685477580812345678901234567890";
1554 let raw = format!(r#"{{"jsonrpc":"2.0","method":"tools/list","id":{large}}}"#);
1555 let decoded = decode_strict_jsonrpc_message(raw.as_bytes(), 4 * 1024)
1556 .expect("an arbitrary-precision mathematical integer is admitted");
1557 let JsonRpcMessage::Request(request) = decoded else {
1558 panic!("the admitted envelope remains a request");
1559 };
1560 assert_eq!(request.id, Some(RequestId::Integer(large.to_owned())));
1561 let echoed = JsonRpcResponse::success(
1562 request
1563 .id
1564 .expect("admitted request keeps its original ID lexeme"),
1565 Value::Null,
1566 );
1567 assert!(
1568 serde_json::to_string(&echoed)
1569 .expect("the exact admitted ID can be echoed")
1570 .contains(large),
1571 "response serialization preserves the accepted arbitrary-precision ID lexeme"
1572 );
1573 assert!(matches!(
1574 dispose_raw_jsonrpc_failure(
1575 JsonRpcEndpointRole::ServerIngress,
1576 JsonRpcMessageDirection::ClientToServer,
1577 Some(RequestId::Number(9)),
1578 ClientIngressFailureScope::OwningExchange,
1579 ),
1580 RawJsonRpcDisposition::CorrelatedError(JsonRpcResponse {
1581 id: Some(RequestId::Number(9)),
1582 ..
1583 })
1584 ));
1585 }
1586
1587 #[test]
1588 fn request_id_fractional_lexeme_is_rejected_before_correlation() {
1589 let baseline = br#"{"jsonrpc":"2.0","method":"tools/list","id":1}"#;
1590 let planted = br#"{"jsonrpc":"2.0","method":"tools/list","id":1.5}"#;
1591 let mut state = AdmittedFrames::default();
1592 admit_frame(&mut state, baseline).expect("integer request ID is admitted");
1593 let state_before = state.clone();
1594
1595 assert!(
1596 matches!(
1597 admit_frame(&mut state, planted),
1598 Err(JsonRpcAdmissionError::InvalidEnvelope)
1599 ),
1600 "changing only the ID to a fractional number must be rejected"
1601 );
1602 assert_eq!(
1603 state, state_before,
1604 "a rejected fractional ID cannot claim a correlation slot"
1605 );
1606 }
1607
1608 #[test]
1609 fn duplicate_nested_member_is_rejected_without_state_change() {
1610 let baseline = br#"{"jsonrpc":"2.0","method":"tools/list","params":{"cursor":"a"}}"#;
1611 let planted =
1612 br#"{"jsonrpc":"2.0","method":"tools/list","params":{"cursor":"a","cursor":"b"}}"#;
1613 let mut state = AdmittedFrames::default();
1614 admit_frame(&mut state, baseline).expect("baseline nested object is admitted");
1615 let state_before = state.clone();
1616
1617 assert!(
1618 matches!(
1619 admit_frame(&mut state, planted),
1620 Err(JsonRpcAdmissionError::Raw(
1621 RawJsonAdmissionError::DuplicateObjectMember
1622 ))
1623 ),
1624 "a one-member duplicate must fail before typed params decoding"
1625 );
1626 assert_eq!(
1627 state, state_before,
1628 "duplicate raw members cannot mutate admitted state"
1629 );
1630 }
1631
1632 #[test]
1633 fn top_level_batches_are_rejected_without_state_change() {
1634 let baseline = br#"{"jsonrpc":"2.0","method":"tools/list"}"#;
1635 let array_of_one = br#"[{"jsonrpc":"2.0","method":"tools/list"}]"#;
1636 let mixed_array = br#"[{"jsonrpc":"2.0","method":"tools/list"},{"jsonrpc":"2.0","method":"notifications/initialized"}]"#;
1637 let mut state = AdmittedFrames::default();
1638 admit_frame(&mut state, baseline).expect("one top-level request object is admitted");
1639 let state_before = state.clone();
1640
1641 for planted in [array_of_one.as_slice(), mixed_array.as_slice()] {
1642 assert!(
1643 matches!(
1644 admit_frame(&mut state, planted),
1645 Err(JsonRpcAdmissionError::Raw(
1646 RawJsonAdmissionError::TopLevelBatch
1647 ))
1648 ),
1649 "a top-level batch fails before envelope construction"
1650 );
1651 assert_eq!(
1652 state, state_before,
1653 "rejected batch traffic has no admitted state effect"
1654 );
1655 }
1656 }
1657
1658 #[test]
1663 fn request_id_number_serialization() {
1664 let id = RequestId::Number(42);
1665 let value = serde_json::to_value(&id).expect("serialize");
1666 assert_eq!(value, 42);
1667 }
1668
1669 #[test]
1670 fn request_id_string_serialization() {
1671 let id = RequestId::String("req-1".to_string());
1672 let value = serde_json::to_value(&id).expect("serialize");
1673 assert_eq!(value, "req-1");
1674 }
1675
1676 #[test]
1677 fn request_id_number_deserialization() {
1678 let id: RequestId = serde_json::from_value(json!(99)).expect("deserialize");
1679 assert_eq!(id, RequestId::Number(99));
1680 }
1681
1682 #[test]
1683 fn request_id_string_deserialization() {
1684 let id: RequestId = serde_json::from_value(json!("abc")).expect("deserialize");
1685 assert_eq!(id, RequestId::String("abc".to_string()));
1686 }
1687
1688 #[test]
1689 fn request_id_string_enforces_encoded_byte_limit() {
1690 let exact = "a".repeat(MAX_JSONRPC_STRING_ID_ENCODED_BYTES - 2);
1691 let too_long = "a".repeat(MAX_JSONRPC_STRING_ID_ENCODED_BYTES - 1);
1692 let exact_json = format!("\"{exact}\"");
1693 let too_long_json = format!("\"{too_long}\"");
1694
1695 assert!(serde_json::from_str::<RequestId>(&exact_json).is_ok());
1696 assert!(serde_json::from_str::<RequestId>(&too_long_json).is_err());
1697 assert!(serde_json::to_string(&RequestId::String(exact)).is_ok());
1698 assert!(serde_json::to_string(&RequestId::String(too_long)).is_err());
1699
1700 let escaped_exact = format!("\"{}\"", "\\u0001".repeat(42));
1701 let escaped_too_long = format!("\"{}\"", "\\u0001".repeat(43));
1702 assert_eq!(escaped_exact.len(), 254);
1703 assert!(serde_json::from_str::<RequestId>(&escaped_exact).is_ok());
1704 assert!(serde_json::from_str::<RequestId>(&escaped_too_long).is_err());
1705 }
1706
1707 #[test]
1708 fn request_id_validation_catches_direct_construction_bypass() {
1709 let too_long = RequestId::String("a".repeat(MAX_JSONRPC_STRING_ID_ENCODED_BYTES));
1710
1711 assert_eq!(
1712 too_long.validate(),
1713 Err("JSON-RPC string id exceeds byte limit")
1714 );
1715 }
1716
1717 #[test]
1718 fn request_rejects_explicit_null_id_but_accepts_absent_id() {
1719 let error = serde_json::from_str::<JsonRpcRequest>(
1720 r#"{"jsonrpc":"2.0","method":"notifications/initialized","id":null}"#,
1721 )
1722 .expect_err("an explicit null id must not become a notification");
1723 assert!(
1724 error
1725 .to_string()
1726 .contains("JSON-RPC id must be a string or mathematical integer")
1727 );
1728
1729 let notification = serde_json::from_str::<JsonRpcRequest>(
1730 r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#,
1731 )
1732 .expect("an absent id denotes a notification");
1733 assert!(notification.is_notification());
1734 }
1735
1736 #[test]
1737 fn request_id_from_i64() {
1738 let id: RequestId = 7i64.into();
1739 assert_eq!(id, RequestId::Number(7));
1740 }
1741
1742 #[test]
1743 fn request_id_from_string() {
1744 let id: RequestId = "test-id".to_string().into();
1745 assert_eq!(id, RequestId::String("test-id".to_string()));
1746 }
1747
1748 #[test]
1749 fn request_id_from_str() {
1750 let id: RequestId = "test-id".into();
1751 assert_eq!(id, RequestId::String("test-id".to_string()));
1752 }
1753
1754 #[test]
1755 fn request_id_display() {
1756 assert_eq!(format!("{}", RequestId::Number(42)), "42");
1757 assert_eq!(
1758 format!("{}", RequestId::String("req-1".to_string())),
1759 "req-1"
1760 );
1761 }
1762
1763 #[test]
1764 fn request_id_equality() {
1765 assert_eq!(RequestId::Number(1), RequestId::Number(1));
1766 assert_ne!(RequestId::Number(1), RequestId::Number(2));
1767 assert_eq!(
1768 RequestId::String("a".to_string()),
1769 RequestId::String("a".to_string())
1770 );
1771 assert_ne!(RequestId::Number(1), RequestId::String("1".to_string()));
1772 }
1773
1774 #[test]
1779 fn jsonrpc_version_deserialize_borrows_static_for_request() {
1780 let req: JsonRpcRequest =
1781 serde_json::from_str(r#"{"jsonrpc":"2.0","method":"tools/list","id":1}"#)
1782 .expect("deserialize");
1783 assert!(matches!(req.jsonrpc, Cow::Borrowed(JSONRPC_VERSION)));
1784 }
1785
1786 #[test]
1787 fn request_rejects_nonstandard_missing_and_non_string_jsonrpc_versions() {
1788 for input in [
1789 r#"{"jsonrpc":"2.1","method":"tools/list","id":1}"#,
1790 r#"{"jsonrpc":"1.0","method":"tools/list","id":1}"#,
1791 r#"{"jsonrpc":null,"method":"tools/list","id":1}"#,
1792 r#"{"method":"tools/list","id":1}"#,
1793 ] {
1794 let error = serde_json::from_str::<JsonRpcRequest>(input).unwrap_err();
1795 assert!(error.is_data(), "unexpected error for {input}: {error}");
1796 }
1797 }
1798
1799 #[test]
1800 fn request_serialization() {
1801 let req = JsonRpcRequest::new("tools/list", None, 1i64);
1802 let json = serde_json::to_string(&req).unwrap();
1803 assert!(json.contains("\"jsonrpc\":\"2.0\""));
1804 assert!(json.contains("\"method\":\"tools/list\""));
1805 assert!(json.contains("\"id\":1"));
1806 }
1807
1808 #[test]
1809 fn request_with_params() {
1810 let params = json!({"name": "greet", "arguments": {"name": "World"}});
1811 let req = JsonRpcRequest::new("tools/call", Some(params.clone()), 2i64);
1812 let value = serde_json::to_value(&req).expect("serialize");
1813 assert_eq!(value["jsonrpc"], "2.0");
1814 assert_eq!(value["method"], "tools/call");
1815 assert_eq!(value["params"]["name"], "greet");
1816 assert_eq!(value["id"], 2);
1817 }
1818
1819 #[test]
1820 fn strict_request_raw_params_sidecar_preserves_exact_source_and_rejects_duplicates() {
1821 let raw_params = r#"{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"name":"weather","inputResponses":{"second":{"roots":[]},"first":{"roots":[]}},"requestState":"retry-1"}"#;
1822 let frame =
1823 format!(r#"{{"jsonrpc":"2.0","method":"tools/call","params":{raw_params},"id":7}}"#);
1824 let (request, sidecar) =
1825 JsonRpcRequest::decode_strict_with_raw_params(frame.as_bytes(), frame.len())
1826 .expect("strict request admission retains the exact parameter source");
1827 assert_eq!(sidecar.as_deref(), Some(raw_params));
1828 assert_eq!(
1829 request.params,
1830 Some(serde_json::from_str(raw_params).expect("raw params materialize")),
1831 "the sidecar belongs to the same admitted typed request"
1832 );
1833
1834 let duplicate = r#"{"jsonrpc":"2.0","method":"tools/call","params":{"inputResponses":{"roots":{"roots":[]},"roots":{"roots":[]}}},"id":7}"#;
1835 assert!(matches!(
1836 JsonRpcRequest::decode_strict_with_raw_params(duplicate.as_bytes(), duplicate.len()),
1837 Err(JsonRpcAdmissionError::Raw(
1838 RawJsonAdmissionError::DuplicateObjectMember
1839 ))
1840 ));
1841 }
1842
1843 #[test]
1844 fn request_without_params_omits_field() {
1845 let req = JsonRpcRequest::new("tools/list", None, 1i64);
1846 let value = serde_json::to_value(&req).expect("serialize");
1847 assert!(value.get("params").is_none());
1848 }
1849
1850 #[test]
1851 fn notification_has_no_id() {
1852 let notif = JsonRpcRequest::notification("notifications/progress", None);
1853 assert!(notif.is_notification());
1854 assert!(notif.id.is_none());
1855 let value = serde_json::to_value(¬if).expect("serialize");
1856 assert!(value.get("id").is_none());
1857 }
1858
1859 #[test]
1860 fn notification_with_params() {
1861 let params = json!({"uri": "file://changed.txt"});
1862 let notif = JsonRpcRequest::notification("notifications/resources/updated", Some(params));
1863 assert!(notif.is_notification());
1864 let value = serde_json::to_value(¬if).expect("serialize");
1865 assert_eq!(value["params"]["uri"], "file://changed.txt");
1866 }
1867
1868 #[test]
1869 fn request_is_not_notification() {
1870 let req = JsonRpcRequest::new("tools/list", None, 1i64);
1871 assert!(!req.is_notification());
1872 }
1873
1874 #[test]
1875 fn request_with_string_id() {
1876 let req = JsonRpcRequest::new("tools/list", None, "req-abc");
1877 let value = serde_json::to_value(&req).expect("serialize");
1878 assert_eq!(value["id"], "req-abc");
1879 }
1880
1881 #[test]
1882 fn request_round_trip() {
1883 let original = JsonRpcRequest::new(
1884 "tools/call",
1885 Some(json!({"name": "add", "arguments": {"a": 1, "b": 2}})),
1886 42i64,
1887 );
1888 let json_str = serde_json::to_string(&original).expect("serialize");
1889 let deserialized: JsonRpcRequest = serde_json::from_str(&json_str).expect("deserialize");
1890 assert_eq!(deserialized.method, "tools/call");
1891 assert_eq!(deserialized.id, Some(RequestId::Number(42)));
1892 assert!(deserialized.params.is_some());
1893 }
1894
1895 #[test]
1896 fn request_rejects_unknown_top_level_envelope_members() {
1897 let error = serde_json::from_str::<JsonRpcRequest>(
1898 r#"{"jsonrpc":"2.0","method":"tools/list","id":1,"extension":true}"#,
1899 )
1900 .expect_err("request envelopes are closed");
1901
1902 assert!(error.to_string().contains("unknown field"));
1903 }
1904
1905 #[test]
1910 fn jsonrpc_error_from_mcp_error_preserves_code_message_and_data() {
1911 let err = fastmcp_core::McpError::with_data(
1912 fastmcp_core::McpErrorCode::InvalidParams,
1913 "bad params",
1914 json!({"field":"name"}),
1915 );
1916 let rpc_err: JsonRpcError = err.into();
1917 assert_eq!(rpc_err.code.as_i32(), Some(-32602));
1918 assert_eq!(rpc_err.message, "bad params");
1919 assert_eq!(rpc_err.data, Some(json!({"field":"name"})));
1920 }
1921
1922 #[test]
1923 fn jsonrpc_error_serialization() {
1924 let error = JsonRpcError {
1925 code: (-32600).into(),
1926 message: "Invalid Request".to_string(),
1927 data: None,
1928 };
1929 let value = serde_json::to_value(&error).expect("serialize");
1930 assert_eq!(value["code"], -32600);
1931 assert_eq!(value["message"], "Invalid Request");
1932 assert!(value.get("data").is_none());
1933 }
1934
1935 #[test]
1936 fn jsonrpc_error_with_data() {
1937 let error = JsonRpcError {
1938 code: (-32602).into(),
1939 message: "Invalid params".to_string(),
1940 data: Some(json!({"field": "name", "reason": "required"})),
1941 };
1942 let value = serde_json::to_value(&error).expect("serialize");
1943 assert_eq!(value["code"], -32602);
1944 assert_eq!(value["data"]["field"], "name");
1945 }
1946
1947 #[test]
1948 fn jsonrpc_error_preserves_arbitrary_width_integer_code() {
1949 let source = r#"{"code":-340282366920938463463374607431768211457,"message":"unbounded"}"#;
1950 let error: JsonRpcError = serde_json::from_str(source).expect("decode arbitrary code");
1951
1952 assert_eq!(
1953 error.code.as_str(),
1954 "-340282366920938463463374607431768211457"
1955 );
1956 assert_eq!(
1957 serde_json::to_string(&error).expect("re-encode arbitrary code"),
1958 source
1959 );
1960 }
1961
1962 #[test]
1963 fn jsonrpc_error_rejects_nearby_fractional_code() {
1964 let source =
1965 r#"{"code":-340282366920938463463374607431768211457.5,"message":"not integer"}"#;
1966
1967 assert!(serde_json::from_str::<JsonRpcError>(source).is_err());
1968 }
1969
1970 #[test]
1971 fn jsonrpc_error_standard_codes() {
1972 let err = JsonRpcError {
1974 code: (-32700).into(),
1975 message: "Parse error".to_string(),
1976 data: None,
1977 };
1978 assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32700);
1979
1980 let err = JsonRpcError {
1982 code: (-32601).into(),
1983 message: "Method not found".to_string(),
1984 data: None,
1985 };
1986 assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32601);
1987
1988 let err = JsonRpcError {
1990 code: (-32603).into(),
1991 message: "Internal error".to_string(),
1992 data: None,
1993 };
1994 assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32603);
1995 }
1996
1997 #[test]
2002 fn jsonrpc_version_deserialize_borrows_static_for_response() {
2003 let resp: JsonRpcResponse =
2004 serde_json::from_str(r#"{"jsonrpc":"2.0","result":{"tools":[]},"id":1}"#)
2005 .expect("deserialize");
2006 assert!(matches!(resp.jsonrpc, Cow::Borrowed(JSONRPC_VERSION)));
2007 }
2008
2009 #[test]
2010 fn response_rejects_nonstandard_missing_and_non_string_jsonrpc_versions() {
2011 for input in [
2012 r#"{"jsonrpc":"2.1","result":{"tools":[]},"id":1}"#,
2013 r#"{"jsonrpc":"1.0","result":{"tools":[]},"id":1}"#,
2014 r#"{"jsonrpc":null,"result":{"tools":[]},"id":1}"#,
2015 r#"{"result":{"tools":[]},"id":1}"#,
2016 ] {
2017 let error = serde_json::from_str::<JsonRpcResponse>(input).unwrap_err();
2018 assert!(error.is_data(), "unexpected error for {input}: {error}");
2019 }
2020 }
2021
2022 #[test]
2023 fn serialization_rejects_mutated_nonstandard_jsonrpc_version() {
2024 let mut request = JsonRpcRequest::new("tools/list", None, 1_i64);
2025 request.jsonrpc = Cow::Borrowed("2.1");
2026 assert!(serde_json::to_string(&request).is_err());
2027
2028 let mut response = JsonRpcResponse::success(RequestId::Number(1), Value::Null);
2029 response.jsonrpc = Cow::Borrowed("1.0");
2030 assert!(serde_json::to_string(&response).is_err());
2031 }
2032
2033 #[test]
2034 fn response_success() {
2035 let resp = JsonRpcResponse::success(RequestId::Number(1), json!({"result": "ok"}));
2036 let value = serde_json::to_value(&resp).expect("serialize");
2037 assert_eq!(value["jsonrpc"], "2.0");
2038 assert_eq!(value["result"]["result"], "ok");
2039 assert_eq!(value["id"], 1);
2040 assert!(value.get("error").is_none());
2041 assert!(!resp.is_error());
2042 }
2043
2044 #[test]
2045 fn response_error() {
2046 let error = JsonRpcError {
2047 code: (-32601).into(),
2048 message: "Method not found".to_string(),
2049 data: None,
2050 };
2051 let resp = JsonRpcResponse::error(Some(RequestId::Number(1)), error);
2052 let value = serde_json::to_value(&resp).expect("serialize");
2053 assert_eq!(value["jsonrpc"], "2.0");
2054 assert!(value.get("result").is_none());
2055 assert_eq!(value["error"]["code"], -32601);
2056 assert_eq!(value["error"]["message"], "Method not found");
2057 assert_eq!(value["id"], 1);
2058 assert!(resp.is_error());
2059 }
2060
2061 #[test]
2062 fn uncorrelated_response_error_omits_id() {
2063 let error = JsonRpcError {
2064 code: (-32700).into(),
2065 message: "Parse error".to_string(),
2066 data: None,
2067 };
2068 let resp = JsonRpcResponse::error(None, error);
2069 let value = serde_json::to_value(&resp).expect("serialize");
2070 assert!(value.get("id").is_none());
2071 }
2072
2073 #[test]
2074 fn response_rejects_explicit_null_id_but_accepts_absent_id() {
2075 let explicit_null = serde_json::from_str::<JsonRpcResponse>(
2076 r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#,
2077 );
2078 assert!(explicit_null.is_err());
2079
2080 let absent = serde_json::from_str::<JsonRpcResponse>(
2081 r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"}}"#,
2082 )
2083 .expect("an uncorrelated MCP error omits id");
2084 assert!(absent.id.is_none());
2085 }
2086
2087 #[test]
2088 fn response_round_trip() {
2089 let original =
2090 JsonRpcResponse::success(RequestId::String("abc".to_string()), json!({"tools": []}));
2091 let json_str = serde_json::to_string(&original).expect("serialize");
2092 let deserialized: JsonRpcResponse = serde_json::from_str(&json_str).expect("deserialize");
2093 assert!(!deserialized.is_error());
2094 assert!(deserialized.result.is_some());
2095 assert_eq!(deserialized.id, Some(RequestId::String("abc".to_string())));
2096 }
2097
2098 #[test]
2099 fn response_null_result_round_trip_preserves_member_presence() {
2100 let raw = r#"{"jsonrpc":"2.0","result":null,"id":1}"#;
2101 let response: JsonRpcResponse = serde_json::from_str(raw).expect("deserialize response");
2102
2103 assert_eq!(response.result, Some(Value::Null));
2104 assert!(response.error.is_none());
2105
2106 let encoded = serde_json::to_value(response).expect("serialize response");
2107 assert_eq!(encoded.get("result"), Some(&Value::Null));
2108 assert!(encoded.get("error").is_none());
2109 }
2110
2111 #[test]
2112 fn response_rejects_both_or_neither_outcome_members() {
2113 for raw in [
2114 r#"{"jsonrpc":"2.0","result":null,"error":{"code":-32603,"message":"failure"},"id":1}"#,
2115 r#"{"jsonrpc":"2.0","id":1}"#,
2116 ] {
2117 let error = serde_json::from_str::<JsonRpcResponse>(raw)
2118 .expect_err("invalid response envelope must be rejected");
2119 assert!(error.to_string().contains("exactly one"));
2120 }
2121
2122 let both = JsonRpcResponse {
2123 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
2124 result: Some(Value::Null),
2125 error: Some(JsonRpcError {
2126 code: (-32_603).into(),
2127 message: "failure".to_string(),
2128 data: None,
2129 }),
2130 id: Some(RequestId::Number(1)),
2131 };
2132 assert!(serde_json::to_value(both).is_err());
2133
2134 let neither = JsonRpcResponse {
2135 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
2136 result: None,
2137 error: None,
2138 id: Some(RequestId::Number(1)),
2139 };
2140 assert!(serde_json::to_value(neither).is_err());
2141 }
2142
2143 #[test]
2144 fn response_rejects_unknown_top_level_envelope_members() {
2145 let error = serde_json::from_str::<JsonRpcResponse>(
2146 r#"{"jsonrpc":"2.0","result":null,"id":1,"extension":true}"#,
2147 )
2148 .expect_err("response envelopes are closed");
2149
2150 assert!(error.to_string().contains("unknown field"));
2151 }
2152
2153 #[test]
2154 fn response_validation_rejects_uncorrelated_success() {
2155 let response = JsonRpcResponse {
2156 jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
2157 result: Some(Value::Null),
2158 error: None,
2159 id: None,
2160 };
2161
2162 assert_eq!(
2163 response.validate(),
2164 Err("JSON-RPC success response must contain an id")
2165 );
2166 assert!(serde_json::to_value(response).is_err());
2167 assert!(
2168 serde_json::from_str::<JsonRpcResponse>(r#"{"jsonrpc":"2.0","result":null}"#).is_err()
2169 );
2170 }
2171
2172 #[test]
2177 fn message_request_variant() {
2178 let req = JsonRpcRequest::new("tools/list", None, 1i64);
2179 let msg = JsonRpcMessage::Request(req);
2180 let value = serde_json::to_value(&msg).expect("serialize");
2181 assert_eq!(value["method"], "tools/list");
2182 }
2183
2184 #[test]
2185 fn message_response_variant() {
2186 let resp = JsonRpcResponse::success(RequestId::Number(1), json!("ok"));
2187 let msg = JsonRpcMessage::Response(resp);
2188 let value = serde_json::to_value(&msg).expect("serialize");
2189 assert_eq!(value["result"], "ok");
2190 }
2191
2192 #[test]
2193 fn message_deserialize_as_request() {
2194 let json_str = r#"{"jsonrpc":"2.0","method":"tools/list","id":1}"#;
2195 let msg: JsonRpcMessage = serde_json::from_str(json_str).expect("deserialize");
2196 let (method, id) = match msg {
2197 JsonRpcMessage::Request(req) => (req.method, req.id),
2198 JsonRpcMessage::Response(_) => (String::new(), None),
2199 };
2200 assert_eq!(method, "tools/list");
2201 assert_eq!(id, Some(RequestId::Number(1)));
2202 }
2203
2204 #[test]
2205 fn message_deserialize_as_response() {
2206 let json_str = r#"{"jsonrpc":"2.0","result":{"tools":[]},"id":1}"#;
2207 let msg: JsonRpcMessage = serde_json::from_str(json_str).expect("deserialize");
2208 let (is_error, id) = match msg {
2209 JsonRpcMessage::Response(resp) => (resp.is_error(), resp.id),
2210 JsonRpcMessage::Request(_) => (true, None),
2211 };
2212 assert!(!is_error);
2213 assert_eq!(id, Some(RequestId::Number(1)));
2214 }
2215
2216 #[test]
2217 fn message_rejects_mixed_request_and_response_envelopes() {
2218 for raw in [
2219 r#"{"jsonrpc":"2.0","method":"tools/list","result":null,"id":1}"#,
2220 r#"{"jsonrpc":"2.0","params":{},"error":{"code":-32603,"message":"failure"},"id":1}"#,
2221 ] {
2222 assert!(
2223 serde_json::from_str::<JsonRpcMessage>(raw).is_err(),
2224 "mixed envelope was accepted: {raw}"
2225 );
2226 }
2227 }
2228
2229 #[test]
2230 fn message_validation_catches_public_field_mutation() {
2231 let mut request = JsonRpcRequest::new("tools/list", None, 1_i64);
2232 request.jsonrpc = Cow::Borrowed("2.1");
2233 let message = JsonRpcMessage::Request(request);
2234
2235 assert_eq!(message.validate(), Err("jsonrpc must be exactly \"2.0\""));
2236 }
2237
2238 #[test]
2243 fn jsonrpc_version_constant() {
2244 assert_eq!(JSONRPC_VERSION, "2.0");
2245 }
2246}