use http::{header::InvalidHeaderValue, HeaderValue}; const APPLICATION_PREFIX: &str = "application/"; const APPLICATION_JSON: &str = "application/json"; const APPLICATION_PROTO: &str = "application/proto"; const APPLICATION_CONNECT_PREFIX: &str = "application/connect+"; const APPLICATION_CONNECT_JSON: &str = "application/connect+json"; const APPLICATION_CONNECT_PROTO: &str = "application/connect+proto"; /// Connect message codec /// /// See: https://connectrpc.com/docs/protocol/#unary-request #[derive(Clone, Debug, Default, PartialEq)] pub enum MessageCodec { #[default] Json, Proto, Custom(String), } impl MessageCodec { pub fn custom(codec: impl Into) -> Self { Self::Custom(codec.into()) } pub(crate) fn from_content_type(content_type: &HeaderValue) -> Option<(Self, bool)> { let content_type = content_type.to_str().ok()?; let streaming = content_type.starts_with(APPLICATION_CONNECT_PREFIX); let codec_str = if streaming { content_type.strip_prefix(APPLICATION_CONNECT_PREFIX)? } else { content_type.strip_prefix(APPLICATION_PREFIX)? }; let codec = match codec_str { "json" => MessageCodec::Json, "proto" => MessageCodec::Proto, custom_codec => MessageCodec::custom(custom_codec), }; Some((codec, streaming)) } /// Returns the content-type header value for this codec. /// /// Unary-Content-Type → "content-type" "application/" Message-Codec /// Streaming-Content-Type → "content-type" "application/connect+" ("proto" / "json" / {custom}) pub fn content_type(&self, streaming: bool) -> Result { Ok(match (self, streaming) { (MessageCodec::Json, false) => HeaderValue::from_static(APPLICATION_JSON), (MessageCodec::Json, true) => HeaderValue::from_static(APPLICATION_CONNECT_JSON), (MessageCodec::Proto, false) => HeaderValue::from_static(APPLICATION_PROTO), (MessageCodec::Proto, true) => HeaderValue::from_static(APPLICATION_CONNECT_PROTO), (MessageCodec::Custom(codec), false) => { HeaderValue::from_str(&format!("{APPLICATION_PREFIX}{codec}"))? } (MessageCodec::Custom(codec), true) => { HeaderValue::from_str(&format!("{APPLICATION_CONNECT_PREFIX}{codec}"))? } }) } } impl std::fmt::Display for MessageCodec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let codec = match self { MessageCodec::Json => "json", MessageCodec::Proto => "proto", MessageCodec::Custom(codec) => codec, }; write!(f, "{codec}") } }