use crate::{Error, StreamError, ietf, lite};
pub trait StreamCodes {
fn encode_stream_code(&self, err: &StreamError) -> u32;
fn decode_stream_code(&self, code: u32) -> StreamError;
fn transport_error<E: web_transport_trait::Error>(&self, err: E) -> Error {
if let Some((code, _reason)) = err.session_error() {
return crate::SessionError::from_code(code).into();
}
if let Some(code) = err.stream_error() {
return self.decode_stream_code(code).into();
}
Error::Transport(err.to_string())
}
}
impl StreamCodes for lite::Version {
fn encode_stream_code(&self, err: &StreamError) -> u32 {
err.to_code()
}
fn decode_stream_code(&self, code: u32) -> StreamError {
StreamError::from_code(code)
}
}
impl StreamCodes for ietf::Version {
fn encode_stream_code(&self, err: &StreamError) -> u32 {
ietf::error::to_stream_code(err, *self)
}
fn decode_stream_code(&self, code: u32) -> StreamError {
ietf::error::from_stream_code(code, *self)
}
}
impl StreamCodes for crate::Version {
fn encode_stream_code(&self, err: &StreamError) -> u32 {
match self {
Self::Lite(version) => version.encode_stream_code(err),
Self::Ietf(version) => version.encode_stream_code(err),
}
}
fn decode_stream_code(&self, code: u32) -> StreamError {
match self {
Self::Lite(version) => version.decode_stream_code(code),
Self::Ietf(version) => version.decode_stream_code(code),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_version_picks_the_registry() {
let lite = crate::Version::Lite(lite::Version::Lite05);
let ietf = crate::Version::Ietf(ietf::Version::Draft20);
assert_eq!(lite.encode_stream_code(&StreamError::Old), StreamError::Old.to_code());
assert_eq!(ietf.encode_stream_code(&StreamError::Old), ietf::error::INTERNAL_ERROR);
assert_eq!(lite.encode_stream_code(&StreamError::Cancel), ietf::error::CANCELLED);
assert_eq!(ietf.encode_stream_code(&StreamError::Cancel), ietf::error::CANCELLED);
let draft17 = crate::Version::Ietf(ietf::Version::Draft17);
assert_eq!(lite.decode_stream_code(0x4), StreamError::GoingAway);
assert_eq!(ietf.decode_stream_code(0x4), StreamError::GoingAway);
assert_eq!(draft17.decode_stream_code(0x4), StreamError::Unknown(0x4));
assert_eq!(
draft17.encode_stream_code(&StreamError::GoingAway),
ietf::error::INTERNAL_ERROR
);
}
#[test]
fn both_registries_agree_about_a_cancellation() {
let versions = crate::Versions::all().iter().copied().collect::<Vec<_>>();
for version in versions {
assert_eq!(
version.encode_stream_code(&StreamError::Cancel),
StreamError::Cancel.to_code(),
"{version:?} cancels with a different code than the Writer's Drop sends"
);
}
}
#[test]
fn a_session_close_keeps_the_session_registry() {
#[derive(Debug, thiserror::Error)]
#[error("failed")]
struct Failed {
session: Option<u32>,
stream: Option<u32>,
}
impl web_transport_trait::Error for Failed {
fn session_error(&self) -> Option<(u32, String)> {
self.session.map(|code| (code, "closed".to_string()))
}
fn stream_error(&self) -> Option<u32> {
self.stream
}
}
let version = ietf::Version::Draft20;
assert!(matches!(
version.transport_error(Failed {
session: Some(0x0),
stream: None
}),
Error::Session(crate::SessionError::Cancel)
));
assert!(matches!(
version.transport_error(Failed {
session: None,
stream: Some(0x0)
}),
Error::Stream(StreamError::Internal)
));
assert!(matches!(
version.transport_error(Failed {
session: None,
stream: None
}),
Error::Transport(_)
));
}
}