1use std::collections::BTreeMap;
8use std::fmt;
9
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use serde_json::Value;
12
13use crate::common_types::{Implementation, JsonInteger, OpenMetadata};
14use crate::jsonrpc::{RawJsonAdmissionError, admit_raw_jsonrpc_document};
15use crate::protocol_policy::ProtocolEra;
16
17pub const MAX_RESULT_ENCODED_BYTES: usize = 1_048_576;
19pub const MAX_RESULT_DEPTH: usize = 64;
21pub const MAX_RESULT_CONTAINER_MEMBERS: usize = 1_024;
23pub const MAX_RESULT_STRING_BYTES: usize = 65_536;
25pub const MAX_RESULT_NUMBER_BYTES: usize = 1_024;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum ExactJsonValue {
33 Null,
35 Bool(bool),
37 String(String),
39 Number(String),
41 Array(Vec<ExactJsonValue>),
43 Object(ExactJsonObject),
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ExactJsonMember {
50 pub name: String,
52 pub value: ExactJsonValue,
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq)]
58pub struct ExactJsonObject {
59 members: Vec<ExactJsonMember>,
60}
61
62impl ExactJsonObject {
63 #[must_use]
65 pub fn members(&self) -> &[ExactJsonMember] {
66 &self.members
67 }
68
69 #[must_use]
71 pub fn get(&self, name: &str) -> Option<&ExactJsonValue> {
72 self.members
73 .iter()
74 .find(|member| member.name == name)
75 .map(|member| &member.value)
76 }
77
78 fn take(&mut self, name: &str) -> Option<ExactJsonValue> {
79 self.members
80 .iter()
81 .position(|member| member.name == name)
82 .map(|index| self.members.remove(index).value)
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ResultDecodeError {
89 kind: ResultDecodeErrorKind,
90 path: String,
91 raw_envelope: Option<RawResultEnvelope>,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum ResultDecodeErrorKind {
97 InvalidJson,
99 BoundExceeded,
101 DuplicateMember,
103 ExpectedObject,
105 InvalidKnownMember,
107 InvalidDiscriminator,
109 MissingDiscriminator,
111 MissingInputRequest,
113 RejectedExtension,
115 KnownMemberCollision,
117 UnexpectedResultType,
119}
120
121impl ResultDecodeError {
122 fn new(kind: ResultDecodeErrorKind, path: impl Into<String>) -> Self {
123 Self {
124 kind,
125 path: path.into(),
126 raw_envelope: None,
127 }
128 }
129
130 fn rejected_extension(raw_envelope: RawResultEnvelope) -> Self {
131 Self {
132 kind: ResultDecodeErrorKind::RejectedExtension,
133 path: "$.resultType".to_owned(),
134 raw_envelope: Some(raw_envelope),
135 }
136 }
137
138 #[must_use]
140 pub const fn kind(&self) -> ResultDecodeErrorKind {
141 self.kind
142 }
143
144 #[must_use]
146 pub fn path(&self) -> &str {
147 &self.path
148 }
149
150 #[must_use]
152 pub fn invalid_known_member(path: impl Into<String>) -> Self {
153 Self::new(ResultDecodeErrorKind::InvalidKnownMember, path)
154 }
155
156 #[must_use]
159 pub fn raw_envelope(&self) -> Option<&RawResultEnvelope> {
160 self.raw_envelope.as_ref()
161 }
162}
163
164impl fmt::Display for ResultDecodeError {
165 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(
167 formatter,
168 "result decode error {:?} at {}",
169 self.kind, self.path
170 )
171 }
172}
173
174impl std::error::Error for ResultDecodeError {}
175
176pub fn parse_exact_json(input: &str) -> Result<ExactJsonValue, ResultDecodeError> {
178 if input.len() > MAX_RESULT_ENCODED_BYTES {
179 return Err(ResultDecodeError::new(
180 ResultDecodeErrorKind::BoundExceeded,
181 "$",
182 ));
183 }
184 let mut parser = ExactJsonParser::new(input);
185 parser.skip_whitespace();
186 let value = parser.value(0, "$")?;
187 parser.skip_whitespace();
188 if parser.offset != input.len() {
189 return Err(ResultDecodeError::new(
190 ResultDecodeErrorKind::InvalidJson,
191 "$",
192 ));
193 }
194 Ok(value)
195}
196
197pub fn exact_json_from_serde(
200 value: &serde_json::Value,
201) -> Result<ExactJsonValue, ResultDecodeError> {
202 let exact = exact_json_from_serde_unchecked(value);
203 let _ = exact_json_value_len(&exact, 0)?;
204 Ok(exact)
205}
206
207fn exact_json_from_serde_unchecked(value: &serde_json::Value) -> ExactJsonValue {
208 match value {
209 serde_json::Value::Null => ExactJsonValue::Null,
210 serde_json::Value::Bool(value) => ExactJsonValue::Bool(*value),
211 serde_json::Value::String(value) => ExactJsonValue::String(value.clone()),
212 serde_json::Value::Number(value) => ExactJsonValue::Number(value.to_string()),
213 serde_json::Value::Array(values) => {
214 ExactJsonValue::Array(values.iter().map(exact_json_from_serde_unchecked).collect())
215 }
216 serde_json::Value::Object(values) => ExactJsonValue::Object(ExactJsonObject {
217 members: values
218 .iter()
219 .map(|(name, value)| ExactJsonMember {
220 name: name.clone(),
221 value: exact_json_from_serde_unchecked(value),
222 })
223 .collect(),
224 }),
225 }
226}
227
228pub fn exact_json_to_serde(value: &ExactJsonValue) -> Result<serde_json::Value, ResultDecodeError> {
232 match value {
233 ExactJsonValue::Null => Ok(serde_json::Value::Null),
234 ExactJsonValue::Bool(value) => Ok(serde_json::Value::Bool(*value)),
235 ExactJsonValue::String(value) => Ok(serde_json::Value::String(value.clone())),
236 ExactJsonValue::Number(value) => serde_json::from_str(value)
237 .map_err(|_| ResultDecodeError::new(ResultDecodeErrorKind::InvalidJson, "$")),
238 ExactJsonValue::Array(values) => values
239 .iter()
240 .map(exact_json_to_serde)
241 .collect::<Result<Vec<_>, _>>()
242 .map(serde_json::Value::Array),
243 ExactJsonValue::Object(object) => object
244 .members()
245 .iter()
246 .map(|member| Ok((member.name.clone(), exact_json_to_serde(&member.value)?)))
247 .collect::<Result<serde_json::Map<_, _>, ResultDecodeError>>()
248 .map(serde_json::Value::Object),
249 }
250}
251
252pub(crate) fn parse_exact_result_object(input: &str) -> Result<ExactJsonObject, ResultDecodeError> {
258 admit_raw_jsonrpc_document(input.as_bytes(), MAX_RESULT_ENCODED_BYTES)
259 .map_err(result_raw_admission_error)?;
260 let ExactJsonValue::Object(object) = parse_exact_json(input)? else {
261 return Err(ResultDecodeError::new(
262 ResultDecodeErrorKind::ExpectedObject,
263 "$",
264 ));
265 };
266 Ok(object)
267}
268
269fn result_raw_admission_error(error: RawJsonAdmissionError) -> ResultDecodeError {
270 let kind = match error {
271 RawJsonAdmissionError::DuplicateObjectMember => ResultDecodeErrorKind::DuplicateMember,
272 RawJsonAdmissionError::DocumentTooLarge
273 | RawJsonAdmissionError::NestingTooDeep
274 | RawJsonAdmissionError::TooManyContainerEntries
275 | RawJsonAdmissionError::NumberTooLong
276 | RawJsonAdmissionError::TooManyNumberBytes
277 | RawJsonAdmissionError::ExponentTooLarge
278 | RawJsonAdmissionError::TooManyDecodedStringBytes => ResultDecodeErrorKind::BoundExceeded,
279 RawJsonAdmissionError::InvalidUtf8
280 | RawJsonAdmissionError::ByteOrderMark
281 | RawJsonAdmissionError::InvalidSyntax
282 | RawJsonAdmissionError::TopLevelBatch
283 | RawJsonAdmissionError::TopLevelNotObject => ResultDecodeErrorKind::InvalidJson,
284 };
285 ResultDecodeError::new(kind, "$")
286}
287
288#[derive(Debug, Clone)]
290pub struct ResultMeta {
291 pub server_info: Option<Implementation>,
297 meta: Option<OpenMetadata>,
298 exact_meta: Option<ExactJsonObject>,
299}
300
301impl ResultMeta {
302 #[must_use]
306 pub const fn empty() -> Self {
307 Self {
308 server_info: None,
309 meta: None,
310 exact_meta: None,
311 }
312 }
313
314 #[must_use]
316 pub fn server_generated(server_info: Implementation) -> Self {
317 let value = serde_json::to_value(server_info)
318 .expect("final implementation identity always serializes");
319 let metadata = OpenMetadata::try_from_entries([(
320 "io.modelcontextprotocol/serverInfo".to_owned(),
321 value,
322 )])
323 .expect("final implementation identity is valid result metadata");
324 Self {
325 server_info: None,
326 meta: None,
327 exact_meta: None,
328 }
329 .with_metadata(metadata)
330 }
331
332 #[must_use]
334 pub fn with_metadata(mut self, metadata: OpenMetadata) -> Self {
335 let object = metadata.entries().clone().into_iter().collect();
336 let exact = exact_json_from_serde_unchecked(&serde_json::Value::Object(object));
337 let ExactJsonValue::Object(exact_meta) = exact else {
338 unreachable!("metadata always encodes as an object");
339 };
340 self.meta = Some(metadata);
341 self.exact_meta = Some(exact_meta);
342 self
343 }
344
345 #[must_use]
348 pub fn metadata(&self) -> MetadataView<'_> {
349 MetadataView {
350 object: self.exact_meta.as_ref(),
351 }
352 }
353
354 pub fn final_server_info(&self) -> Result<Option<Implementation>, ResultDecodeError> {
356 self.meta.as_ref().map_or(Ok(None), |metadata| {
357 metadata.server_info().map_err(|_| {
358 ResultDecodeError::new(ResultDecodeErrorKind::InvalidKnownMember, "$._meta")
359 })
360 })
361 }
362}
363
364#[derive(Debug, Clone, Copy)]
366pub struct MetadataView<'a> {
367 object: Option<&'a ExactJsonObject>,
368}
369
370impl MetadataView<'_> {
371 #[must_use]
373 pub fn get(&self, name: &str) -> Option<&ExactJsonValue> {
374 self.object.and_then(|object| object.get(name))
375 }
376
377 #[must_use]
379 pub fn is_empty(&self) -> bool {
380 self.object.is_none_or(|object| object.members().is_empty())
381 }
382}
383
384#[derive(Debug, Clone, Default, PartialEq, Eq)]
386pub struct UnknownResultMembers {
387 members: Vec<ExactJsonMember>,
388}
389
390impl UnknownResultMembers {
391 pub fn try_new(
394 members: Vec<ExactJsonMember>,
395 known_names: &[&str],
396 ) -> Result<Self, ResultDecodeError> {
397 if members.len() > MAX_RESULT_CONTAINER_MEMBERS {
398 return Err(ResultDecodeError::new(
399 ResultDecodeErrorKind::BoundExceeded,
400 "$",
401 ));
402 }
403 for (index, member) in members.iter().enumerate() {
404 if COMMON_RESULT_MEMBER_NAMES.contains(&member.name.as_str())
405 || known_names.contains(&member.name.as_str())
406 {
407 return Err(ResultDecodeError::new(
408 ResultDecodeErrorKind::KnownMemberCollision,
409 member.name.clone(),
410 ));
411 }
412 if members[..index]
413 .iter()
414 .any(|preceding| preceding.name == member.name)
415 {
416 return Err(ResultDecodeError::new(
417 ResultDecodeErrorKind::DuplicateMember,
418 member.name.clone(),
419 ));
420 }
421 }
422 validate_local_result_members(&members)?;
423 Ok(Self { members })
424 }
425
426 #[must_use]
428 pub fn members(&self) -> &[ExactJsonMember] {
429 &self.members
430 }
431
432 #[must_use]
434 pub fn into_members(self) -> Vec<ExactJsonMember> {
435 self.members
436 }
437}
438
439const COMMON_RESULT_MEMBER_NAMES: [&str; 3] = ["resultType", "_meta", "serverInfo"];
440
441const FINAL_ONLY_METADATA_MEMBER_NAMES: [&str; 6] = [
446 "io.modelcontextprotocol/protocolVersion",
447 "io.modelcontextprotocol/clientCapabilities",
448 "io.modelcontextprotocol/clientInfo",
449 "io.modelcontextprotocol/logLevel",
450 "io.modelcontextprotocol/serverInfo",
451 "io.modelcontextprotocol/subscriptionId",
452];
453
454const FINAL_ORDINARY_RESULT_FORBIDDEN_METADATA_MEMBER_NAMES: [&str; 5] = [
462 "io.modelcontextprotocol/protocolVersion",
463 "io.modelcontextprotocol/clientCapabilities",
464 "io.modelcontextprotocol/clientInfo",
465 "io.modelcontextprotocol/logLevel",
466 "io.modelcontextprotocol/subscriptionId",
467];
468
469#[derive(Clone, Copy, Debug, Eq, PartialEq)]
475pub(crate) enum FinalResultMetadataRole {
476 Ordinary,
478 SubscriptionsListen,
480}
481
482pub(crate) fn has_final_only_metadata(value: &Value) -> bool {
488 value
489 .as_object()
490 .and_then(|object| object.get("_meta"))
491 .and_then(Value::as_object)
492 .is_some_and(|metadata| {
493 FINAL_ONLY_METADATA_MEMBER_NAMES
494 .iter()
495 .any(|member| metadata.contains_key(*member))
496 })
497}
498
499fn exact_result_carries_final_only_metadata(members: &ExactJsonObject) -> bool {
500 matches!(
501 members.get("_meta"),
502 Some(ExactJsonValue::Object(metadata))
503 if FINAL_ONLY_METADATA_MEMBER_NAMES
504 .iter()
505 .any(|member| metadata.get(member).is_some())
506 )
507}
508
509fn exact_result_metadata_entries(
510 metadata: &ExactJsonObject,
511) -> Result<BTreeMap<String, Value>, ResultDecodeError> {
512 let Value::Object(entries) = exact_json_to_serde(&ExactJsonValue::Object(metadata.clone()))?
513 else {
514 unreachable!("exact result metadata always converts to an object");
515 };
516 Ok(entries.into_iter().collect())
517}
518
519pub(crate) fn validate_final_result_metadata_entries(
524 entries: &BTreeMap<String, Value>,
525 role: FinalResultMetadataRole,
526) -> Result<(), ResultDecodeError> {
527 if let Some(member) = FINAL_ORDINARY_RESULT_FORBIDDEN_METADATA_MEMBER_NAMES
528 .iter()
529 .copied()
530 .find(|member| {
531 entries.contains_key(*member)
532 && (role == FinalResultMetadataRole::Ordinary
533 || *member != "io.modelcontextprotocol/subscriptionId")
534 })
535 {
536 return Err(ResultDecodeError::new(
537 ResultDecodeErrorKind::InvalidKnownMember,
538 format!("$._meta.{member}"),
539 ));
540 }
541 if let Some(server_info) = entries.get("io.modelcontextprotocol/serverInfo") {
542 serde_json::from_value::<Implementation>(server_info.clone()).map_err(|_| {
543 ResultDecodeError::new(
544 ResultDecodeErrorKind::InvalidKnownMember,
545 "$._meta.io.modelcontextprotocol/serverInfo",
546 )
547 })?;
548 }
549 Ok(())
550}
551
552fn validate_local_result_members(members: &[ExactJsonMember]) -> Result<(), ResultDecodeError> {
553 let encoded_bytes = exact_json_members_len(members, 0)?;
554 if encoded_bytes > MAX_RESULT_ENCODED_BYTES {
555 return Err(ResultDecodeError::new(
556 ResultDecodeErrorKind::BoundExceeded,
557 "$",
558 ));
559 }
560 Ok(())
561}
562
563fn exact_json_members_len(
564 members: &[ExactJsonMember],
565 depth: usize,
566) -> Result<usize, ResultDecodeError> {
567 if depth > MAX_RESULT_DEPTH || members.len() > MAX_RESULT_CONTAINER_MEMBERS {
568 return Err(ResultDecodeError::new(
569 ResultDecodeErrorKind::BoundExceeded,
570 "$",
571 ));
572 }
573 let mut encoded_bytes = 2_usize;
574 for (index, member) in members.iter().enumerate() {
575 if member.name.len() > MAX_RESULT_STRING_BYTES {
576 return Err(ResultDecodeError::new(
577 ResultDecodeErrorKind::BoundExceeded,
578 "$.<extra-name>",
579 ));
580 }
581 if members[..index]
582 .iter()
583 .any(|preceding| preceding.name == member.name)
584 {
585 return Err(ResultDecodeError::new(
586 ResultDecodeErrorKind::DuplicateMember,
587 member.name.clone(),
588 ));
589 }
590 let value_bytes = exact_json_value_len(&member.value, depth + 1)?;
591 let member_bytes = encoded_json_string_len(&member.name)?
592 .checked_add(1)
593 .and_then(|size| size.checked_add(value_bytes))
594 .ok_or_else(|| ResultDecodeError::new(ResultDecodeErrorKind::BoundExceeded, "$"))?;
595 encoded_bytes = encoded_bytes
596 .checked_add(usize::from(index != 0))
597 .and_then(|size| size.checked_add(member_bytes))
598 .ok_or_else(|| ResultDecodeError::new(ResultDecodeErrorKind::BoundExceeded, "$"))?;
599 if encoded_bytes > MAX_RESULT_ENCODED_BYTES {
600 return Err(ResultDecodeError::new(
601 ResultDecodeErrorKind::BoundExceeded,
602 "$",
603 ));
604 }
605 }
606 Ok(encoded_bytes)
607}
608
609fn exact_json_value_len(value: &ExactJsonValue, depth: usize) -> Result<usize, ResultDecodeError> {
610 if depth > MAX_RESULT_DEPTH {
611 return Err(ResultDecodeError::new(
612 ResultDecodeErrorKind::BoundExceeded,
613 "$",
614 ));
615 }
616 match value {
617 ExactJsonValue::Null => Ok(4),
618 ExactJsonValue::Bool(true) => Ok(4),
619 ExactJsonValue::Bool(false) => Ok(5),
620 ExactJsonValue::String(value) => {
621 if value.len() > MAX_RESULT_STRING_BYTES {
622 return Err(ResultDecodeError::new(
623 ResultDecodeErrorKind::BoundExceeded,
624 "$",
625 ));
626 }
627 encoded_json_string_len(value)
628 }
629 ExactJsonValue::Number(value) => {
630 if value.len() > MAX_RESULT_NUMBER_BYTES {
631 return Err(ResultDecodeError::new(
632 ResultDecodeErrorKind::BoundExceeded,
633 "$",
634 ));
635 }
636 match parse_exact_json(value)? {
637 ExactJsonValue::Number(parsed) if parsed == *value => Ok(value.len()),
638 _ => Err(ResultDecodeError::new(
639 ResultDecodeErrorKind::InvalidJson,
640 "$",
641 )),
642 }
643 }
644 ExactJsonValue::Array(values) => {
645 if values.len() > MAX_RESULT_CONTAINER_MEMBERS {
646 return Err(ResultDecodeError::new(
647 ResultDecodeErrorKind::BoundExceeded,
648 "$",
649 ));
650 }
651 let mut encoded_bytes = 2_usize;
652 for (index, value) in values.iter().enumerate() {
653 let separator_bytes = usize::from(index != 0);
654 let value_bytes = exact_json_value_len(value, depth + 1)?;
655 encoded_bytes = encoded_bytes
656 .checked_add(separator_bytes)
657 .and_then(|size| size.checked_add(value_bytes))
658 .ok_or_else(|| {
659 ResultDecodeError::new(ResultDecodeErrorKind::BoundExceeded, "$")
660 })?;
661 if encoded_bytes > MAX_RESULT_ENCODED_BYTES {
662 return Err(ResultDecodeError::new(
663 ResultDecodeErrorKind::BoundExceeded,
664 "$",
665 ));
666 }
667 }
668 Ok(encoded_bytes)
669 }
670 ExactJsonValue::Object(object) => exact_json_members_len(&object.members, depth + 1),
671 }
672}
673
674fn encoded_json_string_len(value: &str) -> Result<usize, ResultDecodeError> {
675 let mut encoded_bytes = 2_usize;
676 for character in value.chars() {
677 let bytes = match character {
678 '"' | '\\' | '\u{0008}' | '\u{000c}' | '\n' | '\r' | '\t' => 2,
679 '\u{0000}'..='\u{001f}' => 6,
680 _ => character.len_utf8(),
681 };
682 encoded_bytes = encoded_bytes
683 .checked_add(bytes)
684 .ok_or_else(|| ResultDecodeError::new(ResultDecodeErrorKind::BoundExceeded, "$"))?;
685 }
686 Ok(encoded_bytes)
687}
688
689#[derive(Debug, Clone)]
691pub struct CompleteResult<T> {
692 pub payload: T,
694 pub meta: ResultMeta,
696 pub extras: UnknownResultMembers,
698}
699
700impl<T> CompleteResult<T> {
701 #[must_use]
704 pub fn new(payload: T, meta: ResultMeta) -> Self {
705 Self {
706 payload,
707 meta,
708 extras: UnknownResultMembers::default(),
709 }
710 }
711}
712
713pub struct TypedCompleteMembers<'a> {
715 members: &'a mut ExactJsonObject,
716 declared_names: &'static [&'static str],
717}
718
719impl TypedCompleteMembers<'_> {
720 pub fn take(&mut self, name: &str) -> Result<Option<ExactJsonValue>, ResultDecodeError> {
725 if !self.declared_names.contains(&name) {
726 return Err(ResultDecodeError::new(
727 ResultDecodeErrorKind::KnownMemberCollision,
728 format!("$.{name}"),
729 ));
730 }
731 Ok(self.members.take(name))
732 }
733}
734
735pub trait CompleteResultPayload: Sized {
742 const KNOWN_MEMBER_NAMES: &'static [&'static str];
744
745 fn decode_known_members(
747 members: &mut TypedCompleteMembers<'_>,
748 ) -> Result<Self, ResultDecodeError>;
749}
750
751#[derive(Debug, Clone)]
753pub struct InputRequiredResult {
754 input_requests: Option<ExactJsonObject>,
756 request_state: Option<String>,
758 pub meta: ResultMeta,
760 pub extras: UnknownResultMembers,
762}
763
764impl InputRequiredResult {
765 pub fn new(
767 input_requests: Option<ExactJsonObject>,
768 request_state: Option<String>,
769 meta: ResultMeta,
770 ) -> Result<Self, ResultDecodeError> {
771 if input_requests.is_none() && request_state.is_none() {
772 return Err(ResultDecodeError::new(
773 ResultDecodeErrorKind::MissingInputRequest,
774 "$",
775 ));
776 }
777 Ok(Self {
778 input_requests,
779 request_state,
780 meta,
781 extras: UnknownResultMembers::default(),
782 })
783 }
784
785 #[must_use]
787 pub fn input_requests(&self) -> Option<&ExactJsonObject> {
788 self.input_requests.as_ref()
789 }
790
791 #[must_use]
793 pub fn request_state(&self) -> Option<&str> {
794 self.request_state.as_deref()
795 }
796}
797
798#[derive(Debug, Clone, PartialEq, Eq)]
800pub struct CacheTtl(JsonInteger);
801
802#[derive(Debug, Clone, Copy, PartialEq, Eq)]
804pub enum CacheTtlConversionError {
805 Negative,
807 RuntimeOutOfRange,
809}
810
811impl fmt::Display for CacheTtlConversionError {
812 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
813 formatter.write_str(match self {
814 Self::Negative => "cache ttlMs must be a nonnegative JSON integer",
815 Self::RuntimeOutOfRange => "cache ttlMs exceeds the local u64 runtime domain",
816 })
817 }
818}
819
820impl std::error::Error for CacheTtlConversionError {}
821
822impl CacheTtl {
823 #[must_use]
825 pub fn milliseconds(value: u64) -> Self {
826 Self(JsonInteger::from(value))
827 }
828
829 #[must_use]
831 pub fn as_str(&self) -> &str {
832 self.0.as_str()
833 }
834
835 pub fn try_as_millis(&self) -> Result<u64, CacheTtlConversionError> {
840 cache_ttl_runtime_millis(self.0.as_str())
841 }
842}
843
844impl TryFrom<JsonInteger> for CacheTtl {
845 type Error = CacheTtlConversionError;
846
847 fn try_from(value: JsonInteger) -> Result<Self, Self::Error> {
848 if cache_ttl_is_negative(value.as_str()) {
849 return Err(CacheTtlConversionError::Negative);
850 }
851 Ok(Self(value))
852 }
853}
854
855impl Serialize for CacheTtl {
856 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
857 where
858 S: Serializer,
859 {
860 self.0.serialize(serializer)
861 }
862}
863
864impl<'de> Deserialize<'de> for CacheTtl {
865 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
866 where
867 D: Deserializer<'de>,
868 {
869 JsonInteger::deserialize(deserializer)
870 .and_then(|value| Self::try_from(value).map_err(serde::de::Error::custom))
871 }
872}
873
874fn cache_ttl_is_negative(lexeme: &str) -> bool {
875 let Some(absolute) = lexeme.strip_prefix('-') else {
876 return false;
877 };
878 absolute
879 .split(['e', 'E'])
880 .next()
881 .is_some_and(|mantissa| mantissa.bytes().any(|byte| matches!(byte, b'1'..=b'9')))
882}
883
884fn cache_ttl_runtime_millis(lexeme: &str) -> Result<u64, CacheTtlConversionError> {
885 let unsigned = lexeme.strip_prefix('-').unwrap_or(lexeme);
886 let (mantissa, exponent) = match unsigned.split_once(['e', 'E']) {
887 Some((mantissa, exponent)) => (mantissa, exponent),
888 None => (unsigned, "0"),
889 };
890 let exponent = exponent
891 .parse::<i128>()
892 .map_err(|_| CacheTtlConversionError::RuntimeOutOfRange)?;
893 let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
894 let digits = format!("{whole}{fraction}");
895 if !digits.bytes().any(|byte| matches!(byte, b'1'..=b'9')) {
896 return Ok(0);
897 }
898
899 let fraction_len =
900 i128::try_from(fraction.len()).map_err(|_| CacheTtlConversionError::RuntimeOutOfRange)?;
901 let decimal_shift = exponent
902 .checked_sub(fraction_len)
903 .ok_or(CacheTtlConversionError::RuntimeOutOfRange)?;
904 let integer_digits = if decimal_shift >= 0 {
905 let zeroes = usize::try_from(decimal_shift)
906 .map_err(|_| CacheTtlConversionError::RuntimeOutOfRange)?;
907 let length = digits
908 .len()
909 .checked_add(zeroes)
910 .ok_or(CacheTtlConversionError::RuntimeOutOfRange)?;
911 if length > 20 {
912 return Err(CacheTtlConversionError::RuntimeOutOfRange);
913 }
914 format!("{digits}{}", "0".repeat(zeroes))
915 } else {
916 let trimmed = usize::try_from(decimal_shift.unsigned_abs())
917 .map_err(|_| CacheTtlConversionError::RuntimeOutOfRange)?;
918 let Some(length) = digits.len().checked_sub(trimmed) else {
919 return Err(CacheTtlConversionError::RuntimeOutOfRange);
920 };
921 if !digits[length..].bytes().all(|byte| byte == b'0') {
922 return Err(CacheTtlConversionError::RuntimeOutOfRange);
923 }
924 digits[..length].to_owned()
925 };
926 let normalized = integer_digits.trim_start_matches('0');
927 if normalized.is_empty() {
928 return Ok(0);
929 }
930 if normalized.len() > 20 {
931 return Err(CacheTtlConversionError::RuntimeOutOfRange);
932 }
933 normalized
934 .parse()
935 .map_err(|_| CacheTtlConversionError::RuntimeOutOfRange)
936}
937
938#[derive(Debug, Clone, Copy, PartialEq, Eq)]
940pub enum CacheScope {
941 Public,
943 Private,
945}
946
947#[derive(Debug, Clone)]
949pub struct CacheableResult<T> {
950 pub result: CompleteResult<T>,
952 pub ttl: CacheTtl,
954 pub scope: CacheScope,
956}
957
958#[derive(Debug, Clone)]
960pub struct PaginatedResult<T> {
961 pub result: CompleteResult<T>,
963 pub next_cursor: String,
965}
966
967#[derive(Debug, Clone, PartialEq, Eq)]
970pub struct RawResultEnvelope {
971 discriminator: String,
972 members: ExactJsonObject,
973}
974
975impl RawResultEnvelope {
976 #[must_use]
978 pub fn discriminator(&self) -> &str {
979 &self.discriminator
980 }
981
982 #[must_use]
984 pub fn members(&self) -> &[ExactJsonMember] {
985 self.members.members()
986 }
987}
988
989#[derive(Debug, Clone, Copy, PartialEq, Eq)]
991pub enum ResultDiscriminatorDecision {
992 Core,
994 DeferredExtension,
996 Rejected,
998}
999
1000mod sealed {
1001 pub trait Sealed {}
1002}
1003
1004pub trait ResultDiscriminatorPolicy: sealed::Sealed {
1007 fn decide(&self, discriminator: &str) -> ResultDiscriminatorDecision;
1009}
1010
1011#[derive(Debug, Clone, Copy, Default)]
1013pub struct CoreResultDiscriminatorPolicy;
1014
1015impl sealed::Sealed for CoreResultDiscriminatorPolicy {}
1016
1017impl ResultDiscriminatorPolicy for CoreResultDiscriminatorPolicy {
1018 fn decide(&self, discriminator: &str) -> ResultDiscriminatorDecision {
1019 match discriminator {
1020 "complete" | "input_required" => ResultDiscriminatorDecision::Core,
1021 _ => ResultDiscriminatorDecision::Rejected,
1022 }
1023 }
1024}
1025
1026#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1028pub enum ResultPeerEra {
1029 Legacy,
1031 Modern,
1033}
1034
1035impl From<ProtocolEra> for ResultPeerEra {
1036 fn from(era: ProtocolEra) -> Self {
1037 match era {
1038 ProtocolEra::Legacy2024 => Self::Legacy,
1039 ProtocolEra::Modern2026 => Self::Modern,
1040 }
1041 }
1042}
1043
1044#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1046pub enum ResultPeerDiagnostic {
1047 LegacyCompatibilityShape,
1049 ModernMissingResultType,
1055}
1056
1057#[derive(Debug, Clone)]
1059pub enum DecodedResult {
1060 Complete(CompleteResult<ExactJsonObject>),
1062 InputRequired(InputRequiredResult),
1064 Deferred(RawResultEnvelope),
1066}
1067
1068pub fn decode_peer_result(
1077 input: &str,
1078 era: ResultPeerEra,
1079 policy: &dyn ResultDiscriminatorPolicy,
1080) -> Result<(DecodedResult, Option<ResultPeerDiagnostic>), ResultDecodeError> {
1081 decode_peer_result_with_metadata_role(input, era, policy, FinalResultMetadataRole::Ordinary)
1082}
1083
1084pub(crate) fn decode_peer_result_with_metadata_role(
1089 input: &str,
1090 era: ResultPeerEra,
1091 policy: &dyn ResultDiscriminatorPolicy,
1092 metadata_role: FinalResultMetadataRole,
1093) -> Result<(DecodedResult, Option<ResultPeerDiagnostic>), ResultDecodeError> {
1094 let mut members = parse_exact_result_object(input)?;
1095 let result_type = match members.get("resultType") {
1096 None if era == ResultPeerEra::Legacy => ("complete".to_owned(), None),
1097 None => (
1098 "complete".to_owned(),
1099 Some(ResultPeerDiagnostic::ModernMissingResultType),
1100 ),
1101 Some(ExactJsonValue::String(value)) => (value.clone(), None),
1102 Some(_) => {
1103 return Err(ResultDecodeError::new(
1104 ResultDecodeErrorKind::InvalidDiscriminator,
1105 "$.resultType",
1106 ));
1107 }
1108 };
1109 if era == ResultPeerEra::Legacy && exact_result_carries_final_only_metadata(&members) {
1110 return Err(ResultDecodeError::new(
1111 ResultDecodeErrorKind::InvalidKnownMember,
1112 "$._meta",
1113 ));
1114 }
1115 if era == ResultPeerEra::Modern
1116 && let Some(ExactJsonValue::Object(metadata)) = members.get("_meta")
1117 {
1118 let metadata_entries = exact_result_metadata_entries(metadata)?;
1119 validate_final_result_metadata_entries(&metadata_entries, metadata_role)?;
1120 if metadata_role == FinalResultMetadataRole::SubscriptionsListen
1121 && let Some(subscription_id) =
1122 metadata_entries.get("io.modelcontextprotocol/subscriptionId")
1123 && serde_json::from_value::<crate::jsonrpc::RequestId>(subscription_id.clone()).is_err()
1124 {
1125 return Err(ResultDecodeError::new(
1126 ResultDecodeErrorKind::InvalidKnownMember,
1127 "$._meta.io.modelcontextprotocol/subscriptionId",
1128 ));
1129 }
1130 }
1131 match policy.decide(&result_type.0) {
1132 ResultDiscriminatorDecision::Rejected => {
1133 return Err(ResultDecodeError::rejected_extension(RawResultEnvelope {
1134 discriminator: result_type.0,
1135 members,
1136 }));
1137 }
1138 ResultDiscriminatorDecision::DeferredExtension => {
1139 return Ok((
1140 DecodedResult::Deferred(RawResultEnvelope {
1141 discriminator: result_type.0,
1142 members,
1143 }),
1144 result_type.1,
1145 ));
1146 }
1147 ResultDiscriminatorDecision::Core => {}
1148 }
1149 let _ = members.take("resultType");
1150 let meta = decode_result_meta(&mut members, metadata_role)?;
1151 match result_type.0.as_str() {
1152 "complete" => Ok((
1153 DecodedResult::Complete(CompleteResult {
1154 payload: ExactJsonObject::default(),
1155 meta,
1156 extras: UnknownResultMembers {
1157 members: members.members,
1158 },
1159 }),
1160 result_type.1,
1161 )),
1162 "input_required" => {
1163 if members.get("input").is_some() {
1164 return Err(ResultDecodeError::new(
1165 ResultDecodeErrorKind::InvalidKnownMember,
1166 "$.input",
1167 ));
1168 }
1169 if members.get("request").is_some() {
1170 return Err(ResultDecodeError::new(
1171 ResultDecodeErrorKind::InvalidKnownMember,
1172 "$.request",
1173 ));
1174 }
1175 let input_requests = match members.take("inputRequests") {
1176 None => None,
1177 Some(ExactJsonValue::Object(value)) => Some(value),
1178 Some(_) => {
1179 return Err(ResultDecodeError::new(
1180 ResultDecodeErrorKind::InvalidKnownMember,
1181 "$.inputRequests",
1182 ));
1183 }
1184 };
1185 let request_state = match members.take("requestState") {
1186 None => None,
1187 Some(ExactJsonValue::String(value)) => Some(value),
1188 Some(_) => {
1189 return Err(ResultDecodeError::new(
1190 ResultDecodeErrorKind::InvalidKnownMember,
1191 "$.requestState",
1192 ));
1193 }
1194 };
1195 let input_required = InputRequiredResult::new(input_requests, request_state, meta)?;
1196 Ok((
1197 DecodedResult::InputRequired(InputRequiredResult {
1198 extras: UnknownResultMembers {
1199 members: members.members,
1200 },
1201 ..input_required
1202 }),
1203 result_type.1,
1204 ))
1205 }
1206 _ => Err(ResultDecodeError::new(
1207 ResultDecodeErrorKind::InvalidDiscriminator,
1208 "$.resultType",
1209 )),
1210 }
1211}
1212
1213pub fn decode_peer_result_for_era(
1219 input: &str,
1220 era: ProtocolEra,
1221 policy: &dyn ResultDiscriminatorPolicy,
1222) -> Result<(DecodedResult, Option<ResultPeerDiagnostic>), ResultDecodeError> {
1223 decode_peer_result(input, era.into(), policy)
1224}
1225
1226pub(crate) fn decode_peer_result_for_era_with_metadata_role(
1230 input: &str,
1231 era: ProtocolEra,
1232 policy: &dyn ResultDiscriminatorPolicy,
1233 metadata_role: FinalResultMetadataRole,
1234) -> Result<(DecodedResult, Option<ResultPeerDiagnostic>), ResultDecodeError> {
1235 decode_peer_result_with_metadata_role(input, era.into(), policy, metadata_role)
1236}
1237
1238pub fn decode_typed_complete<T: CompleteResultPayload>(
1244 input: &str,
1245 era: ResultPeerEra,
1246) -> Result<(CompleteResult<T>, Option<ResultPeerDiagnostic>), ResultDecodeError> {
1247 validate_complete_payload_names::<T>()?;
1248 let (decoded, diagnostic) = decode_peer_result(input, era, &CoreResultDiscriminatorPolicy)?;
1249 let DecodedResult::Complete(complete) = decoded else {
1250 return Err(ResultDecodeError::new(
1251 ResultDecodeErrorKind::UnexpectedResultType,
1252 "$.resultType",
1253 ));
1254 };
1255 let CompleteResult { meta, extras, .. } = complete;
1256 let mut members = ExactJsonObject {
1257 members: extras.members,
1258 };
1259 let payload = {
1260 let mut typed_members = TypedCompleteMembers {
1261 members: &mut members,
1262 declared_names: T::KNOWN_MEMBER_NAMES,
1263 };
1264 T::decode_known_members(&mut typed_members)?
1265 };
1266 for name in T::KNOWN_MEMBER_NAMES {
1267 if members.get(name).is_some() {
1268 return Err(ResultDecodeError::new(
1269 ResultDecodeErrorKind::InvalidKnownMember,
1270 format!("$.{name}"),
1271 ));
1272 }
1273 }
1274 Ok((
1275 CompleteResult {
1276 payload,
1277 meta,
1278 extras: UnknownResultMembers {
1279 members: members.members,
1280 },
1281 },
1282 diagnostic,
1283 ))
1284}
1285
1286fn validate_complete_payload_names<T: CompleteResultPayload>() -> Result<(), ResultDecodeError> {
1287 validate_known_member_names(T::KNOWN_MEMBER_NAMES)
1288}
1289
1290fn validate_known_member_names(names: &[&str]) -> Result<(), ResultDecodeError> {
1291 for (index, name) in names.iter().enumerate() {
1292 if COMMON_RESULT_MEMBER_NAMES.contains(name)
1293 || names[..index].iter().any(|previous| previous == name)
1294 {
1295 return Err(ResultDecodeError::new(
1296 ResultDecodeErrorKind::KnownMemberCollision,
1297 (*name).to_owned(),
1298 ));
1299 }
1300 }
1301 Ok(())
1302}
1303
1304fn decode_result_meta(
1305 members: &mut ExactJsonObject,
1306 metadata_role: FinalResultMetadataRole,
1307) -> Result<ResultMeta, ResultDecodeError> {
1308 let (meta, exact_meta) = match members.take("_meta") {
1309 None => (None, None),
1310 Some(ExactJsonValue::Object(value)) => {
1311 let entries = exact_result_metadata_entries(&value)?;
1312 validate_final_result_metadata_entries(&entries, metadata_role)?;
1313 let metadata = OpenMetadata::try_from_notification_entries(entries).map_err(|_| {
1314 ResultDecodeError::new(ResultDecodeErrorKind::InvalidKnownMember, "$._meta")
1315 })?;
1316 (Some(metadata), Some(value))
1317 }
1318 Some(_) => {
1319 return Err(ResultDecodeError::new(
1320 ResultDecodeErrorKind::InvalidKnownMember,
1321 "$._meta",
1322 ));
1323 }
1324 };
1325 let server_info = match members.take("serverInfo") {
1331 None => None,
1332 Some(value) => {
1333 let metadata_has_final_identity =
1334 meta.as_ref()
1335 .is_some_and(|metadata: &crate::common_types::OpenMetadata| {
1336 matches!(metadata.server_info(), Ok(Some(_)))
1337 });
1338 if metadata_has_final_identity {
1339 return Err(ResultDecodeError::new(
1340 ResultDecodeErrorKind::InvalidKnownMember,
1341 "$.serverInfo",
1342 ));
1343 }
1344 let value = exact_json_to_serde(&value)?;
1345 Some(
1346 serde_json::from_value::<Implementation>(value).map_err(|_| {
1347 ResultDecodeError::new(
1348 ResultDecodeErrorKind::InvalidKnownMember,
1349 "$.serverInfo",
1350 )
1351 })?,
1352 )
1353 }
1354 };
1355 Ok(ResultMeta {
1356 server_info,
1357 meta,
1358 exact_meta,
1359 })
1360}
1361
1362#[must_use]
1366pub fn encode_result(result: &DecodedResult) -> String {
1367 let mut members = Vec::new();
1368 match result {
1369 DecodedResult::Complete(complete) => {
1370 members.push(ExactJsonMember {
1371 name: "resultType".to_owned(),
1372 value: ExactJsonValue::String("complete".to_owned()),
1373 });
1374 append_result_meta(&mut members, &complete.meta);
1375 members.extend(complete.payload.members.clone());
1376 members.extend(complete.extras.members.clone());
1377 }
1378 DecodedResult::InputRequired(input_required) => {
1379 members.push(ExactJsonMember {
1380 name: "resultType".to_owned(),
1381 value: ExactJsonValue::String("input_required".to_owned()),
1382 });
1383 append_result_meta(&mut members, &input_required.meta);
1384 if let Some(input_requests) = &input_required.input_requests {
1385 members.push(ExactJsonMember {
1386 name: "inputRequests".to_owned(),
1387 value: ExactJsonValue::Object(input_requests.clone()),
1388 });
1389 }
1390 if let Some(request_state) = &input_required.request_state {
1391 members.push(ExactJsonMember {
1392 name: "requestState".to_owned(),
1393 value: ExactJsonValue::String(request_state.clone()),
1394 });
1395 }
1396 members.extend(input_required.extras.members.clone());
1397 }
1398 DecodedResult::Deferred(deferred) => return encode_exact_object(&deferred.members),
1399 }
1400 encode_exact_object(&ExactJsonObject { members })
1401}
1402
1403pub fn encode_complete_result(
1407 meta: &ResultMeta,
1408 known_members: Vec<ExactJsonMember>,
1409 known_names: &[&str],
1410 extras: &UnknownResultMembers,
1411) -> Result<String, ResultDecodeError> {
1412 validate_known_member_names(known_names)?;
1413 for (index, member) in known_members.iter().enumerate() {
1414 if !known_names.contains(&member.name.as_str())
1415 || known_members[..index]
1416 .iter()
1417 .any(|previous| previous.name == member.name)
1418 {
1419 return Err(ResultDecodeError::new(
1420 ResultDecodeErrorKind::KnownMemberCollision,
1421 member.name.clone(),
1422 ));
1423 }
1424 }
1425 let checked_extras = UnknownResultMembers::try_new(extras.members.clone(), known_names)?;
1426 let mut members = vec![ExactJsonMember {
1427 name: "resultType".to_owned(),
1428 value: ExactJsonValue::String("complete".to_owned()),
1429 }];
1430 members.extend(known_members);
1433 append_result_meta(&mut members, meta);
1434 members.extend(checked_extras.members);
1435 validate_local_result_members(&members)?;
1436 Ok(encode_exact_object(&ExactJsonObject { members }))
1437}
1438
1439fn append_result_meta(members: &mut Vec<ExactJsonMember>, meta: &ResultMeta) {
1440 let mut exact_meta = meta.exact_meta.clone();
1441 if let Some(server_info) = &meta.server_info {
1442 let exact_server_info = exact_json_from_serde_unchecked(
1443 &serde_json::to_value(server_info)
1444 .expect("final implementation identity always serializes"),
1445 );
1446 let object = exact_meta.get_or_insert_with(ExactJsonObject::default);
1447 if object.get("io.modelcontextprotocol/serverInfo").is_none() {
1448 object.members.push(ExactJsonMember {
1449 name: "io.modelcontextprotocol/serverInfo".to_owned(),
1450 value: exact_server_info,
1451 });
1452 }
1453 }
1454 if let Some(exact_meta) = exact_meta {
1455 members.push(ExactJsonMember {
1456 name: "_meta".to_owned(),
1457 value: ExactJsonValue::Object(exact_meta),
1458 });
1459 } else if let Some(value) = &meta.meta {
1460 let object = value.entries().clone().into_iter().collect();
1461 members.push(ExactJsonMember {
1462 name: "_meta".to_owned(),
1463 value: exact_json_from_serde_unchecked(&serde_json::Value::Object(object)),
1464 });
1465 }
1466}
1467
1468pub(crate) fn encode_exact_object(object: &ExactJsonObject) -> String {
1472 let mut output = String::from("{");
1473 for (index, member) in object.members.iter().enumerate() {
1474 if index != 0 {
1475 output.push(',');
1476 }
1477 encode_json_string(&member.name, &mut output);
1478 output.push(':');
1479 encode_exact_value(&member.value, &mut output);
1480 }
1481 output.push('}');
1482 output
1483}
1484
1485pub(crate) fn deserialize_exact_object<T>(
1492 members: Vec<ExactJsonMember>,
1493) -> Result<T, ResultDecodeError>
1494where
1495 T: serde::de::DeserializeOwned,
1496{
1497 let source = encode_exact_object(&ExactJsonObject { members });
1498 serde_json::from_str(&source).map_err(|_| ResultDecodeError::invalid_known_member("$"))
1499}
1500
1501fn encode_exact_value(value: &ExactJsonValue, output: &mut String) {
1502 match value {
1503 ExactJsonValue::Null => output.push_str("null"),
1504 ExactJsonValue::Bool(value) => output.push_str(if *value { "true" } else { "false" }),
1505 ExactJsonValue::String(value) => encode_json_string(value, output),
1506 ExactJsonValue::Number(value) => output.push_str(value),
1507 ExactJsonValue::Array(values) => {
1508 output.push('[');
1509 for (index, value) in values.iter().enumerate() {
1510 if index != 0 {
1511 output.push(',');
1512 }
1513 encode_exact_value(value, output);
1514 }
1515 output.push(']');
1516 }
1517 ExactJsonValue::Object(object) => output.push_str(&encode_exact_object(object)),
1518 }
1519}
1520
1521fn encode_json_string(value: &str, output: &mut String) {
1522 output.push('"');
1523 for character in value.chars() {
1524 match character {
1525 '"' => output.push_str("\\\""),
1526 '\\' => output.push_str("\\\\"),
1527 '\u{0008}' => output.push_str("\\b"),
1528 '\u{000c}' => output.push_str("\\f"),
1529 '\n' => output.push_str("\\n"),
1530 '\r' => output.push_str("\\r"),
1531 '\t' => output.push_str("\\t"),
1532 '\u{0000}'..='\u{001f}' => {
1533 use std::fmt::Write as _;
1534 let _ = write!(output, "\\u{:04x}", u32::from(character));
1535 }
1536 _ => output.push(character),
1537 }
1538 }
1539 output.push('"');
1540}
1541
1542struct ExactJsonParser<'a> {
1543 input: &'a str,
1544 offset: usize,
1545}
1546
1547impl<'a> ExactJsonParser<'a> {
1548 const fn new(input: &'a str) -> Self {
1549 Self { input, offset: 0 }
1550 }
1551
1552 fn skip_whitespace(&mut self) {
1553 while matches!(self.byte(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
1554 self.offset += 1;
1555 }
1556 }
1557
1558 fn byte(&self) -> Option<u8> {
1559 self.input.as_bytes().get(self.offset).copied()
1560 }
1561
1562 fn value(&mut self, depth: usize, path: &str) -> Result<ExactJsonValue, ResultDecodeError> {
1563 if depth > MAX_RESULT_DEPTH {
1564 return Err(ResultDecodeError::new(
1565 ResultDecodeErrorKind::BoundExceeded,
1566 path,
1567 ));
1568 }
1569 match self.byte() {
1570 Some(b'n') if self.consume(b"null") => Ok(ExactJsonValue::Null),
1571 Some(b't') if self.consume(b"true") => Ok(ExactJsonValue::Bool(true)),
1572 Some(b'f') if self.consume(b"false") => Ok(ExactJsonValue::Bool(false)),
1573 Some(b'"') => self.string(path).map(ExactJsonValue::String),
1574 Some(b'[') => self.array(depth, path),
1575 Some(b'{') => self.object(depth, path),
1576 Some(b'-' | b'0'..=b'9') => self.number(path).map(ExactJsonValue::Number),
1577 _ => Err(ResultDecodeError::new(
1578 ResultDecodeErrorKind::InvalidJson,
1579 path,
1580 )),
1581 }
1582 }
1583
1584 fn consume(&mut self, token: &[u8]) -> bool {
1585 if self
1586 .input
1587 .as_bytes()
1588 .get(self.offset..self.offset + token.len())
1589 == Some(token)
1590 {
1591 self.offset += token.len();
1592 true
1593 } else {
1594 false
1595 }
1596 }
1597
1598 fn string(&mut self, path: &str) -> Result<String, ResultDecodeError> {
1599 self.offset += 1;
1600 let mut value = String::new();
1601 loop {
1602 let Some(byte) = self.byte() else {
1603 return Err(ResultDecodeError::new(
1604 ResultDecodeErrorKind::InvalidJson,
1605 path,
1606 ));
1607 };
1608 match byte {
1609 b'"' => {
1610 self.offset += 1;
1611 if value.len() > MAX_RESULT_STRING_BYTES {
1612 return Err(ResultDecodeError::new(
1613 ResultDecodeErrorKind::BoundExceeded,
1614 path,
1615 ));
1616 }
1617 return Ok(value);
1618 }
1619 0x00..=0x1f => {
1620 return Err(ResultDecodeError::new(
1621 ResultDecodeErrorKind::InvalidJson,
1622 path,
1623 ));
1624 }
1625 b'\\' => {
1626 self.offset += 1;
1627 let escaped = self.byte().ok_or_else(|| {
1628 ResultDecodeError::new(ResultDecodeErrorKind::InvalidJson, path)
1629 })?;
1630 self.offset += 1;
1631 match escaped {
1632 b'"' => value.push('"'),
1633 b'\\' => value.push('\\'),
1634 b'/' => value.push('/'),
1635 b'b' => value.push('\u{0008}'),
1636 b'f' => value.push('\u{000c}'),
1637 b'n' => value.push('\n'),
1638 b'r' => value.push('\r'),
1639 b't' => value.push('\t'),
1640 b'u' => value.push(self.unicode_escape(path)?),
1641 _ => {
1642 return Err(ResultDecodeError::new(
1643 ResultDecodeErrorKind::InvalidJson,
1644 path,
1645 ));
1646 }
1647 }
1648 }
1649 _ => {
1650 let tail = &self.input[self.offset..];
1651 let character = tail.chars().next().ok_or_else(|| {
1652 ResultDecodeError::new(ResultDecodeErrorKind::InvalidJson, path)
1653 })?;
1654 value.push(character);
1655 self.offset += character.len_utf8();
1656 }
1657 }
1658 if value.len() > MAX_RESULT_STRING_BYTES {
1659 return Err(ResultDecodeError::new(
1660 ResultDecodeErrorKind::BoundExceeded,
1661 path,
1662 ));
1663 }
1664 }
1665 }
1666
1667 fn unicode_escape(&mut self, path: &str) -> Result<char, ResultDecodeError> {
1668 let unit = self.hex_unit(path)?;
1669 if !(0xd800..=0xdbff).contains(&unit) {
1670 return char::from_u32(u32::from(unit))
1671 .ok_or_else(|| ResultDecodeError::new(ResultDecodeErrorKind::InvalidJson, path));
1672 }
1673 if !self.consume(b"\\u") {
1674 return Err(ResultDecodeError::new(
1675 ResultDecodeErrorKind::InvalidJson,
1676 path,
1677 ));
1678 }
1679 let low = self.hex_unit(path)?;
1680 if !(0xdc00..=0xdfff).contains(&low) {
1681 return Err(ResultDecodeError::new(
1682 ResultDecodeErrorKind::InvalidJson,
1683 path,
1684 ));
1685 }
1686 let codepoint = 0x10000 + ((u32::from(unit) - 0xd800) << 10) + (u32::from(low) - 0xdc00);
1687 char::from_u32(codepoint)
1688 .ok_or_else(|| ResultDecodeError::new(ResultDecodeErrorKind::InvalidJson, path))
1689 }
1690
1691 fn hex_unit(&mut self, path: &str) -> Result<u16, ResultDecodeError> {
1692 let bytes = self
1693 .input
1694 .as_bytes()
1695 .get(self.offset..self.offset + 4)
1696 .ok_or_else(|| ResultDecodeError::new(ResultDecodeErrorKind::InvalidJson, path))?;
1697 self.offset += 4;
1698 bytes.iter().try_fold(0_u16, |value, byte| {
1699 let digit = match byte {
1700 b'0'..=b'9' => byte - b'0',
1701 b'a'..=b'f' => byte - b'a' + 10,
1702 b'A'..=b'F' => byte - b'A' + 10,
1703 _ => {
1704 return Err(ResultDecodeError::new(
1705 ResultDecodeErrorKind::InvalidJson,
1706 path,
1707 ));
1708 }
1709 };
1710 Ok((value << 4) | u16::from(digit))
1711 })
1712 }
1713
1714 fn number(&mut self, path: &str) -> Result<String, ResultDecodeError> {
1715 let start = self.offset;
1716 if self.byte() == Some(b'-') {
1717 self.offset += 1;
1718 }
1719 match self.byte() {
1720 Some(b'0') => self.offset += 1,
1721 Some(b'1'..=b'9') => {
1722 self.offset += 1;
1723 while matches!(self.byte(), Some(b'0'..=b'9')) {
1724 self.offset += 1;
1725 }
1726 }
1727 _ => {
1728 return Err(ResultDecodeError::new(
1729 ResultDecodeErrorKind::InvalidJson,
1730 path,
1731 ));
1732 }
1733 }
1734 if self.byte() == Some(b'.') {
1735 self.offset += 1;
1736 let fraction = self.offset;
1737 while matches!(self.byte(), Some(b'0'..=b'9')) {
1738 self.offset += 1;
1739 }
1740 if self.offset == fraction {
1741 return Err(ResultDecodeError::new(
1742 ResultDecodeErrorKind::InvalidJson,
1743 path,
1744 ));
1745 }
1746 }
1747 if matches!(self.byte(), Some(b'e' | b'E')) {
1748 self.offset += 1;
1749 if matches!(self.byte(), Some(b'+' | b'-')) {
1750 self.offset += 1;
1751 }
1752 let exponent = self.offset;
1753 while matches!(self.byte(), Some(b'0'..=b'9')) {
1754 self.offset += 1;
1755 }
1756 if self.offset == exponent {
1757 return Err(ResultDecodeError::new(
1758 ResultDecodeErrorKind::InvalidJson,
1759 path,
1760 ));
1761 }
1762 }
1763 let value = &self.input[start..self.offset];
1764 if value.len() > MAX_RESULT_NUMBER_BYTES {
1765 return Err(ResultDecodeError::new(
1766 ResultDecodeErrorKind::BoundExceeded,
1767 path,
1768 ));
1769 }
1770 Ok(value.to_owned())
1771 }
1772
1773 fn array(&mut self, depth: usize, path: &str) -> Result<ExactJsonValue, ResultDecodeError> {
1774 self.offset += 1;
1775 self.skip_whitespace();
1776 let mut values = Vec::new();
1777 if self.byte() == Some(b']') {
1778 self.offset += 1;
1779 return Ok(ExactJsonValue::Array(values));
1780 }
1781 loop {
1782 if values.len() == MAX_RESULT_CONTAINER_MEMBERS {
1783 return Err(ResultDecodeError::new(
1784 ResultDecodeErrorKind::BoundExceeded,
1785 path,
1786 ));
1787 }
1788 let item_path = format!("{path}/{}", values.len());
1789 values.push(self.value(depth + 1, &item_path)?);
1790 self.skip_whitespace();
1791 match self.byte() {
1792 Some(b',') => {
1793 self.offset += 1;
1794 self.skip_whitespace();
1795 }
1796 Some(b']') => {
1797 self.offset += 1;
1798 return Ok(ExactJsonValue::Array(values));
1799 }
1800 _ => {
1801 return Err(ResultDecodeError::new(
1802 ResultDecodeErrorKind::InvalidJson,
1803 path,
1804 ));
1805 }
1806 }
1807 }
1808 }
1809
1810 fn object(&mut self, depth: usize, path: &str) -> Result<ExactJsonValue, ResultDecodeError> {
1811 self.offset += 1;
1812 self.skip_whitespace();
1813 let mut members = Vec::new();
1814 if self.byte() == Some(b'}') {
1815 self.offset += 1;
1816 return Ok(ExactJsonValue::Object(ExactJsonObject { members }));
1817 }
1818 loop {
1819 if members.len() == MAX_RESULT_CONTAINER_MEMBERS {
1820 return Err(ResultDecodeError::new(
1821 ResultDecodeErrorKind::BoundExceeded,
1822 path,
1823 ));
1824 }
1825 if self.byte() != Some(b'"') {
1826 return Err(ResultDecodeError::new(
1827 ResultDecodeErrorKind::InvalidJson,
1828 path,
1829 ));
1830 }
1831 let name = self.string(path)?;
1832 if members
1833 .iter()
1834 .any(|member: &ExactJsonMember| member.name == name)
1835 {
1836 return Err(ResultDecodeError::new(
1837 ResultDecodeErrorKind::DuplicateMember,
1838 format!("{path}/{name}"),
1839 ));
1840 }
1841 self.skip_whitespace();
1842 if self.byte() != Some(b':') {
1843 return Err(ResultDecodeError::new(
1844 ResultDecodeErrorKind::InvalidJson,
1845 path,
1846 ));
1847 }
1848 self.offset += 1;
1849 self.skip_whitespace();
1850 let member_path = format!("{path}/{name}");
1851 let value = self.value(depth + 1, &member_path)?;
1852 members.push(ExactJsonMember { name, value });
1853 self.skip_whitespace();
1854 match self.byte() {
1855 Some(b',') => {
1856 self.offset += 1;
1857 self.skip_whitespace();
1858 }
1859 Some(b'}') => {
1860 self.offset += 1;
1861 return Ok(ExactJsonValue::Object(ExactJsonObject { members }));
1862 }
1863 _ => {
1864 return Err(ResultDecodeError::new(
1865 ResultDecodeErrorKind::InvalidJson,
1866 path,
1867 ));
1868 }
1869 }
1870 }
1871 }
1872}
1873
1874#[cfg(test)]
1875mod tests {
1876 use super::*;
1877
1878 #[test]
1879 fn cache_ttl_retains_the_unbounded_wire_value_until_runtime_conversion() {
1880 let boundary = CacheTtl::milliseconds(u64::MAX);
1881 assert_eq!(boundary.as_str(), "18446744073709551615");
1882 assert_eq!(boundary.try_as_millis(), Ok(u64::MAX));
1883
1884 let exponent: JsonInteger = serde_json::from_str("1.0e3")
1885 .expect("an exponent spelling for an integer is valid JSON");
1886 assert_eq!(
1887 CacheTtl::try_from(exponent)
1888 .expect("a nonnegative exponent integer is admitted")
1889 .try_as_millis(),
1890 Ok(1_000)
1891 );
1892
1893 let over_boundary: JsonInteger = serde_json::from_str("18446744073709551616")
1894 .expect("the one-over-u64 boundary is a JSON integer");
1895 let over_boundary = CacheTtl::try_from(over_boundary)
1896 .expect("the unbounded final wire integer is admitted");
1897 assert_eq!(over_boundary.as_str(), "18446744073709551616");
1898 assert_eq!(
1899 over_boundary.try_as_millis(),
1900 Err(CacheTtlConversionError::RuntimeOutOfRange),
1901 "only the checked runtime conversion rejects the one-over-u64 boundary"
1902 );
1903 assert_eq!(
1904 serde_json::to_string(&over_boundary).expect("unbounded TTL re-encodes"),
1905 "18446744073709551616"
1906 );
1907 assert!(
1908 serde_json::from_str::<CacheTtl>("-1").is_err(),
1909 "the final minimum rejects a negative integer TTL"
1910 );
1911 assert!(
1912 serde_json::from_str::<CacheTtl>("18446744073709551616.5").is_err(),
1913 "changing only the unbounded integer TTL to a fraction violates the final integer schema"
1914 );
1915 }
1916
1917 #[test]
1918 fn result_unit_a_positive_round_trip() {
1919 let source = r#"{"resultType":"complete","_meta":{"trace":true,"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"}},"first":{"integer":123456789012345678901234567890,"decimal":1.20e+4,"nil":null,"array":[false,"text"]},"second":{"nested":{"ok":true}}}"#;
1920 let (decoded, diagnostic) = decode_peer_result(
1921 source,
1922 ResultPeerEra::Modern,
1923 &CoreResultDiscriminatorPolicy,
1924 )
1925 .expect("complete result must round-trip through the public codec");
1926 assert_eq!(diagnostic, None);
1927 let DecodedResult::Complete(complete) = decoded else {
1928 panic!("complete result");
1929 };
1930 assert!(complete.meta.server_info.is_none());
1931 assert!(matches!(
1932 complete.meta.metadata().get("trace"),
1933 Some(ExactJsonValue::Bool(true))
1934 ));
1935 assert!(matches!(
1936 complete
1937 .meta
1938 .metadata()
1939 .get("io.modelcontextprotocol/serverInfo"),
1940 Some(ExactJsonValue::Object(server_info))
1941 if server_info.get("name") == Some(&ExactJsonValue::String("FastMCP".to_owned()))
1942 ));
1943 let extras = complete.extras.members();
1944 assert_eq!(
1945 extras
1946 .iter()
1947 .map(|member| member.name.as_str())
1948 .collect::<Vec<_>>(),
1949 ["first", "second"]
1950 );
1951 let Some(ExactJsonValue::Object(first)) = complete
1952 .extras
1953 .members()
1954 .first()
1955 .map(|member| &member.value)
1956 else {
1957 panic!("first extra");
1958 };
1959 assert_eq!(
1960 first.get("integer"),
1961 Some(&ExactJsonValue::Number(
1962 "123456789012345678901234567890".to_owned()
1963 ))
1964 );
1965 assert_eq!(
1966 first.get("decimal"),
1967 Some(&ExactJsonValue::Number("1.20e+4".to_owned()))
1968 );
1969 assert_eq!(encode_result(&DecodedResult::Complete(complete)), source);
1970 }
1971
1972 #[test]
1973 fn exact_object_encoding_preserves_admitted_member_order_and_number_lexemes() {
1974 let source = r#"{"resultType":"complete","z-last":1.20e+4,"a-first":{"second":2,"first":1},"middle":[-7.30E-12,0]}"#;
1975 let (decoded, diagnostic) = decode_peer_result(
1976 source,
1977 ResultPeerEra::Modern,
1978 &CoreResultDiscriminatorPolicy,
1979 )
1980 .expect("ordered exact result object with exponent lexemes is admitted");
1981 assert_eq!(diagnostic, None);
1982 let DecodedResult::Complete(complete) = &decoded else {
1983 panic!("fixture is a complete result");
1984 };
1985
1986 assert_eq!(
1987 complete
1988 .extras
1989 .members()
1990 .iter()
1991 .map(|member| member.name.as_str())
1992 .collect::<Vec<_>>(),
1993 ["z-last", "a-first", "middle"]
1994 );
1995 assert_eq!(encode_result(&decoded), source);
1996
1997 let planted = r#"{"resultType":"complete","z-last":1.,"a-first":{"second":2,"first":1},"middle":[-7.30E-12,0]}"#;
1998 let error = decode_peer_result(
1999 planted,
2000 ResultPeerEra::Modern,
2001 &CoreResultDiscriminatorPolicy,
2002 )
2003 .expect_err("changing only the number grammar must reject the result object");
2004 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidJson);
2005 assert_eq!(
2006 encode_result(&decoded),
2007 source,
2008 "the rejected near-identical object cannot mutate the admitted raw result"
2009 );
2010 }
2011
2012 #[test]
2013 fn generic_peer_result_missing_type_compatibility_is_era_diagnostic_only() {
2014 let accepted = r#"{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"}},"extension":true}"#;
2015 let (baseline, diagnostic) = decode_peer_result(
2016 accepted,
2017 ResultPeerEra::Modern,
2018 &CoreResultDiscriminatorPolicy,
2019 )
2020 .expect("final result with a metadata serverInfo is admitted");
2021 assert_eq!(diagnostic, None);
2022
2023 let missing = r#"{"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"}},"extension":true}"#;
2024 let (modern_compatibility, diagnostic) = decode_peer_result(
2025 missing,
2026 ResultPeerEra::Modern,
2027 &CoreResultDiscriminatorPolicy,
2028 )
2029 .expect("a modern peer omission follows the pinned complete-result compatibility rule");
2030 assert_eq!(
2031 diagnostic,
2032 Some(ResultPeerDiagnostic::ModernMissingResultType)
2033 );
2034 assert_eq!(encode_result(&modern_compatibility), accepted);
2035
2036 let wrong_type = r#"{"resultType":null,"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"}},"extension":true}"#;
2037 let error = decode_peer_result(
2038 wrong_type,
2039 ResultPeerEra::Modern,
2040 &CoreResultDiscriminatorPolicy,
2041 )
2042 .expect_err("only resultType changes from the exact wire string to null");
2043 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidDiscriminator);
2044 assert_eq!(error.path(), "$.resultType");
2045
2046 #[cfg(feature = "legacy-2024-11-05")]
2047 {
2048 let legacy_missing = r#"{"extension":true}"#;
2049 let legacy_complete = r#"{"resultType":"complete","extension":true}"#;
2050 let (legacy_compatibility, diagnostic) = decode_peer_result(
2051 legacy_missing,
2052 ResultPeerEra::Legacy,
2053 &CoreResultDiscriminatorPolicy,
2054 )
2055 .expect("generic legacy peer ingestion defaults an omitted discriminator to complete");
2056 assert_eq!(diagnostic, None);
2057 assert_eq!(encode_result(&legacy_compatibility), legacy_complete);
2058
2059 let legacy_dispatch = crate::messages::CoreRequest::decode(
2060 ProtocolEra::Legacy2024,
2061 crate::methods::TOOLS_LIST,
2062 None,
2063 )
2064 .expect("legacy tools/list request");
2065 assert!(legacy_dispatch.decode_result(r#"{"tools":[]}"#).is_ok());
2066 assert!(matches!(
2067 legacy_dispatch.decode_result(r#"{"tools":[],"resultType":"complete"}"#),
2068 Err(crate::messages::CoreDispatchError::CrossEraResultType {
2069 method: crate::methods::TOOLS_LIST
2070 })
2071 ));
2072 }
2073
2074 let top_level_server_info = r#"{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"}},"serverInfo":{"name":"legacy-location","version":"0.1"},"extension":true}"#;
2075 let error = decode_peer_result(
2076 top_level_server_info,
2077 ResultPeerEra::Modern,
2078 &CoreResultDiscriminatorPolicy,
2079 )
2080 .expect_err("final serverInfo is never admitted as a top-level result member");
2081 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2082 assert_eq!(error.path(), "$.serverInfo");
2083
2084 let (reaccepted, _) = decode_peer_result(
2085 accepted,
2086 ResultPeerEra::Modern,
2087 &CoreResultDiscriminatorPolicy,
2088 )
2089 .expect("rejections do not mutate final result admission");
2090 assert_eq!(encode_result(&baseline), accepted);
2091 assert_eq!(encode_result(&reaccepted), accepted);
2092 }
2093
2094 #[test]
2095 fn final_result_metadata_roles_preserve_open_entries_and_reject_one_field_variants() {
2096 let accepted = r#"{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"},"io.modelcontextprotocol/futureResult":{"second":1.20e+4,"first":null},"com.example/trace":"retained"},"extension":true}"#;
2097 let (baseline, diagnostic) = decode_peer_result(
2098 accepted,
2099 ResultPeerEra::Modern,
2100 &CoreResultDiscriminatorPolicy,
2101 )
2102 .expect("response-only final metadata is admitted");
2103 assert_eq!(diagnostic, None);
2104 let baseline_wire = encode_result(&baseline);
2105 assert_eq!(
2106 baseline_wire, accepted,
2107 "open metadata retains its exact source"
2108 );
2109
2110 for (member, value) in [
2111 (
2112 "io.modelcontextprotocol/protocolVersion",
2113 Value::String("2026-07-28".to_owned()),
2114 ),
2115 (
2116 "io.modelcontextprotocol/clientCapabilities",
2117 serde_json::json!({}),
2118 ),
2119 (
2120 "io.modelcontextprotocol/clientInfo",
2121 serde_json::json!({"name": "client", "version": "1"}),
2122 ),
2123 (
2124 "io.modelcontextprotocol/logLevel",
2125 Value::String("notice".to_owned()),
2126 ),
2127 (
2128 "io.modelcontextprotocol/subscriptionId",
2129 Value::String("subscription-7".to_owned()),
2130 ),
2131 ] {
2132 let mut planted: Value = serde_json::from_str(accepted).expect("baseline is JSON");
2133 planted["_meta"][member] = value;
2134 let error = decode_peer_result(
2135 &serde_json::to_string(&planted).expect("one-field variant encodes"),
2136 ResultPeerEra::Modern,
2137 &CoreResultDiscriminatorPolicy,
2138 )
2139 .expect_err("one wrong-role metadata member rejects a final response");
2140 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2141 assert_eq!(error.path(), format!("$._meta.{member}"));
2142 }
2143
2144 let mut invalid_server_info: Value =
2145 serde_json::from_str(accepted).expect("baseline is JSON");
2146 invalid_server_info["_meta"]["io.modelcontextprotocol/serverInfo"] = Value::Null;
2147 let error = decode_peer_result(
2148 &serde_json::to_string(&invalid_server_info).expect("null variant encodes"),
2149 ResultPeerEra::Modern,
2150 &CoreResultDiscriminatorPolicy,
2151 )
2152 .expect_err("only a null serverInfo type rejects the otherwise valid response");
2153 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2154 assert_eq!(error.path(), "$._meta.io.modelcontextprotocol/serverInfo");
2155
2156 let (reaccepted, diagnostic) = decode_peer_result(
2157 accepted,
2158 ResultPeerEra::Modern,
2159 &CoreResultDiscriminatorPolicy,
2160 )
2161 .expect("rejection does not mutate subsequent final result admission");
2162 assert_eq!(diagnostic, None);
2163 assert_eq!(encode_result(&reaccepted), baseline_wire);
2164 }
2165
2166 #[test]
2167 fn legacy_results_reject_each_final_only_metadata_member_without_changing_valid_round_trip() {
2168 let accepted = r#"{"resultType":"complete","_meta":{"trace":"legacy"},"extension":true}"#;
2169 let (baseline, diagnostic) = decode_peer_result(
2170 accepted,
2171 ResultPeerEra::Legacy,
2172 &CoreResultDiscriminatorPolicy,
2173 )
2174 .expect("baseline legacy result is admitted");
2175 assert_eq!(diagnostic, None);
2176 assert_eq!(encode_result(&baseline), accepted);
2177
2178 for final_metadata_member in FINAL_ONLY_METADATA_MEMBER_NAMES {
2179 let mut planted: Value =
2180 serde_json::from_str(accepted).expect("baseline result is JSON");
2181 planted["_meta"][final_metadata_member] = Value::Bool(true);
2182 let planted = serde_json::to_string(&planted).expect("planted result encodes");
2183
2184 let error = decode_peer_result(
2185 &planted,
2186 ResultPeerEra::Legacy,
2187 &CoreResultDiscriminatorPolicy,
2188 )
2189 .expect_err("adding one final-only metadata member rejects the legacy result");
2190 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2191 assert_eq!(error.path(), "$._meta");
2192 }
2193
2194 let (reaccepted, _) = decode_peer_result(
2195 accepted,
2196 ResultPeerEra::Legacy,
2197 &CoreResultDiscriminatorPolicy,
2198 )
2199 .expect("rejections leave the valid legacy result unchanged");
2200 assert_eq!(encode_result(&reaccepted), accepted);
2201 }
2202
2203 #[test]
2204 fn locally_authored_final_server_info_encodes_only_in_metadata() {
2205 let server_info =
2206 Implementation::try_new("FastMCP", "0.1").expect("valid final implementation identity");
2207 let result = DecodedResult::Complete(CompleteResult::new(
2208 ExactJsonObject::default(),
2209 ResultMeta::server_generated(server_info),
2210 ));
2211 let encoded = encode_result(&result);
2212 assert_eq!(
2213 encoded,
2214 r#"{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"FastMCP","version":"0.1"}}}"#
2215 );
2216 let wire: serde_json::Value =
2217 serde_json::from_str(&encoded).expect("final result encoding is JSON");
2218 assert!(wire.get("serverInfo").is_none());
2219 }
2220
2221 #[test]
2222 fn final_input_required_uses_retry_members_not_legacy_input() {
2223 let accepted = r#"{"resultType":"input_required","inputRequests":{"consent":{"type":"form"}},"requestState":"retry-7"}"#;
2224 let (decoded, diagnostic) = decode_peer_result(
2225 accepted,
2226 ResultPeerEra::Modern,
2227 &CoreResultDiscriminatorPolicy,
2228 )
2229 .expect("final retry result is admitted");
2230 assert_eq!(diagnostic, None);
2231 let DecodedResult::InputRequired(input_required) = &decoded else {
2232 panic!("input-required result");
2233 };
2234 assert_eq!(input_required.request_state(), Some("retry-7"));
2235 assert!(matches!(
2236 input_required
2237 .input_requests()
2238 .and_then(|requests| requests.get("consent")),
2239 Some(ExactJsonValue::Object(_))
2240 ));
2241 assert_eq!(encode_result(&decoded), accepted);
2242
2243 let missing = r#"{"resultType":"input_required"}"#;
2244 let error = decode_peer_result(
2245 missing,
2246 ResultPeerEra::Modern,
2247 &CoreResultDiscriminatorPolicy,
2248 )
2249 .expect_err("input-required needs a retry member");
2250 assert_eq!(error.kind(), ResultDecodeErrorKind::MissingInputRequest);
2251 assert_eq!(error.path(), "$");
2252
2253 let wrong_type =
2254 r#"{"resultType":"input_required","inputRequests":[],"requestState":"retry-7"}"#;
2255 let error = decode_peer_result(
2256 wrong_type,
2257 ResultPeerEra::Modern,
2258 &CoreResultDiscriminatorPolicy,
2259 )
2260 .expect_err("inputRequests must be an object");
2261 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2262 assert_eq!(error.path(), "$.inputRequests");
2263
2264 let legacy_input =
2265 r#"{"resultType":"input_required","input":{"type":"form"},"requestState":"retry-7"}"#;
2266 let error = decode_peer_result(
2267 legacy_input,
2268 ResultPeerEra::Modern,
2269 &CoreResultDiscriminatorPolicy,
2270 )
2271 .expect_err("the obsolete input field is not admitted in final input-required results");
2272 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2273 assert_eq!(error.path(), "$.input");
2274 }
2275
2276 #[test]
2277 fn result_unit_a_rejects_null_discriminator() {
2278 let accepted = r#"{"resultType":"complete","extension":{"count":1.20e+4}}"#;
2279 let (baseline, _) = decode_peer_result(
2280 accepted,
2281 ResultPeerEra::Legacy,
2282 &CoreResultDiscriminatorPolicy,
2283 )
2284 .expect("baseline");
2285 let planted = r#"{"resultType":null,"extension":{"count":1.20e+4}}"#;
2286 let error = decode_peer_result(
2287 planted,
2288 ResultPeerEra::Legacy,
2289 &CoreResultDiscriminatorPolicy,
2290 )
2291 .expect_err("only the discriminator dimension changed");
2292 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidDiscriminator);
2293 assert_eq!(error.path(), "$.resultType");
2294 let (reaccepted, _) = decode_peer_result(
2295 accepted,
2296 ResultPeerEra::Legacy,
2297 &CoreResultDiscriminatorPolicy,
2298 )
2299 .expect("pristine input is unchanged by the rejection");
2300 let (DecodedResult::Complete(before), DecodedResult::Complete(after)) =
2301 (baseline, reaccepted)
2302 else {
2303 panic!("complete baseline");
2304 };
2305 assert_eq!(before.extras, after.extras);
2306 assert_eq!(
2307 before.extras.members().first().map(|member| &member.value),
2308 Some(&ExactJsonValue::Object(ExactJsonObject {
2309 members: vec![ExactJsonMember {
2310 name: "count".to_owned(),
2311 value: ExactJsonValue::Number("1.20e+4".to_owned())
2312 }]
2313 }))
2314 );
2315 assert_eq!(encode_result(&DecodedResult::Complete(before)), accepted);
2316 }
2317
2318 #[test]
2319 fn rejected_extension_retains_its_raw_envelope() {
2320 let source = r#"{"before":true,"resultType":"example/extension","after":1.20e+4}"#;
2321 let error = decode_peer_result(
2322 source,
2323 ResultPeerEra::Modern,
2324 &CoreResultDiscriminatorPolicy,
2325 )
2326 .expect_err("the default policy must not activate an unclaimed extension");
2327 assert_eq!(error.kind(), ResultDecodeErrorKind::RejectedExtension);
2328 assert_eq!(error.path(), "$.resultType");
2329 let envelope = error
2330 .raw_envelope()
2331 .expect("rejected envelope is diagnostic data");
2332 assert_eq!(envelope.discriminator(), "example/extension");
2333 assert_eq!(
2334 envelope
2335 .members()
2336 .iter()
2337 .map(|member| member.name.as_str())
2338 .collect::<Vec<_>>(),
2339 ["before", "resultType", "after"]
2340 );
2341 assert_eq!(
2342 envelope.members()[2].value,
2343 ExactJsonValue::Number("1.20e+4".to_owned())
2344 );
2345 }
2346
2347 #[test]
2348 fn locally_authored_extras_use_the_bounded_exact_value_boundary() {
2349 let valid = UnknownResultMembers::try_new(
2350 vec![ExactJsonMember {
2351 name: "extension".to_owned(),
2352 value: ExactJsonValue::Number("123456789012345678901234567890".to_owned()),
2353 }],
2354 &["resultType", "_meta", "serverInfo"],
2355 )
2356 .expect("a bounded exact numeric lexeme is retained");
2357 assert_eq!(
2358 valid.members()[0].value,
2359 ExactJsonValue::Number("123456789012345678901234567890".to_owned())
2360 );
2361
2362 let invalid = UnknownResultMembers::try_new(
2363 vec![ExactJsonMember {
2364 name: "extension".to_owned(),
2365 value: ExactJsonValue::Number("1.".to_owned()),
2366 }],
2367 &["resultType", "_meta", "serverInfo"],
2368 )
2369 .expect_err("an invalid numeric lexeme never enters an open-member result");
2370 assert_eq!(invalid.kind(), ResultDecodeErrorKind::InvalidJson);
2371
2372 let collision = UnknownResultMembers::try_new(
2373 vec![ExactJsonMember {
2374 name: "resultType".to_owned(),
2375 value: ExactJsonValue::String("complete".to_owned()),
2376 }],
2377 &[],
2378 )
2379 .expect_err("a common discriminator can never be locally authored as an extra");
2380 assert_eq!(
2381 collision.kind(),
2382 ResultDecodeErrorKind::KnownMemberCollision
2383 );
2384 }
2385
2386 #[derive(Debug, PartialEq, Eq)]
2387 struct LookupResult {
2388 status: String,
2389 record: ExactJsonObject,
2390 }
2391
2392 impl CompleteResultPayload for LookupResult {
2393 const KNOWN_MEMBER_NAMES: &'static [&'static str] = &["status", "record"];
2394
2395 fn decode_known_members(
2396 members: &mut TypedCompleteMembers<'_>,
2397 ) -> Result<Self, ResultDecodeError> {
2398 let Some(ExactJsonValue::String(status)) = members.take("status")? else {
2399 return Err(ResultDecodeError::invalid_known_member("$.status"));
2400 };
2401 let Some(ExactJsonValue::Object(record)) = members.take("record")? else {
2402 return Err(ResultDecodeError::invalid_known_member("$.record"));
2403 };
2404 Ok(Self { status, record })
2405 }
2406 }
2407
2408 #[test]
2409 fn result_unit_b_typed_decode_preserves_open_members() {
2410 let source = r#"{"resultType":"complete","status":"ready","record":{"id":123456789012345678901234567890},"opaque":{"null":null,"bool":true,"decimal":1.20e+4,"array":["kept"]}}"#;
2411 let (decoded, diagnostic) =
2412 decode_typed_complete::<LookupResult>(source, ResultPeerEra::Modern)
2413 .expect("selected complete members decode through the public result codec");
2414 assert_eq!(diagnostic, None);
2415 assert_eq!(decoded.payload.status, "ready");
2416 assert_eq!(
2417 decoded.payload.record.get("id"),
2418 Some(&ExactJsonValue::Number(
2419 "123456789012345678901234567890".to_owned()
2420 ))
2421 );
2422 assert_eq!(
2423 decoded
2424 .extras
2425 .members()
2426 .iter()
2427 .map(|member| member.name.as_str())
2428 .collect::<Vec<_>>(),
2429 ["opaque"]
2430 );
2431 assert_eq!(
2432 decoded.extras.members().first().map(|member| &member.value),
2433 Some(&ExactJsonValue::Object(ExactJsonObject {
2434 members: vec![
2435 ExactJsonMember {
2436 name: "null".to_owned(),
2437 value: ExactJsonValue::Null
2438 },
2439 ExactJsonMember {
2440 name: "bool".to_owned(),
2441 value: ExactJsonValue::Bool(true)
2442 },
2443 ExactJsonMember {
2444 name: "decimal".to_owned(),
2445 value: ExactJsonValue::Number("1.20e+4".to_owned())
2446 },
2447 ExactJsonMember {
2448 name: "array".to_owned(),
2449 value: ExactJsonValue::Array(vec![ExactJsonValue::String(
2450 "kept".to_owned()
2451 )])
2452 }
2453 ]
2454 }))
2455 );
2456 }
2457
2458 #[test]
2459 fn result_unit_b_typed_decode_rejects_wrong_known_kind() {
2460 let accepted = r#"{"resultType":"complete","status":"ready","record":{"id":123456789012345678901234567890},"opaque":{"decimal":1.20e+4}}"#;
2461 let (baseline, _) = decode_typed_complete::<LookupResult>(accepted, ResultPeerEra::Modern)
2462 .expect("baseline selected complete result");
2463 let planted = r#"{"resultType":"complete","status":false,"record":{"id":123456789012345678901234567890},"opaque":{"decimal":1.20e+4}}"#;
2464 let error = decode_typed_complete::<LookupResult>(planted, ResultPeerEra::Modern)
2465 .expect_err("only the selected status kind changed");
2466 assert_eq!(error.kind(), ResultDecodeErrorKind::InvalidKnownMember);
2467 assert_eq!(error.path(), "$.status");
2468 let (reaccepted, _) =
2469 decode_typed_complete::<LookupResult>(accepted, ResultPeerEra::Modern)
2470 .expect("the rejected peer document cannot mutate future typed decodes");
2471 assert_eq!(reaccepted.payload, baseline.payload);
2472 assert_eq!(reaccepted.extras, baseline.extras);
2473 assert_eq!(
2474 reaccepted
2475 .extras
2476 .members()
2477 .first()
2478 .map(|member| &member.value),
2479 Some(&ExactJsonValue::Object(ExactJsonObject {
2480 members: vec![ExactJsonMember {
2481 name: "decimal".to_owned(),
2482 value: ExactJsonValue::Number("1.20e+4".to_owned())
2483 }]
2484 }))
2485 );
2486 }
2487}