use core::fmt;
use crate::error::DecodeError;
use crate::wire::{Reader, Sink};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TagsError {
EmptyKey,
DuplicateKey,
TooLarge,
}
impl fmt::Display for TagsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyKey => f.write_str("empty tag key"),
Self::DuplicateKey => f.write_str("duplicate tag key"),
Self::TooLarge => f.write_str("encoded tags too large"),
}
}
}
impl std::error::Error for TagsError {}
pub fn encode_tags<'t>(
tags: impl IntoIterator<Item = (&'t str, &'t str)>,
max_len: usize,
) -> Result<Vec<u8>, TagsError> {
let mut out = Vec::new();
let mut seen: Vec<&str> = Vec::new();
for (k, v) in tags {
if k.is_empty() {
return Err(TagsError::EmptyKey);
}
if seen.contains(&k) {
return Err(TagsError::DuplicateKey);
}
seen.push(k);
out.put_bytes(k.as_bytes());
out.put_bytes(v.as_bytes());
if out.len() > max_len {
return Err(TagsError::TooLarge);
}
}
Ok(out)
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Tags<'a> {
bytes: &'a [u8],
}
impl<'a> Tags<'a> {
pub fn parse(meta: &'a [u8]) -> Result<Self, DecodeError> {
let mut r = Reader::new(meta);
let mut keys: Vec<&[u8]> = Vec::new();
while !r.is_empty() {
let key = r.bytes().map_err(|_| DecodeError::BadTags)?;
let value = r.bytes().map_err(|_| DecodeError::BadTags)?;
if key.is_empty()
|| core::str::from_utf8(key).is_err()
|| core::str::from_utf8(value).is_err()
|| keys.contains(&key)
{
return Err(DecodeError::BadTags);
}
keys.push(key);
}
Ok(Self { bytes: meta })
}
pub fn iter(&self) -> TagIter<'a> {
TagIter {
r: Reader::new(self.bytes),
}
}
pub fn get(&self, key: &str) -> Option<&'a str> {
self.iter().find(|(k, _)| *k == key).map(|(_, v)| v)
}
pub fn len(&self) -> usize {
self.iter().count()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
impl<'a> IntoIterator for Tags<'a> {
type Item = (&'a str, &'a str);
type IntoIter = TagIter<'a>;
fn into_iter(self) -> TagIter<'a> {
self.iter()
}
}
impl fmt::Debug for Tags<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
pub struct TagIter<'a> {
r: Reader<'a>,
}
impl<'a> Iterator for TagIter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
if self.r.is_empty() {
return None;
}
let k = core::str::from_utf8(self.r.bytes().ok()?).ok()?;
let v = core::str::from_utf8(self.r.bytes().ok()?).ok()?;
Some((k, v))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_keeps_order() {
let bytes = encode_tags([("role", "api"), ("zone", "eu-1"), ("empty", "")], 512).unwrap();
let tags = Tags::parse(&bytes).unwrap();
let got: Vec<_> = tags.iter().collect();
assert_eq!(got, [("role", "api"), ("zone", "eu-1"), ("empty", "")]);
assert_eq!(tags.get("zone"), Some("eu-1"));
assert_eq!(tags.get("nope"), None);
assert_eq!(tags.len(), 3);
}
#[test]
fn empty_is_valid() {
assert!(Tags::parse(&[]).unwrap().is_empty());
assert_eq!(encode_tags([], 0).unwrap(), Vec::<u8>::new());
}
#[test]
fn encoder_rejects_bad_input() {
assert_eq!(encode_tags([("", "x")], 512), Err(TagsError::EmptyKey));
assert_eq!(
encode_tags([("a", "1"), ("a", "2")], 512),
Err(TagsError::DuplicateKey)
);
assert_eq!(
encode_tags([("key", "0123456789")], 8),
Err(TagsError::TooLarge)
);
}
#[test]
fn decoder_rejects_bad_input() {
for bad in [
&[0x00, 0x00][..], &[0x01, b'a'], &[0x01, b'a', 0x05, b'x'], &[0x01, 0xff, 0x00], &[0x01, b'a', 0x01, 0xff], &[0x01, b'a', 0x00, 0x01, b'a', 0x00], ] {
assert_eq!(Tags::parse(bad), Err(DecodeError::BadTags), "{bad:02x?}");
}
}
}