mod de;
mod ser;
use serde::Serialize;
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::headers::HeaderMap;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DeserializeHeadersError {
#[error("header `{header}` is not valid UTF-8")]
NotUtf8 {
header: String,
},
#[error("header `{header}` does not parse as {expected}: `{value}`")]
Parse {
header: String,
expected: &'static str,
value: String,
},
#[error("header `{header}` cannot be deserialized as {kind}: header values are scalars")]
UnsupportedShape {
header: String,
kind: &'static str,
},
#[error("typed headers deserialize into a struct or map, not {kind}")]
TopLevel {
kind: &'static str,
},
#[error("{0}")]
Message(String),
}
impl serde::de::Error for DeserializeHeadersError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum SerializeHeadersError {
#[error("field `{field}` cannot be serialized into a header value: {kind} is not a scalar")]
UnsupportedValue {
field: String,
kind: &'static str,
},
#[error("typed headers serialize from a struct or map, not {kind}")]
TopLevel {
kind: &'static str,
},
#[error("{0}")]
Message(String),
}
impl serde::ser::Error for SerializeHeadersError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
impl HeaderMap {
pub(crate) fn to_typed<T: DeserializeOwned>(&self) -> Result<T, DeserializeHeadersError> {
T::deserialize(de::HeadersDeserializer::new(self))
}
pub fn insert_typed<T: Serialize + ?Sized>(
&mut self,
value: &T,
) -> Result<(), SerializeHeadersError> {
value.serialize(ser::HeadersSerializer::new(self))
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use super::*;
#[derive(Debug, Deserialize)]
struct Meta {
#[allow(dead_code)]
task_id: u64,
#[allow(dead_code)]
ratio: f64,
}
#[test]
fn missing_required_header_is_an_error_naming_the_field() {
let mut headers = HeaderMap::new();
headers.insert("task_id", "7");
let err = headers.to_typed::<Meta>().expect_err("ratio missing");
assert!(err.to_string().contains("ratio"), "got: {err}");
}
#[test]
fn non_utf8_value_for_textual_field_is_an_error() {
#[derive(Debug, Deserialize)]
struct OnlyLabel {
#[allow(dead_code)]
label: String,
}
let mut headers = HeaderMap::new();
headers.insert("label", Bytes::from_static(&[0xff, 0xfe]));
let err = headers.to_typed::<OnlyLabel>().expect_err("not utf-8");
assert!(
matches!(err, DeserializeHeadersError::NotUtf8 { .. }),
"got: {err}"
);
}
#[test]
fn flattened_struct_is_rejected_on_serialize() {
#[derive(Serialize)]
struct Inner {
n: u64,
}
#[derive(Serialize)]
struct Flat {
top: u64,
#[serde(flatten)]
rest: Inner,
}
let mut headers = HeaderMap::new();
let err = headers
.insert_typed(&Flat {
top: 1,
rest: Inner { n: 2 },
})
.expect_err("flatten cannot round-trip");
assert!(
matches!(err, SerializeHeadersError::TopLevel { .. }),
"got: {err}"
);
}
#[test]
fn a_custom_serialize_failure_is_reported_as_written() {
struct Refuses;
impl Serialize for Refuses {
fn serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
Err(serde::ser::Error::custom("this value refuses to travel"))
}
}
let mut headers = HeaderMap::new();
let err = headers
.insert_typed(&Refuses)
.expect_err("the custom failure must surface");
assert!(err.to_string().contains("this value refuses to travel"));
}
#[test]
fn a_top_level_scalar_is_rejected_on_read() {
assert!(matches!(
HeaderMap::new().to_typed::<u64>(),
Err(DeserializeHeadersError::TopLevel { .. })
));
}
#[test]
fn renamed_field_reads_dashed_wire_name() {
#[derive(Serialize, Deserialize)]
struct Renamed {
#[serde(rename = "event-class")]
event_class: String,
}
let mut headers = HeaderMap::new();
headers
.insert_typed(&Renamed {
event_class: "upload".to_owned(),
})
.expect("renamed");
assert_eq!(headers.get_str("event-class"), Some("upload"));
let back: Renamed = headers.to_typed().expect("read back");
assert_eq!(back.event_class, "upload");
}
}