Skip to main content

protocol_droid/application/jsonrpc/
batch.rs

1//! Whether this session accepts a batch frame at all.
2//!
3//! # Why this is a port and not a flag
4//!
5//! Batching is not a transport setting and it is not a decode setting. It is a
6//! property of the negotiated pair, and it moved once already: `2025-06-18`
7//! removed it, and `2026-07-28` is stateless precisely because a stateless
8//! request cannot be correlated without one. So the question is asked against
9//! the session's [`FeatureProfile`] rather than answered once at construction.
10//!
11//! Keeping it a trait rather than a bool is what lets A2A and ACP each bring
12//! their own answer. A bool would put the default — "batching is fine" — in the
13//! JSON-RPC core, and the default is the wrong one: a protocol that never
14//! defined batching would silently inherit support for it.
15
16use crate::domain::feature::{Feature, FeatureProfile};
17
18/// Decides whether a batch frame is acceptable in this session.
19///
20/// The trait belongs to the consumer, so it lives in the module that consumes
21/// it rather than in a shared ports module or in an adapter.
22pub trait BatchPolicy {
23    /// Whether a frame holding several messages may be processed.
24    fn allows_batch(&self, profile: &FeatureProfile) -> bool;
25}
26
27/// Batching only when the negotiated pair supports it.
28///
29/// This is the MCP policy. [`Feature::JsonRpcBatching`] is absent from a
30/// `2025-06-18`-or-later profile, so a modern peer that sends a batch is
31/// refused rather than quietly processed — the difference is observable to the
32/// sender, which is the point.
33#[derive(Debug, Clone, Copy, Default)]
34pub struct ProfileBatching;
35
36impl BatchPolicy for ProfileBatching {
37    fn allows_batch(&self, profile: &FeatureProfile) -> bool {
38        profile.allows(Feature::JsonRpcBatching)
39    }
40}
41
42/// Always accept a batch, whatever the profile says.
43///
44/// For a protocol whose specification defines batching in every version, and
45/// for tests that need a frame to reach the router without a capability
46/// negotiation first. It is named rather than being the default so that
47/// choosing it is a decision in the source rather than an omission.
48#[derive(Debug, Clone, Copy, Default)]
49pub struct AllowBatching;
50
51impl BatchPolicy for AllowBatching {
52    fn allows_batch(&self, _profile: &FeatureProfile) -> bool {
53        true
54    }
55}
56
57/// Refuse every batch.
58#[derive(Debug, Clone, Copy, Default)]
59pub struct DenyBatching;
60
61impl BatchPolicy for DenyBatching {
62    fn allows_batch(&self, _profile: &FeatureProfile) -> bool {
63        false
64    }
65}