mod endpoint;
mod framing;
mod rate_limit;
mod transport_spec;
pub use endpoint::{MessageEndpoint, MessageEndpointError};
pub use framing::{FrameDecoder, FramingError, MaxFrameSize, MaxFrameSizeError, encode_frame};
pub use rate_limit::{RateLimit, Verdict, check as check_rate_limit};
pub use transport_spec::{SpecError, TransportSpec};
use core::fmt;
use crate::domain::ProtocolVersion;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TransportKind {
Stdio,
LegacySse,
StreamableHttp,
StatelessHttp,
}
impl TransportKind {
pub const ALL: [Self; 4] = [
Self::Stdio,
Self::LegacySse,
Self::StreamableHttp,
Self::StatelessHttp,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Stdio => "stdio",
Self::LegacySse => "sse",
Self::StreamableHttp => "http",
Self::StatelessHttp => "http-stateless",
}
}
}
impl fmt::Display for TransportKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[must_use]
pub const fn transport_allowed(version: ProtocolVersion, transport: TransportKind) -> bool {
match version {
ProtocolVersion::V2024_11_05 => {
matches!(transport, TransportKind::Stdio | TransportKind::LegacySse)
}
ProtocolVersion::V2025_03_26
| ProtocolVersion::V2025_06_18
| ProtocolVersion::V2025_11_25 => {
matches!(
transport,
TransportKind::Stdio | TransportKind::StreamableHttp
)
}
ProtocolVersion::V2026_07_28 => matches!(
transport,
TransportKind::Stdio | TransportKind::StreamableHttp | TransportKind::StatelessHttp
),
ProtocolVersion::Unknown(_) => false,
}
}
#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests assert on fallible calls and panic with the failing precondition; propagating errors would replace a clear failure message with noise"
)]
use super::{TransportKind, transport_allowed};
use crate::domain::{ProtocolVersion, ReleaseDate};
#[test]
fn derived_ordering_agrees_in_both_directions() {
for left in TransportKind::ALL {
for right in TransportKind::ALL {
let total = left.cmp(&right);
assert_eq!(left.partial_cmp(&right), Some(total));
assert_eq!(left < right, total.is_lt());
assert_eq!(left > right, total.is_gt());
}
}
assert!(TransportKind::Stdio < TransportKind::LegacySse);
assert!(TransportKind::LegacySse < TransportKind::StreamableHttp);
assert!(TransportKind::StreamableHttp < TransportKind::StatelessHttp);
}
#[test]
fn the_whole_matrix_is_what_the_changelogs_say() {
let expected: [[bool; 4]; 5] = [
[true, true, false, false],
[true, false, true, false],
[true, false, true, false],
[true, false, true, false],
[true, false, true, true],
];
for (version, row) in ProtocolVersion::KNOWN.iter().zip(expected) {
for (transport, allowed) in TransportKind::ALL.iter().zip(row) {
assert_eq!(
transport_allowed(*version, *transport),
allowed,
"{version} over {transport}"
);
}
}
}
#[test]
fn unknown_revisions_allow_nothing() {
for date in ["2027-01-01", "2030-12-31", "2999-01-01"] {
let version = ProtocolVersion::Unknown(date.parse::<ReleaseDate>().unwrap());
for transport in TransportKind::ALL {
assert!(
!transport_allowed(version, transport),
"{version} must not allow {transport}"
);
}
}
}
#[test]
fn stdio_is_defined_for_every_known_revision() {
for version in ProtocolVersion::KNOWN {
assert!(
transport_allowed(version, TransportKind::Stdio),
"{version}"
);
}
}
#[test]
fn short_names_are_stable_and_distinct() {
let names: Vec<&str> = TransportKind::ALL.iter().map(|t| t.as_str()).collect();
assert_eq!(names, vec!["stdio", "sse", "http", "http-stateless"]);
let unique: std::collections::BTreeSet<&str> = names.iter().copied().collect();
assert_eq!(unique.len(), names.len(), "short names collide");
for transport in TransportKind::ALL {
assert_eq!(transport.to_string(), transport.as_str());
}
}
}