1use crate::mcp::era::{
2 classify_message, correlation_id, fold_envelope, id_is_acceptable, observe_client_capabilities,
3 observe_header, observe_request_metadata, observe_result, resolve_era, CapabilityObservation,
4 EnvelopeObservation, McpEraContext, MessageKind, ParsedMcpEvent, RequestMetadata,
5};
6use crate::mcp::era::{CorrelationId, DuplicateAwareSink, EraResolution, SeenMembers, UniqueValue};
7use crate::mcp::json_depth;
8use crate::mcp::types::*;
9use anyhow::{bail, Context, Result};
10use serde::Deserialize;
11
12pub fn parse_mcp_transcript(text: &str, format: McpInputFormat) -> Result<Vec<McpEvent>> {
16 Ok(parse_mcp_transcript_detailed(text, format)?
17 .into_iter()
18 .map(|parsed| parsed.event)
19 .collect())
20}
21
22pub(crate) fn parse_mcp_transcript_detailed(
29 text: &str,
30 format: McpInputFormat,
31) -> Result<Vec<ParsedMcpEvent>> {
32 parse_mcp_transcript_detailed_internal(text, format, None)
33}
34
35pub(crate) fn parse_mcp_transcript_detailed_with_depth(
39 text: &str,
40 format: McpInputFormat,
41 max_json_depth: usize,
42) -> Result<Vec<ParsedMcpEvent>> {
43 parse_mcp_transcript_detailed_internal(text, format, Some(max_json_depth))
44}
45
46fn parse_mcp_transcript_detailed_internal(
47 text: &str,
48 format: McpInputFormat,
49 max_embedded_json_depth: Option<usize>,
50) -> Result<Vec<ParsedMcpEvent>> {
51 let (events, envelopes) = parse_events_with_envelopes(text, format, max_embedded_json_depth)?;
52 let framed = is_framed(format);
53 let parsed: Vec<ParsedMcpEvent> = events
54 .into_iter()
55 .map(|event| {
56 let (request_metadata, result_observation, capability_observation, is_error_response) =
69 match payload_raw(&event.payload) {
70 Some(raw) => match classify_message(raw) {
74 Ok(MessageKind::Request { .. }) => (
75 Some(observe_request_metadata(raw)),
76 None,
77 observe_client_capabilities(raw),
78 false,
79 ),
80 Ok(MessageKind::Notification { .. }) => (None, None, None, false),
81 Ok(MessageKind::Response) => {
82 (None, observe_result(raw), None, raw.get("error").is_some())
83 }
84 Err(_) => (None, None, None, false),
85 },
86 None => (None, None, None, false),
87 };
88 let envelope = if framed {
92 envelopes
93 .get(event.source_line.saturating_sub(1) as usize)
94 .cloned()
95 .unwrap_or(EnvelopeObservation::Malformed)
96 } else {
97 EnvelopeObservation::NotApplicable
98 };
99 let era = resolve_era(
100 &envelope,
101 request_metadata
102 .as_ref()
103 .unwrap_or(&RequestMetadata::Absent),
104 );
105 let correlation = payload_raw(&event.payload).and_then(correlation_id);
108 ParsedMcpEvent {
109 event,
110 context: McpEraContext {
111 envelope,
112 era,
113 correlation,
114 request_metadata,
115 result_observation,
116 capability_observation,
117 },
118 is_error_response,
119 }
120 })
121 .collect();
122 correlate_calls(parsed)
123}
124
125struct CallSignals {
127 era: EraResolution,
128 capability: Option<CapabilityObservation>,
129}
130
131fn correlate_calls(mut parsed: Vec<ParsedMcpEvent>) -> Result<Vec<ParsedMcpEvent>> {
148 let mut outstanding: std::collections::HashMap<CorrelationId, CallSignals> =
157 std::collections::HashMap::new();
158 for p in &mut parsed {
159 let Some(raw) = payload_raw(&p.event.payload) else {
162 continue;
163 };
164 let Some(id) = correlation_id(raw) else {
165 continue;
166 };
167 let kind = classify_message(raw).ok();
173 match kind {
174 Some(MessageKind::Request { .. }) => {
175 let signals = CallSignals {
179 era: p.context.era.clone(),
180 capability: p.context.capability_observation.clone(),
181 };
182 if outstanding.insert(id.clone(), signals).is_some() {
183 bail!(
184 "two outstanding JSON-RPC requests share an id at source line {}",
185 p.event.source_line
186 );
187 }
188 }
189 Some(MessageKind::Response) => {
190 if let Some(signals) = outstanding.remove(&id) {
195 p.context.era = signals.era;
196 p.context.capability_observation = signals.capability;
197 }
198 }
199 Some(MessageKind::Notification { .. }) | None => {}
200 }
201 }
202 Ok(parsed)
203}
204
205fn observe_transport_context(ctx: Option<&serde_json::Value>) -> Option<EnvelopeObservation> {
213 let ctx = ctx?;
214 let Some(map) = ctx.as_object() else {
215 return Some(EnvelopeObservation::Malformed);
216 };
217 observe_header(map.get("headers"))
220}
221
222fn is_framed(format: McpInputFormat) -> bool {
225 matches!(
226 format,
227 McpInputFormat::StreamableHttp | McpInputFormat::HttpSse
228 )
229}
230
231fn payload_raw(payload: &McpPayload) -> Option<&serde_json::Value> {
232 match payload {
233 McpPayload::SessionStart { raw }
234 | McpPayload::ToolsListRequest { raw }
235 | McpPayload::ToolsListResponse { raw, .. }
236 | McpPayload::ToolCallRequest { raw, .. }
237 | McpPayload::ToolCallResponse { raw, .. }
238 | McpPayload::SessionEnd { raw, .. }
239 | McpPayload::Other { raw, .. } => Some(raw),
240 }
241}
242
243fn parse_events_with_envelopes(
244 text: &str,
245 format: McpInputFormat,
246 max_embedded_json_depth: Option<usize>,
247) -> Result<(Vec<McpEvent>, Vec<EnvelopeObservation>)> {
248 let (events, envelopes) = match format {
249 McpInputFormat::JsonRpc => (parse_jsonrpc_jsonl(text)?, Vec::new()),
250 McpInputFormat::Inspector => (parse_inspector_best_effort(text)?, Vec::new()),
251 McpInputFormat::StreamableHttp => parse_transport_transcript_detailed(
252 text,
253 "streamable-http",
254 "streamable-http transcript",
255 false,
256 max_embedded_json_depth,
257 )?,
258 McpInputFormat::HttpSse => parse_transport_transcript_detailed(
259 text,
260 "http-sse",
261 "http-sse transcript",
262 true,
263 max_embedded_json_depth,
264 )?,
265 };
266 Ok((events, envelopes))
272}
273
274fn parse_jsonrpc_jsonl(text: &str) -> Result<Vec<McpEvent>> {
275 let mut out = Vec::new();
276
277 for (lineno, line) in text.lines().enumerate() {
278 let line = line.trim();
279 if line.is_empty() {
280 continue;
281 }
282
283 let UniqueValue(v) = serde_json::from_str::<UniqueValue>(line)
284 .with_context(|| format!("invalid JSON on line {}", lineno + 1))?;
285
286 let event = parse_jsonrpc_message(
287 v,
288 (lineno + 1) as u64,
289 None,
290 McpAuthorizationDiscovery::default(),
291 )?;
292 out.push(event);
293 }
294
295 Ok(out)
296}
297
298fn parse_inspector_best_effort(text: &str) -> Result<Vec<McpEvent>> {
299 let UniqueValue(v) =
300 serde_json::from_str::<UniqueValue>(text).context("invalid inspector JSON")?;
301
302 let arr = v
306 .get("events")
307 .cloned()
308 .or_else(|| v.as_array().cloned().map(serde_json::Value::Array))
309 .and_then(|x| x.as_array().cloned())
310 .unwrap_or_default();
311
312 let mut out = Vec::new();
313 for (idx, item) in arr.into_iter().enumerate() {
314 let event = parse_jsonrpc_message(
316 item,
317 (idx + 1) as u64,
318 None,
319 McpAuthorizationDiscovery::default(),
320 )?;
321 out.push(event);
322 }
323
324 Ok(out)
325}
326
327fn parse_transport_transcript_detailed(
333 text: &str,
334 expected_transport: &str,
335 source_label: &str,
336 allow_endpoint_event: bool,
337 max_embedded_json_depth: Option<usize>,
338) -> Result<(Vec<McpEvent>, Vec<EnvelopeObservation>)> {
339 let transcript: TransportTranscript =
340 serde_json::from_str(text).with_context(|| format!("invalid {}", source_label))?;
341
342 let actual_transport = transcript.transport.as_deref().unwrap_or("missing");
343 if actual_transport != expected_transport {
344 bail!(
345 "{} transport must be {:?}, found {:?}",
346 source_label,
347 expected_transport,
348 actual_transport
349 );
350 }
351
352 let mut transcript_slots = Vec::new();
353 if let Some(o) = observe_transport_context(transcript.transport_context.as_ref().map(|u| &u.0))
354 {
355 transcript_slots.push(o);
356 }
357 if let Some(o) = observe_header(transcript.headers.as_ref().map(|u| &u.0)) {
358 transcript_slots.push(o);
359 }
360
361 let mut envelopes = Vec::new();
362 let mut out = Vec::new();
363 for (idx, entry) in transcript.entries.into_iter().enumerate() {
364 let mut slots = transcript_slots.clone();
365 if let Some(o) = observe_transport_context(entry.transport_context.as_ref().map(|u| &u.0)) {
366 slots.push(o);
367 }
368 if let Some(o) = observe_header(entry.headers.as_ref().map(|u| &u.0)) {
369 slots.push(o);
370 }
371 envelopes.push(fold_envelope(slots, true));
372 let source_line = (idx + 1) as u64;
373 let present = usize::from(entry.request.is_some())
374 + usize::from(entry.response.is_some())
375 + usize::from(entry.sse.is_some());
376
377 if present != 1 {
378 bail!(
379 "{} entry {} must contain exactly one of request, response, or sse",
380 source_label,
381 source_line
382 );
383 }
384
385 if let Some(UniqueValue(request)) = entry.request {
386 out.push(parse_jsonrpc_message(
387 request,
388 source_line,
389 entry.timestamp_ms,
390 McpAuthorizationDiscovery::default(),
391 )?);
392 continue;
393 }
394
395 let auth_discovery = parse_transport_auth_discovery(&entry);
396
397 if let Some(UniqueValue(response)) = entry.response {
398 out.push(parse_jsonrpc_message(
399 response,
400 source_line,
401 entry.timestamp_ms,
402 auth_discovery,
403 )?);
404 continue;
405 }
406
407 if let Some(sse) = entry.sse {
408 if let Some(jsonrpc) =
409 extract_jsonrpc_from_sse(&sse, allow_endpoint_event, max_embedded_json_depth)?
410 {
411 out.push(parse_jsonrpc_message(
412 jsonrpc,
413 source_line,
414 entry.timestamp_ms,
415 McpAuthorizationDiscovery::default(),
416 )?);
417 }
418 }
419 }
420
421 Ok((out, envelopes))
422}
423
424fn parse_jsonrpc_message(
425 v: serde_json::Value,
426 source_line: u64,
427 timestamp_ms_override: Option<u64>,
428 auth_discovery: McpAuthorizationDiscovery,
429) -> Result<McpEvent> {
430 if !v.is_object() {
431 bail!(
432 "MCP event at source line {} must be a JSON object",
433 source_line
434 );
435 }
436
437 if v.get("jsonrpc").and_then(serde_json::Value::as_str) != Some("2.0") {
438 bail!(
439 "MCP event at source line {} must carry JSON-RPC version 2.0",
440 source_line
441 );
442 }
443
444 let ts_ms = timestamp_ms_override.or_else(|| extract_ts_ms(&v));
445
446 let kind = classify_message(&v)
451 .map_err(|e| anyhow::anyhow!("MCP event at source line {}: {}", source_line, e))?;
452
453 let raw_id = v.get("id");
459 let id_str = match kind {
460 MessageKind::Notification { .. } => None,
461 MessageKind::Request { .. } => Some(require_acceptable_id(raw_id, source_line)?),
462 MessageKind::Response => {
463 let is_error = v.get("error").is_some();
464 match raw_id {
465 None | Some(serde_json::Value::Null) if is_error => None,
467 _ => Some(require_acceptable_id(raw_id, source_line)?),
468 }
469 }
470 };
471 let payload =
472 if let MessageKind::Request { method } | MessageKind::Notification { method } = kind {
473 match method {
474 "tools/list" => McpPayload::ToolsListRequest { raw: v.clone() },
475 "tools/call" => {
476 let params = v.get("params").cloned().unwrap_or(serde_json::Value::Null);
477 let name = params
478 .get("name")
479 .and_then(|x| x.as_str())
480 .unwrap_or("unknown_tool")
481 .to_string();
482 let arguments = params
483 .get("arguments")
484 .cloned()
485 .unwrap_or(serde_json::Value::Null);
486 McpPayload::ToolCallRequest {
487 name,
488 arguments,
489 raw: v.clone(),
490 }
491 }
492 _ => McpPayload::Other { raw: v.clone() },
494 }
495 } else {
496 if v.get("result").is_some() {
498 if looks_like_tools_list_result(&v) {
499 let tools = parse_tools_list_result(&v)?;
500 McpPayload::ToolsListResponse {
501 tools,
502 raw: v.clone(),
503 }
504 } else {
505 McpPayload::ToolCallResponse {
506 result: v.get("result").cloned().unwrap_or(serde_json::Value::Null),
507 is_error: false,
508 raw: v.clone(),
509 }
510 }
511 } else if v.get("error").is_some() {
512 McpPayload::ToolCallResponse {
513 result: v.get("error").cloned().unwrap_or(serde_json::Value::Null),
514 is_error: true,
515 raw: v.clone(),
516 }
517 } else {
518 McpPayload::Other { raw: v.clone() }
521 }
522 };
523
524 Ok(McpEvent {
525 source_line,
526 timestamp_ms: ts_ms,
527 jsonrpc_id: id_str,
528 auth_discovery,
529 payload,
530 })
531}
532
533fn parse_transport_auth_discovery(entry: &TransportTranscriptEntry) -> McpAuthorizationDiscovery {
534 let Some(status) = extract_http_status(entry) else {
535 return McpAuthorizationDiscovery::default();
536 };
537
538 if status != 401 {
539 return McpAuthorizationDiscovery::default();
540 }
541
542 let header_value = entry
543 .transport_context
544 .as_ref()
545 .and_then(|value| find_header_case_insensitive(&value.0, "www-authenticate"))
546 .or_else(|| {
547 entry
548 .headers
549 .as_ref()
550 .and_then(|value| find_header_case_insensitive(&value.0, "www-authenticate"))
551 });
552
553 let Some(www_authenticate) = header_value else {
554 return McpAuthorizationDiscovery::default();
555 };
556
557 let resource_metadata_visible = auth_param_visible(&www_authenticate, "resource_metadata");
558 let scope_challenge_visible = auth_param_visible(&www_authenticate, "scope");
559
560 if !resource_metadata_visible && !scope_challenge_visible {
561 return McpAuthorizationDiscovery::default();
562 }
563
564 McpAuthorizationDiscovery {
565 visible: true,
566 source_kind: McpAuthorizationDiscoverySourceKind::WwwAuthenticate,
567 resource_metadata_visible,
568 authorization_servers_visible: false,
569 scope_challenge_visible,
570 }
571}
572
573fn extract_http_status(entry: &TransportTranscriptEntry) -> Option<u16> {
574 entry
575 .transport_context
576 .as_ref()
577 .and_then(|v| extract_http_status_from_value(&v.0))
578 .or_else(|| {
579 entry
580 .headers
581 .as_ref()
582 .and_then(|v| extract_http_status_from_value(&v.0))
583 })
584}
585
586fn extract_http_status_from_value(value: &serde_json::Value) -> Option<u16> {
587 match value {
588 serde_json::Value::Object(map) => {
589 for key in ["status", "status_code", "http_status"] {
590 if let Some(status) = map.get(key).and_then(json_value_to_u16) {
591 return Some(status);
592 }
593 }
594
595 map.get("response").and_then(extract_http_status_from_value)
596 }
597 _ => None,
598 }
599}
600
601fn json_value_to_u16(value: &serde_json::Value) -> Option<u16> {
602 match value {
603 serde_json::Value::Number(n) => n.as_u64().and_then(|n| u16::try_from(n).ok()),
604 serde_json::Value::String(s) => s.parse::<u16>().ok(),
605 _ => None,
606 }
607}
608
609fn find_header_case_insensitive(value: &serde_json::Value, header_name: &str) -> Option<String> {
610 match value {
611 serde_json::Value::Object(map) => {
612 if let Some(headers) = map.get("headers") {
613 if let Some(found) = find_header_case_insensitive(headers, header_name) {
614 return Some(found);
615 }
616 }
617
618 if let Some(response) = map.get("response") {
619 if let Some(found) = find_header_case_insensitive(response, header_name) {
620 return Some(found);
621 }
622 }
623
624 map.iter().find_map(|(key, value)| {
625 if key.eq_ignore_ascii_case(header_name) {
626 value.as_str().map(ToString::to_string)
627 } else {
628 None
629 }
630 })
631 }
632 _ => None,
633 }
634}
635
636fn auth_param_visible(header_value: &str, param_name: &str) -> bool {
637 let lower = header_value.to_ascii_lowercase();
638 let needle = format!("{param_name}=");
639
640 lower
641 .match_indices(&needle)
642 .any(|(idx, _)| idx == 0 || matches!(lower.as_bytes()[idx - 1], b' ' | b',' | b'\t'))
643}
644
645fn require_acceptable_id(raw_id: Option<&serde_json::Value>, source_line: u64) -> Result<String> {
650 reject_unusable_id_shape(raw_id, source_line)?;
653 match raw_id.filter(|v| id_is_acceptable(v)) {
658 Some(serde_json::Value::String(id)) => Ok(id.clone()),
659 Some(serde_json::Value::Number(id)) => Ok(id.to_string()),
660 _ => bail!(
661 "JSON-RPC id on source line {} must be a string or a number",
662 source_line
663 ),
664 }
665}
666
667fn reject_unusable_id_shape(raw_id: Option<&serde_json::Value>, source_line: u64) -> Result<()> {
671 match raw_id {
672 None
673 | Some(serde_json::Value::Null)
674 | Some(serde_json::Value::String(_))
675 | Some(serde_json::Value::Number(_)) => Ok(()),
676 Some(serde_json::Value::Bool(_)) => {
677 bail!(
678 "JSON-RPC id on source line {} must not be a boolean",
679 source_line
680 )
681 }
682 Some(serde_json::Value::Array(_)) => {
683 bail!(
684 "JSON-RPC id on source line {} must not be an array",
685 source_line
686 )
687 }
688 Some(serde_json::Value::Object(_)) => {
689 bail!(
690 "JSON-RPC id on source line {} must not be an object",
691 source_line
692 )
693 }
694 }
695}
696
697fn extract_jsonrpc_from_sse(
698 sse: &TransportSseEnvelope,
699 allow_endpoint_event: bool,
700 max_embedded_json_depth: Option<usize>,
701) -> Result<Option<serde_json::Value>> {
702 let event_name = sse.event.as_deref().unwrap_or("message");
703 if event_name == "endpoint" && allow_endpoint_event {
704 return Ok(None);
705 }
706
707 if event_name != "message" {
708 return Ok(None);
709 }
710
711 extract_jsonrpc_like_value(&sse.data.0, max_embedded_json_depth)
712}
713
714#[derive(Debug, thiserror::Error)]
715#[error("embedded SSE JSON exceeds its depth limit")]
716pub(crate) struct EmbeddedJsonDepthExceeded;
717
718fn extract_jsonrpc_like_value(
726 value: &serde_json::Value,
727 max_embedded_json_depth: Option<usize>,
728) -> Result<Option<serde_json::Value>> {
729 match value {
730 serde_json::Value::Object(map)
731 if map.contains_key("method")
732 || map.contains_key("result")
733 || map.contains_key("error")
734 || map.contains_key("jsonrpc") =>
735 {
736 Ok(Some(value.clone()))
737 }
738 serde_json::Value::String(text) => {
743 if max_embedded_json_depth
744 .is_some_and(|limit| json_depth::exceeds_limit(text.as_bytes(), limit, false))
745 {
746 return Err(EmbeddedJsonDepthExceeded.into());
747 }
748 match serde_json::from_str::<UniqueValue>(text) {
749 Ok(UniqueValue(parsed)) => {
750 extract_jsonrpc_like_value(&parsed, max_embedded_json_depth)
751 }
752 Err(e) if e.classify() == serde_json::error::Category::Data => {
753 Err(anyhow::Error::new(e).context("invalid SSE data payload"))
754 }
755 Err(_) => Ok(None),
756 }
757 }
758 _ => Ok(None),
759 }
760}
761
762fn extract_ts_ms(v: &serde_json::Value) -> Option<u64> {
763 if let Some(t) = v.get("timestamp_ms").and_then(|t| t.as_u64()) {
765 return Some(t);
766 }
767 if let Some(t) = v.get("timestamp").and_then(|t| t.as_u64()) {
768 return Some(t); }
771 None
772}
773
774fn looks_like_tools_list_result(v: &serde_json::Value) -> bool {
775 v.get("result")
776 .and_then(|r| r.get("tools"))
777 .and_then(|t| t.as_array())
778 .is_some()
779}
780
781fn parse_tools_list_result(v: &serde_json::Value) -> Result<Vec<McpToolDef>> {
782 let tools = v
783 .get("result")
784 .and_then(|r| r.get("tools"))
785 .and_then(|t| t.as_array())
786 .cloned()
787 .unwrap_or_default();
788
789 let mut out = Vec::new();
790 for tool in tools {
791 let name = tool
792 .get("name")
793 .and_then(|x| x.as_str())
794 .unwrap_or("unknown")
795 .to_string();
796 let description = tool
797 .get("description")
798 .and_then(|x| x.as_str())
799 .map(|s| s.to_string());
800 let input_schema = tool
802 .get("inputSchema")
803 .cloned()
804 .or_else(|| tool.get("input_schema").cloned());
805 out.push(McpToolDef {
806 name,
807 description,
808 input_schema,
809 tool_identity: None,
810 });
811 }
812 Ok(out)
813}
814
815#[derive(Debug, Default)]
816struct TransportTranscript {
817 transport: Option<String>,
818 #[allow(dead_code)]
819 transport_context: Option<UniqueValue>,
820 #[allow(dead_code)]
821 headers: Option<UniqueValue>,
822 entries: Vec<TransportTranscriptEntry>,
823}
824
825impl<'de> Deserialize<'de> for TransportTranscript {
830 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
831 struct V;
832 impl<'de> serde::de::Visitor<'de> for V {
833 type Value = TransportTranscript;
834 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
835 f.write_str("a transport transcript with unique members")
836 }
837 fn visit_map<A: serde::de::MapAccess<'de>>(
838 self,
839 mut map: A,
840 ) -> Result<TransportTranscript, A::Error> {
841 let mut out = TransportTranscript::default();
842 let mut seen = SeenMembers::default();
843 while let Some(key) = map.next_key::<String>()? {
844 seen.insert::<A::Error>(&key)?;
845 match key.as_str() {
846 "transport" => out.transport = map.next_value()?,
847 "transport_context" => out.transport_context = Some(map.next_value()?),
848 "headers" => out.headers = Some(map.next_value()?),
849 "entries" => out.entries = map.next_value()?,
850 _ => {
851 map.next_value::<DuplicateAwareSink>()?;
852 }
853 }
854 }
855 Ok(out)
856 }
857 }
858 d.deserialize_map(V)
859 }
860}
861
862#[derive(Debug, Default)]
863struct TransportTranscriptEntry {
864 timestamp_ms: Option<u64>,
865 #[allow(dead_code)]
866 transport_context: Option<UniqueValue>,
867 #[allow(dead_code)]
868 headers: Option<UniqueValue>,
869 request: Option<UniqueValue>,
870 response: Option<UniqueValue>,
871 sse: Option<TransportSseEnvelope>,
872}
873
874impl<'de> Deserialize<'de> for TransportTranscriptEntry {
876 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
877 struct V;
878 impl<'de> serde::de::Visitor<'de> for V {
879 type Value = TransportTranscriptEntry;
880 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
881 f.write_str("a transport transcript entry with unique members")
882 }
883 fn visit_map<A: serde::de::MapAccess<'de>>(
884 self,
885 mut map: A,
886 ) -> Result<TransportTranscriptEntry, A::Error> {
887 let mut out = TransportTranscriptEntry::default();
888 let mut seen = SeenMembers::default();
889 while let Some(key) = map.next_key::<String>()? {
890 seen.insert::<A::Error>(&key)?;
891 match key.as_str() {
892 "timestamp_ms" => out.timestamp_ms = map.next_value()?,
893 "transport_context" => out.transport_context = Some(map.next_value()?),
894 "headers" => out.headers = Some(map.next_value()?),
895 "request" => out.request = Some(map.next_value()?),
896 "response" => out.response = Some(map.next_value()?),
897 "sse" => match map.next_value::<Option<TransportSseEnvelope>>()? {
901 Some(envelope) => out.sse = Some(envelope),
902 None => {
903 return Err(serde::de::Error::custom(
904 "sse slot is present and null",
905 ))
906 }
907 },
908 _ => {
909 map.next_value::<DuplicateAwareSink>()?;
910 }
911 }
912 }
913 Ok(out)
914 }
915 }
916 d.deserialize_map(V)
917 }
918}
919
920#[derive(Debug, Default)]
921struct TransportSseEnvelope {
922 event: Option<String>,
923 #[allow(dead_code)]
924 id: Option<String>,
925 data: UniqueValue,
926}
927
928impl<'de> Deserialize<'de> for TransportSseEnvelope {
931 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
932 struct V;
933 impl<'de> serde::de::Visitor<'de> for V {
934 type Value = TransportSseEnvelope;
935 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
936 f.write_str("an SSE envelope with unique members")
937 }
938 fn visit_map<A: serde::de::MapAccess<'de>>(
939 self,
940 mut map: A,
941 ) -> Result<TransportSseEnvelope, A::Error> {
942 let mut out = TransportSseEnvelope::default();
943 let mut seen = SeenMembers::default();
944 let mut saw_data = false;
949 while let Some(key) = map.next_key::<String>()? {
950 seen.insert::<A::Error>(&key)?;
951 match key.as_str() {
952 "event" => out.event = map.next_value()?,
953 "id" => out.id = map.next_value()?,
954 "data" => {
955 out.data = map.next_value()?;
956 saw_data = true;
957 }
958 _ => {
959 map.next_value::<DuplicateAwareSink>()?;
960 }
961 }
962 }
963 if !saw_data {
964 return Err(serde::de::Error::missing_field("data"));
965 }
966 Ok(out)
967 }
968 }
969 d.deserialize_map(V)
970 }
971}
972
973#[cfg(test)]
974mod era_wiring_tests;