use std::borrow::Cow;
use std::collections::HashMap;
use std::ops::Deref;
#[non_exhaustive]
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub enum Attribute {
ContentDisposition,
ContentEncoding,
ContentLanguage,
ContentType,
CacheControl,
StorageClass,
Metadata(Cow<'static, str>),
}
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub struct AttributeValue(Cow<'static, str>);
impl AsRef<str> for AttributeValue {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<&'static str> for AttributeValue {
fn from(value: &'static str) -> Self {
Self(Cow::Borrowed(value))
}
}
impl From<String> for AttributeValue {
fn from(value: String) -> Self {
Self(Cow::Owned(value))
}
}
impl Deref for AttributeValue {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct Attributes(HashMap<Attribute, AttributeValue>);
impl Attributes {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self(HashMap::with_capacity(capacity))
}
pub fn insert(&mut self, key: Attribute, value: AttributeValue) -> Option<AttributeValue> {
self.0.insert(key, value)
}
pub fn get(&self, key: &Attribute) -> Option<&AttributeValue> {
self.0.get(key)
}
pub fn remove(&mut self, key: &Attribute) -> Option<AttributeValue> {
self.0.remove(key)
}
pub fn iter(&self) -> AttributesIter<'_> {
self.into_iter()
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<K, V> FromIterator<(K, V)> for Attributes
where
K: Into<Attribute>,
V: Into<AttributeValue>,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
}
impl<'a> IntoIterator for &'a Attributes {
type Item = (&'a Attribute, &'a AttributeValue);
type IntoIter = AttributesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
AttributesIter(self.0.iter())
}
}
#[derive(Debug)]
pub struct AttributesIter<'a>(std::collections::hash_map::Iter<'a, Attribute, AttributeValue>);
impl<'a> Iterator for AttributesIter<'a> {
type Item = (&'a Attribute, &'a AttributeValue);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attributes_basic() {
let mut attributes = Attributes::from_iter([
(Attribute::ContentDisposition, "inline"),
(Attribute::ContentEncoding, "gzip"),
(Attribute::ContentLanguage, "en-US"),
(Attribute::ContentType, "test"),
(Attribute::CacheControl, "control"),
(Attribute::Metadata("key1".into()), "value1"),
]);
assert!(!attributes.is_empty());
assert_eq!(attributes.len(), 6);
assert_eq!(
attributes.get(&Attribute::ContentType),
Some(&"test".into())
);
let metav = "control".into();
assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
assert_eq!(
attributes.insert(Attribute::CacheControl, "v1".into()),
Some(metav)
);
assert_eq!(attributes.len(), 6);
assert_eq!(
attributes.remove(&Attribute::CacheControl).unwrap(),
"v1".into()
);
assert_eq!(attributes.len(), 5);
let metav: AttributeValue = "v2".into();
attributes.insert(Attribute::CacheControl, metav.clone());
assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
assert_eq!(attributes.len(), 6);
assert_eq!(
attributes.get(&Attribute::ContentDisposition),
Some(&"inline".into())
);
assert_eq!(
attributes.get(&Attribute::ContentEncoding),
Some(&"gzip".into())
);
assert_eq!(
attributes.get(&Attribute::ContentLanguage),
Some(&"en-US".into())
);
assert_eq!(
attributes.get(&Attribute::Metadata("key1".into())),
Some(&"value1".into())
);
}
}