use crate::{RetryOptions, request_extensions::RetryConfigForCall};
use std::time::Duration;
use tonic::metadata::{
AsciiMetadataKey, AsciiMetadataValue, BinaryMetadataKey, BinaryMetadataValue, KeyAndValueRef,
MetadataMap,
};
#[derive(Clone, Debug, Default)]
pub struct RpcMetadata {
inner: MetadataMap,
}
impl RpcMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn insert(
&mut self,
key: impl Into<String>,
value: impl Into<String>,
) -> Result<Option<String>, RpcMetadataError> {
let key = key.into();
let value = value.into();
let parsed_key = key
.parse::<AsciiMetadataKey>()
.map_err(|_| RpcMetadataError::InvalidAsciiKey { key: key.clone() })?;
let parsed_value = value.parse::<AsciiMetadataValue>().map_err(|_| {
RpcMetadataError::InvalidAsciiValue {
key,
value: value.clone(),
}
})?;
Ok(self.inner.insert(parsed_key, parsed_value).map(|previous| {
previous
.to_str()
.expect("ASCII RPC metadata remains valid while stored")
.to_owned()
}))
}
pub fn insert_binary(
&mut self,
key: impl Into<String>,
value: impl Into<Vec<u8>>,
) -> Result<Option<Vec<u8>>, RpcMetadataError> {
let key = key.into();
let parsed_key = key
.parse::<BinaryMetadataKey>()
.map_err(|_| RpcMetadataError::InvalidBinaryKey { key })?;
Ok(self
.inner
.insert_bin(parsed_key, BinaryMetadataValue::from_bytes(&value.into()))
.map(|previous| {
previous
.to_bytes()
.expect("binary RPC metadata remains valid while stored")
.to_vec()
}))
}
pub fn get_ascii(&self, key: &str) -> Option<&str> {
self.inner.get(key).and_then(|value| value.to_str().ok())
}
pub fn get_binary(&self, key: &str) -> Option<Vec<u8>> {
self.inner
.get_bin(key)
.and_then(|value| value.to_bytes().ok())
.map(|value| value.to_vec())
}
pub fn remove_ascii(&mut self, key: &str) -> Option<String> {
self.inner.remove(key).map(|value| {
value
.to_str()
.expect("ASCII RPC metadata remains valid while stored")
.to_owned()
})
}
pub fn remove_binary(&mut self, key: &str) -> Option<Vec<u8>> {
self.inner.remove_bin(key).map(|value| {
value
.to_bytes()
.expect("binary RPC metadata remains valid while stored")
.to_vec()
})
}
pub fn ascii(&self) -> impl Iterator<Item = (&str, &str)> {
self.inner.iter().filter_map(|entry| match entry {
KeyAndValueRef::Ascii(key, value) => Some((
key.as_str(),
value
.to_str()
.expect("ASCII RPC metadata remains valid while stored"),
)),
KeyAndValueRef::Binary(_, _) => None,
})
}
pub fn binary(&self) -> impl Iterator<Item = (&str, Vec<u8>)> {
self.inner.iter().filter_map(|entry| match entry {
KeyAndValueRef::Ascii(_, _) => None,
KeyAndValueRef::Binary(key, value) => Some((
key.as_str(),
value
.to_bytes()
.expect("binary RPC metadata remains valid while stored")
.to_vec(),
)),
})
}
fn apply_to<T>(&self, request: &mut tonic::Request<T>) {
for entry in self.inner.iter() {
match entry {
KeyAndValueRef::Ascii(key, value) => {
request.metadata_mut().insert(key.clone(), value.clone());
}
KeyAndValueRef::Binary(key, value) => {
request
.metadata_mut()
.insert_bin(key.clone(), value.clone());
}
}
}
}
}
impl PartialEq for RpcMetadata {
fn eq(&self, other: &Self) -> bool {
self.inner.as_ref() == other.inner.as_ref()
}
}
impl Eq for RpcMetadata {}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum RpcMetadataError {
#[error("invalid ASCII RPC metadata key: {key}")]
InvalidAsciiKey {
key: String,
},
#[error("invalid ASCII RPC metadata value for key {key}: {value:?}")]
InvalidAsciiValue {
key: String,
value: String,
},
#[error("invalid binary RPC metadata key: {key}")]
InvalidBinaryKey {
key: String,
},
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct RpcOptions {
pub metadata: RpcMetadata,
pub timeout: Option<Duration>,
pub retry_options: Option<RetryOptions>,
}
impl RpcOptions {
pub(crate) fn apply_to<T>(&self, request: &mut tonic::Request<T>) {
self.metadata.apply_to(request);
if let Some(timeout) = self.timeout {
request.set_timeout(timeout);
}
if let Some(retry_options) = &self.retry_options {
request
.extensions_mut()
.insert(RetryConfigForCall(retry_options.clone()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_validates_and_exposes_values() {
let mut metadata = RpcMetadata::new();
assert_eq!(metadata.insert("trace-id", "first").unwrap(), None);
assert_eq!(
metadata.insert("trace-id", "second").unwrap(),
Some("first".to_owned())
);
assert_eq!(
metadata
.insert_binary("trace-data-bin", vec![0, 255])
.unwrap(),
None
);
assert_eq!(metadata.get_ascii("trace-id"), Some("second"));
assert_eq!(metadata.get_binary("trace-data-bin"), Some(vec![0, 255]));
assert!(matches!(
metadata.insert("bad key", "value"),
Err(RpcMetadataError::InvalidAsciiKey { .. })
));
assert!(matches!(
metadata.insert("valid-key", "bad\nvalue"),
Err(RpcMetadataError::InvalidAsciiValue { .. })
));
assert!(matches!(
metadata.insert_binary("missing-suffix", vec![]),
Err(RpcMetadataError::InvalidBinaryKey { .. })
));
assert_eq!(metadata.remove_ascii("trace-id"), Some("second".to_owned()));
assert_eq!(metadata.remove_binary("trace-data-bin"), Some(vec![0, 255]));
}
}