Skip to main content

fastmcp_protocol/
result.rs

1//! Bounded, lossless MCP result envelopes.
2//!
3//! This module deliberately keeps result decoding separate from the legacy MCP
4//! message structs.  It admits one JSON object, preserves open members in their
5//! received order, and does not activate an unrecognised discriminator.
6
7use 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
17/// Maximum encoded bytes accepted by the result codec.
18pub const MAX_RESULT_ENCODED_BYTES: usize = 1_048_576;
19/// Maximum nesting depth accepted by the result codec.
20pub const MAX_RESULT_DEPTH: usize = 64;
21/// Maximum members/elements accepted by one JSON object or array.
22pub const MAX_RESULT_CONTAINER_MEMBERS: usize = 1_024;
23/// Maximum decoded bytes in one JSON string or object key.
24pub const MAX_RESULT_STRING_BYTES: usize = 65_536;
25/// Maximum source bytes in one JSON number lexeme.
26pub const MAX_RESULT_NUMBER_BYTES: usize = 1_024;
27
28/// A recursively bounded JSON value which preserves each number's source
29/// lexeme. This is the result codec's exact-value boundary; it never uses an
30/// `f64` or a fixed-width integer for an admitted JSON number.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum ExactJsonValue {
33    /// JSON `null`.
34    Null,
35    /// A JSON boolean.
36    Bool(bool),
37    /// A decoded JSON string.
38    String(String),
39    /// The exact source lexeme of a syntactically valid JSON number.
40    Number(String),
41    /// An ordered JSON array.
42    Array(Vec<ExactJsonValue>),
43    /// An ordered JSON object with duplicate names rejected at admission.
44    Object(ExactJsonObject),
45}
46
47/// One JSON object member.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ExactJsonMember {
50    /// Member name as decoded from JSON.
51    pub name: String,
52    /// Member value.
53    pub value: ExactJsonValue,
54}
55
56/// An ordered JSON object.
57#[derive(Debug, Clone, Default, PartialEq, Eq)]
58pub struct ExactJsonObject {
59    members: Vec<ExactJsonMember>,
60}
61
62impl ExactJsonObject {
63    /// Returns the object's members in their admitted order.
64    #[must_use]
65    pub fn members(&self) -> &[ExactJsonMember] {
66        &self.members
67    }
68
69    /// Finds one member by its exact decoded name.
70    #[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/// An error raised while admitting or decoding a result envelope.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ResultDecodeError {
89    kind: ResultDecodeErrorKind,
90    path: String,
91    raw_envelope: Option<RawResultEnvelope>,
92}
93
94/// Stable result-decoding failure categories.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum ResultDecodeErrorKind {
97    /// The input did not contain exactly one complete JSON value.
98    InvalidJson,
99    /// A configured result bound was exceeded.
100    BoundExceeded,
101    /// An object contained the same member name more than once.
102    DuplicateMember,
103    /// The result envelope was not a JSON object.
104    ExpectedObject,
105    /// A selected, known field has the wrong JSON kind.
106    InvalidKnownMember,
107    /// `resultType` was explicit `null` or another non-string value.
108    InvalidDiscriminator,
109    /// A modern result omitted its required `resultType` discriminator.
110    MissingDiscriminator,
111    /// `input_required` had neither `inputRequests` nor `requestState`.
112    MissingInputRequest,
113    /// A deferred extension was rejected by the supplied discriminator policy.
114    RejectedExtension,
115    /// A local custom-extra request collided with a known member name.
116    KnownMemberCollision,
117    /// A typed complete decoder was given a non-complete core result.
118    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    /// Returns the stable category of this error.
139    #[must_use]
140    pub const fn kind(&self) -> ResultDecodeErrorKind {
141        self.kind
142    }
143
144    /// Returns the bounded logical path at which admission failed.
145    #[must_use]
146    pub fn path(&self) -> &str {
147        &self.path
148    }
149
150    /// Constructs a precise selected-known-member failure for a typed decoder.
151    #[must_use]
152    pub fn invalid_known_member(path: impl Into<String>) -> Self {
153        Self::new(ResultDecodeErrorKind::InvalidKnownMember, path)
154    }
155
156    /// Returns the bounded raw envelope retained when a discriminator policy
157    /// rejected an otherwise structurally admitted extension result.
158    #[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
176/// Parses one bounded exact JSON value.
177pub 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
197/// Converts an ordinary serde value into the result algebra's bounded exact
198/// representation before it is attached to a locally authored result.
199pub 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
228/// Converts one exact result value into a serde value for a selected typed
229/// method payload. Open result siblings remain exact and are never converted
230/// unless that payload explicitly owns their member name.
231pub 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
252/// Parses one bounded, duplicate-free exact final-result object.
253///
254/// Method-specific direct decoders use this same admission boundary before
255/// validating their typed fields, so their public replay paths retain the
256/// source object rather than reserializing it through `serde_json::Value`.
257pub(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/// Result metadata common to complete and input-required results.
289#[derive(Debug, Clone)]
290pub struct ResultMeta {
291    /// Compatibility view of a locally authored server identity.
292    ///
293    /// Final encoding moves this identity into
294    /// `_meta.io.modelcontextprotocol/serverInfo`; it is never emitted as a
295    /// top-level result member.
296    pub server_info: Option<Implementation>,
297    meta: Option<OpenMetadata>,
298    exact_meta: Option<ExactJsonObject>,
299}
300
301impl ResultMeta {
302    /// Creates empty metadata for a locally authored result.
303    ///
304    /// This preserves the absence of the optional `_meta` member on the wire.
305    #[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    /// Creates metadata for a locally constructed successful result.
315    #[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    /// Attaches final common metadata without synthesizing it when absent.
333    #[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    /// Returns a view that behaves as an empty metadata object when `_meta` was
346    /// absent, without causing serialization to synthesize `_meta`.
347    #[must_use]
348    pub fn metadata(&self) -> MetadataView<'_> {
349        MetadataView {
350            object: self.exact_meta.as_ref(),
351        }
352    }
353
354    /// Decodes the final server identity from its reserved metadata location.
355    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/// Read-only view of optional result metadata.
365#[derive(Debug, Clone, Copy)]
366pub struct MetadataView<'a> {
367    object: Option<&'a ExactJsonObject>,
368}
369
370impl MetadataView<'_> {
371    /// Looks up one metadata member.
372    #[must_use]
373    pub fn get(&self, name: &str) -> Option<&ExactJsonValue> {
374        self.object.and_then(|object| object.get(name))
375    }
376
377    /// Returns whether `_meta` was absent or had no members.
378    #[must_use]
379    pub fn is_empty(&self) -> bool {
380        self.object.is_none_or(|object| object.members().is_empty())
381    }
382}
383
384/// Bounded, inert open members retained after known-field consumption.
385#[derive(Debug, Clone, Default, PartialEq, Eq)]
386pub struct UnknownResultMembers {
387    members: Vec<ExactJsonMember>,
388}
389
390impl UnknownResultMembers {
391    /// Constructs locally authored extras while refusing collisions with the
392    /// selected composition's common and method-specific member names.
393    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    /// Returns the retained members in their admitted order.
427    #[must_use]
428    pub fn members(&self) -> &[ExactJsonMember] {
429        &self.members
430    }
431
432    /// Returns the retained members for a selected typed composition.
433    #[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
441/// Reserved `_meta` members introduced by the final protocol era.
442///
443/// Legacy envelopes may retain application-defined metadata, but cannot carry
444/// any of these protocol-defined members without crossing protocol eras.
445const 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
454/// Reserved final metadata members that do not belong on an ordinary result.
455///
456/// The protocol version and client facts select and constrain a request; a
457/// peer response must not be allowed to echo or redefine them as open result
458/// metadata. `logLevel` is likewise request-only, while `subscriptionId`
459/// belongs to notification metadata except for the dedicated
460/// `subscriptions/listen` terminal result.
461const 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/// The method-owned metadata shape selected for a final result.
470///
471/// Ordinary final results reject request and notification metadata. Only the
472/// terminal `subscriptions/listen` result has the schema-defined
473/// `subscriptionId` exception.
474#[derive(Clone, Copy, Debug, Eq, PartialEq)]
475pub(crate) enum FinalResultMetadataRole {
476    /// Every ordinary final result, including `server/discover`.
477    Ordinary,
478    /// The terminal `subscriptions/listen` result.
479    SubscriptionsListen,
480}
481
482/// Returns whether an otherwise legacy result envelope carries a reserved
483/// final-era metadata member.
484///
485/// A legacy decoder must reject these rather than silently dropping them
486/// through an open legacy result payload.
487pub(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
519/// Validates the reserved members of final result metadata without assigning
520/// behavior to schema-open names. Exact callers retain the original metadata
521/// object separately, so this typed check never rewrites member order or
522/// number lexemes.
523pub(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/// A typed core `complete` result with inert open siblings.
690#[derive(Debug, Clone)]
691pub struct CompleteResult<T> {
692    /// Method-specific complete payload.
693    pub payload: T,
694    /// Common result metadata.
695    pub meta: ResultMeta,
696    /// Open siblings that did not belong to the selected complete composition.
697    pub extras: UnknownResultMembers,
698}
699
700impl<T> CompleteResult<T> {
701    /// Creates a strict complete result. Serialization always emits
702    /// `resultType: "complete"`.
703    #[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
713/// Guarded access to one selected complete composition's declared members.
714pub struct TypedCompleteMembers<'a> {
715    members: &'a mut ExactJsonObject,
716    declared_names: &'static [&'static str],
717}
718
719impl TypedCompleteMembers<'_> {
720    /// Removes one declared method-specific member.
721    ///
722    /// An implementation cannot consume an undeclared open member; it remains
723    /// inert and is preserved in `UnknownResultMembers` instead.
724    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
735/// Method-specific decoder for a selected core `complete` result composition.
736///
737/// Implementations declare every method-specific name they own, consume those
738/// names from the raw object, and reject an invalid value at that exact member.
739/// The result codec verifies that no declared name remains before it retains
740/// all other members as inert `UnknownResultMembers`.
741pub trait CompleteResultPayload: Sized {
742    /// All method-specific names owned by this selected complete composition.
743    const KNOWN_MEMBER_NAMES: &'static [&'static str];
744
745    /// Consumes and validates this composition's method-specific members.
746    fn decode_known_members(
747        members: &mut TypedCompleteMembers<'_>,
748    ) -> Result<Self, ResultDecodeError>;
749}
750
751/// A typed core `input_required` result with inert open siblings.
752#[derive(Debug, Clone)]
753pub struct InputRequiredResult {
754    /// Final input requests supplied by the server, when present.
755    input_requests: Option<ExactJsonObject>,
756    /// Opaque state supplied for the final retry, when present.
757    request_state: Option<String>,
758    /// Common result metadata.
759    pub meta: ResultMeta,
760    /// Open siblings, including standard names from another composition.
761    pub extras: UnknownResultMembers,
762}
763
764impl InputRequiredResult {
765    /// Creates an input-required result using final retry members only.
766    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    /// Returns final input requests without converting their exact members.
786    #[must_use]
787    pub fn input_requests(&self) -> Option<&ExactJsonObject> {
788        self.input_requests.as_ref()
789    }
790
791    /// Returns the opaque final retry state, if supplied.
792    #[must_use]
793    pub fn request_state(&self) -> Option<&str> {
794        self.request_state.as_deref()
795    }
796}
797
798/// A nonnegative final cache TTL that retains its exact JSON-integer spelling.
799#[derive(Debug, Clone, PartialEq, Eq)]
800pub struct CacheTtl(JsonInteger);
801
802/// Failure while validating or converting a final cache TTL.
803#[derive(Debug, Clone, Copy, PartialEq, Eq)]
804pub enum CacheTtlConversionError {
805    /// The wire integer is mathematically negative.
806    Negative,
807    /// The wire integer is valid but exceeds the local `u64` runtime domain.
808    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    /// Creates a nonnegative TTL in milliseconds.
824    #[must_use]
825    pub fn milliseconds(value: u64) -> Self {
826        Self(JsonInteger::from(value))
827    }
828
829    /// Returns the exact nonnegative JSON-integer wire spelling.
830    #[must_use]
831    pub fn as_str(&self) -> &str {
832        self.0.as_str()
833    }
834
835    /// Converts this wire value into the bounded local millisecond domain.
836    ///
837    /// This is intentionally checked: the final schema has no maximum, while
838    /// local duration APIs accept at most `u64` milliseconds.
839    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/// Peer cache scope. `Public` is a wire value, not an authority grant.
939#[derive(Debug, Clone, Copy, PartialEq, Eq)]
940pub enum CacheScope {
941    /// Shareable only when a separately sealed cache registration permits it.
942    Public,
943    /// The safe default for locally generated cache hints.
944    Private,
945}
946
947/// A complete-result composition with strict cache hints.
948#[derive(Debug, Clone)]
949pub struct CacheableResult<T> {
950    /// The wrapped complete result.
951    pub result: CompleteResult<T>,
952    /// Server-generated nonnegative cache TTL.
953    pub ttl: CacheTtl,
954    /// Server-generated cache scope, defaulting to private.
955    pub scope: CacheScope,
956}
957
958/// A complete-result composition with a pagination cursor.
959#[derive(Debug, Clone)]
960pub struct PaginatedResult<T> {
961    /// The wrapped complete result.
962    pub result: CompleteResult<T>,
963    /// Cursor for the following page.
964    pub next_cursor: String,
965}
966
967/// A raw result envelope for a non-core discriminator. It is diagnostic data,
968/// not an activated extension result.
969#[derive(Debug, Clone, PartialEq, Eq)]
970pub struct RawResultEnvelope {
971    discriminator: String,
972    members: ExactJsonObject,
973}
974
975impl RawResultEnvelope {
976    /// Returns the losslessly retained non-core discriminator.
977    #[must_use]
978    pub fn discriminator(&self) -> &str {
979        &self.discriminator
980    }
981
982    /// Returns every admitted member of the raw envelope.
983    #[must_use]
984    pub fn members(&self) -> &[ExactJsonMember] {
985        self.members.members()
986    }
987}
988
989/// Result-discriminator decision made after raw structural admission.
990#[derive(Debug, Clone, Copy, PartialEq, Eq)]
991pub enum ResultDiscriminatorDecision {
992    /// Decode one of the core result discriminators.
993    Core,
994    /// Retain the raw envelope for a later negotiated extension decoder.
995    DeferredExtension,
996    /// Reject the raw envelope without activating it.
997    Rejected,
998}
999
1000mod sealed {
1001    pub trait Sealed {}
1002}
1003
1004/// Registry-agnostic policy seam for already-admitted non-core result types.
1005/// No policy in this module owns a descriptor registry or activates extensions.
1006pub trait ResultDiscriminatorPolicy: sealed::Sealed {
1007    /// Decides whether an admitted discriminator is core, deferred, or rejected.
1008    fn decide(&self, discriminator: &str) -> ResultDiscriminatorDecision;
1009}
1010
1011/// The default policy: only core discriminators are accepted locally.
1012#[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/// Protocol era used only for peer-ingress compatibility diagnostics.
1027#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1028pub enum ResultPeerEra {
1029    /// A peer negotiated an earlier initialize-handshake era.
1030    Legacy,
1031    /// A peer negotiated the modern per-request-metadata era.
1032    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/// Bounded diagnostics reserved for peer-result compatibility handling.
1045#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1046pub enum ResultPeerDiagnostic {
1047    /// A legacy peer used a compatibility result shape.
1048    LegacyCompatibilityShape,
1049    /// A modern peer omitted the required final `resultType` discriminator.
1050    ///
1051    /// The peer result still decodes as a complete result for the pinned
1052    /// compatibility rule, but locally authored modern results always emit the
1053    /// discriminator explicitly.
1054    ModernMissingResultType,
1055}
1056
1057/// Core or deferred result decoded from a peer envelope.
1058#[derive(Debug, Clone)]
1059pub enum DecodedResult {
1060    /// A complete result with all non-common members retained as inert extras.
1061    Complete(CompleteResult<ExactJsonObject>),
1062    /// An input-required result.
1063    InputRequired(InputRequiredResult),
1064    /// A policy-deferred, non-activated raw extension envelope.
1065    Deferred(RawResultEnvelope),
1066}
1067
1068/// Decodes one peer result through the public result codec.
1069///
1070/// A final peer must provide `resultType`, but peer ingestion preserves the
1071/// pinned compatibility rule by decoding an absent discriminator as
1072/// `complete` in either era. A modern omission receives a bounded diagnostic;
1073/// locally authored modern results still emit `resultType: "complete"`.
1074/// Explicit null and non-string discriminators are rejected instead of
1075/// conflating them with absence.
1076pub 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
1084/// Decodes one peer result with the selected method-owned metadata role.
1085///
1086/// This remains crate-visible because core method decoding is the sole caller
1087/// permitted to select the `subscriptions/listen` terminal-result exception.
1088pub(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
1213/// Decodes one peer result using the protocol era selected for its request.
1214///
1215/// This is the dispatch-facing entry point. It keeps the older
1216/// [`ResultPeerEra`] spelling available for existing callers while ensuring
1217/// that request and result selection share one exact era source of truth.
1218pub 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
1226/// Decodes a peer result through the selected era and method-owned metadata
1227/// role. The special role is crate-private to prevent unrelated public result
1228/// consumers from accepting notification-only subscription metadata.
1229pub(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
1238/// Decodes a selected method-specific `complete` composition.
1239///
1240/// The core result envelope is admitted first. The selected payload then
1241/// consumes precisely its declared members; any declared member that remains
1242/// is rejected as a known-field failure, never demoted to an inert extra.
1243pub 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    // A top-level serverInfo is admitted as the compatibility view of the
1326    // peer identity; re-encoding normalizes it into result metadata. It is
1327    // rejected when metadata already carries the final identity — two
1328    // divergent identities in one result are ambiguous — and typed method
1329    // dispatch layers keep their own stricter rejection.
1330    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/// Re-emits a result without discarding or semantically rewriting any retained
1363/// open member. Safe complete and input-required variants always emit their
1364/// explicit core discriminator.
1365#[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
1403/// Encodes one selected typed `complete` result through the final result
1404/// algebra. Method dispatch supplies exactly the members it owns; every other
1405/// admitted member remains an inert [`UnknownResultMembers`] sibling.
1406pub 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    // Canonical local layout: method-owned members first, then `_meta`, then
1431    // inert open siblings — matching the frozen final wires end to end.
1432    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
1468/// Encodes one admitted exact object without normalizing member order or JSON
1469/// number lexemes. Protocol extension envelopes use this internally when a
1470/// nested result must survive a typed decode/re-encode boundary unchanged.
1471pub(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
1485/// Deserializes one already-admitted set of exact object members without
1486/// routing number tokens through `serde_json::Value` first.
1487///
1488/// Selected typed result members use this boundary so a declared numeric
1489/// field retains the peer's exact spelling (for example `7.3e1`) while the
1490/// ordinary typed deserializer still enforces its schema.
1491pub(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}