#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::{collections::BTreeMap, error::Error};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AnnotationKeyError {
Empty,
}
impl fmt::Display for AnnotationKeyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("annotation key cannot be empty"),
}
}
}
impl Error for AnnotationKeyError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AnnotationKey(String);
impl AnnotationKey {
pub fn new(value: impl AsRef<str>) -> Result<Self, AnnotationKeyError> {
let trimmed = value.as_ref().trim();
if trimmed.is_empty() {
Err(AnnotationKeyError::Empty)
} else {
Ok(Self(trimmed.to_string()))
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for AnnotationKey {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for AnnotationKey {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for AnnotationKey {
type Err = AnnotationKeyError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AnnotationValue(String);
impl AnnotationValue {
#[must_use]
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for AnnotationValue {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for AnnotationValue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl From<&str> for AnnotationValue {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for AnnotationValue {
fn from(value: String) -> Self {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Annotation {
key: AnnotationKey,
value: AnnotationValue,
}
impl Annotation {
#[must_use]
pub const fn new(key: AnnotationKey, value: AnnotationValue) -> Self {
Self { key, value }
}
#[must_use]
pub const fn key(&self) -> &AnnotationKey {
&self.key
}
#[must_use]
pub const fn value(&self) -> &AnnotationValue {
&self.value
}
#[must_use]
pub fn into_parts(self) -> (AnnotationKey, AnnotationValue) {
(self.key, self.value)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AnnotationSet {
values: BTreeMap<AnnotationKey, AnnotationValue>,
}
impl AnnotationSet {
#[must_use]
pub const fn new() -> Self {
Self {
values: BTreeMap::new(),
}
}
pub fn insert(&mut self, annotation: Annotation) -> Option<AnnotationValue> {
let (key, value) = annotation.into_parts();
self.values.insert(key, value)
}
pub fn insert_pair(
&mut self,
key: impl AsRef<str>,
value: impl Into<AnnotationValue>,
) -> Result<Option<AnnotationValue>, AnnotationKeyError> {
Ok(self.values.insert(AnnotationKey::new(key)?, value.into()))
}
#[must_use]
pub fn get(&self, key: impl AsRef<str>) -> Option<&AnnotationValue> {
let key = AnnotationKey::new(key).ok()?;
self.values.get(&key)
}
pub fn remove(&mut self, key: impl AsRef<str>) -> Option<AnnotationValue> {
let key = AnnotationKey::new(key).ok()?;
self.values.remove(&key)
}
pub fn iter(&self) -> impl Iterator<Item = (&AnnotationKey, &AnnotationValue)> {
self.values.iter()
}
#[must_use]
pub fn len(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::{Annotation, AnnotationKey, AnnotationKeyError, AnnotationSet, AnnotationValue};
#[test]
fn creates_valid_annotation_key() {
let key = AnnotationKey::new("source").expect("valid key");
assert_eq!(key.as_str(), "source");
}
#[test]
fn rejects_empty_annotation_key() {
assert_eq!(AnnotationKey::new(" "), Err(AnnotationKeyError::Empty));
}
#[test]
fn constructs_annotation_value() {
let value = AnnotationValue::new("manual");
assert_eq!(value.as_str(), "manual");
}
#[test]
fn annotation_ordering_is_deterministic() {
let mut annotations = AnnotationSet::new();
annotations.insert_pair("zeta", "last").expect("valid key");
annotations
.insert_pair("alpha", "first")
.expect("valid key");
let keys = annotations
.iter()
.map(|(key, _)| key.as_str())
.collect::<Vec<_>>();
assert_eq!(keys, vec!["alpha", "zeta"]);
}
#[test]
fn insert_get_remove_behavior() {
let mut annotations = AnnotationSet::new();
let annotation = Annotation::new(
AnnotationKey::new("source").expect("valid key"),
AnnotationValue::new("manual"),
);
assert_eq!(annotations.insert(annotation), None);
assert_eq!(
annotations.get("source").expect("stored value").as_str(),
"manual"
);
assert_eq!(
annotations.remove("source"),
Some(AnnotationValue::new("manual"))
);
assert!(annotations.is_empty());
}
}