Skip to main content

use_annotation/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::{collections::BTreeMap, error::Error};
6
7/// Error returned by annotation key constructors.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum AnnotationKeyError {
10    /// The supplied key was empty after trimming surrounding whitespace.
11    Empty,
12}
13
14impl fmt::Display for AnnotationKeyError {
15    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match self {
17            Self::Empty => formatter.write_str("annotation key cannot be empty"),
18        }
19    }
20}
21
22impl Error for AnnotationKeyError {}
23
24/// A non-empty annotation key.
25#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
26pub struct AnnotationKey(String);
27
28impl AnnotationKey {
29    /// Creates an annotation key from non-empty text.
30    ///
31    /// # Errors
32    ///
33    /// Returns [`AnnotationKeyError::Empty`] when the trimmed key is empty.
34    pub fn new(value: impl AsRef<str>) -> Result<Self, AnnotationKeyError> {
35        let trimmed = value.as_ref().trim();
36
37        if trimmed.is_empty() {
38            Err(AnnotationKeyError::Empty)
39        } else {
40            Ok(Self(trimmed.to_string()))
41        }
42    }
43
44    /// Returns the key text.
45    #[must_use]
46    pub fn as_str(&self) -> &str {
47        &self.0
48    }
49
50    /// Consumes the key and returns the owned string.
51    #[must_use]
52    pub fn into_string(self) -> String {
53        self.0
54    }
55}
56
57impl AsRef<str> for AnnotationKey {
58    fn as_ref(&self) -> &str {
59        self.as_str()
60    }
61}
62
63impl fmt::Display for AnnotationKey {
64    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65        formatter.write_str(self.as_str())
66    }
67}
68
69impl FromStr for AnnotationKey {
70    type Err = AnnotationKeyError;
71
72    fn from_str(value: &str) -> Result<Self, Self::Err> {
73        Self::new(value)
74    }
75}
76
77/// A plain string annotation value.
78#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
79pub struct AnnotationValue(String);
80
81impl AnnotationValue {
82    /// Creates an annotation value from plain string data.
83    #[must_use]
84    pub fn new(value: impl Into<String>) -> Self {
85        Self(value.into())
86    }
87
88    /// Returns the value text.
89    #[must_use]
90    pub fn as_str(&self) -> &str {
91        &self.0
92    }
93
94    /// Consumes the value and returns the owned string.
95    #[must_use]
96    pub fn into_string(self) -> String {
97        self.0
98    }
99}
100
101impl AsRef<str> for AnnotationValue {
102    fn as_ref(&self) -> &str {
103        self.as_str()
104    }
105}
106
107impl fmt::Display for AnnotationValue {
108    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
109        formatter.write_str(self.as_str())
110    }
111}
112
113impl From<&str> for AnnotationValue {
114    fn from(value: &str) -> Self {
115        Self::new(value)
116    }
117}
118
119impl From<String> for AnnotationValue {
120    fn from(value: String) -> Self {
121        Self::new(value)
122    }
123}
124
125/// A single annotation key-value pair.
126#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct Annotation {
128    key: AnnotationKey,
129    value: AnnotationValue,
130}
131
132impl Annotation {
133    /// Creates an annotation from a key and value.
134    #[must_use]
135    pub const fn new(key: AnnotationKey, value: AnnotationValue) -> Self {
136        Self { key, value }
137    }
138
139    /// Returns the annotation key.
140    #[must_use]
141    pub const fn key(&self) -> &AnnotationKey {
142        &self.key
143    }
144
145    /// Returns the annotation value.
146    #[must_use]
147    pub const fn value(&self) -> &AnnotationValue {
148        &self.value
149    }
150
151    /// Splits the annotation into its key and value.
152    #[must_use]
153    pub fn into_parts(self) -> (AnnotationKey, AnnotationValue) {
154        (self.key, self.value)
155    }
156}
157
158/// A deterministic annotation collection.
159#[derive(Clone, Debug, Default, Eq, PartialEq)]
160pub struct AnnotationSet {
161    values: BTreeMap<AnnotationKey, AnnotationValue>,
162}
163
164impl AnnotationSet {
165    /// Creates an empty annotation set.
166    #[must_use]
167    pub const fn new() -> Self {
168        Self {
169            values: BTreeMap::new(),
170        }
171    }
172
173    /// Inserts an annotation and returns the previous value for the key, if any.
174    pub fn insert(&mut self, annotation: Annotation) -> Option<AnnotationValue> {
175        let (key, value) = annotation.into_parts();
176        self.values.insert(key, value)
177    }
178
179    /// Inserts a key-value pair and returns the previous value for the key, if any.
180    ///
181    /// # Errors
182    ///
183    /// Returns [`AnnotationKeyError::Empty`] when the key is empty.
184    pub fn insert_pair(
185        &mut self,
186        key: impl AsRef<str>,
187        value: impl Into<AnnotationValue>,
188    ) -> Result<Option<AnnotationValue>, AnnotationKeyError> {
189        Ok(self.values.insert(AnnotationKey::new(key)?, value.into()))
190    }
191
192    /// Gets a value by key text.
193    #[must_use]
194    pub fn get(&self, key: impl AsRef<str>) -> Option<&AnnotationValue> {
195        let key = AnnotationKey::new(key).ok()?;
196        self.values.get(&key)
197    }
198
199    /// Removes a value by key text.
200    pub fn remove(&mut self, key: impl AsRef<str>) -> Option<AnnotationValue> {
201        let key = AnnotationKey::new(key).ok()?;
202        self.values.remove(&key)
203    }
204
205    /// Iterates annotations in deterministic key order.
206    pub fn iter(&self) -> impl Iterator<Item = (&AnnotationKey, &AnnotationValue)> {
207        self.values.iter()
208    }
209
210    /// Returns the number of annotations.
211    #[must_use]
212    pub fn len(&self) -> usize {
213        self.values.len()
214    }
215
216    /// Returns true when the set has no annotations.
217    #[must_use]
218    pub fn is_empty(&self) -> bool {
219        self.values.is_empty()
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::{Annotation, AnnotationKey, AnnotationKeyError, AnnotationSet, AnnotationValue};
226
227    #[test]
228    fn creates_valid_annotation_key() {
229        let key = AnnotationKey::new("source").expect("valid key");
230
231        assert_eq!(key.as_str(), "source");
232    }
233
234    #[test]
235    fn rejects_empty_annotation_key() {
236        assert_eq!(AnnotationKey::new("  "), Err(AnnotationKeyError::Empty));
237    }
238
239    #[test]
240    fn constructs_annotation_value() {
241        let value = AnnotationValue::new("manual");
242
243        assert_eq!(value.as_str(), "manual");
244    }
245
246    #[test]
247    fn annotation_ordering_is_deterministic() {
248        let mut annotations = AnnotationSet::new();
249        annotations.insert_pair("zeta", "last").expect("valid key");
250        annotations
251            .insert_pair("alpha", "first")
252            .expect("valid key");
253
254        let keys = annotations
255            .iter()
256            .map(|(key, _)| key.as_str())
257            .collect::<Vec<_>>();
258        assert_eq!(keys, vec!["alpha", "zeta"]);
259    }
260
261    #[test]
262    fn insert_get_remove_behavior() {
263        let mut annotations = AnnotationSet::new();
264        let annotation = Annotation::new(
265            AnnotationKey::new("source").expect("valid key"),
266            AnnotationValue::new("manual"),
267        );
268
269        assert_eq!(annotations.insert(annotation), None);
270        assert_eq!(
271            annotations.get("source").expect("stored value").as_str(),
272            "manual"
273        );
274        assert_eq!(
275            annotations.remove("source"),
276            Some(AnnotationValue::new("manual"))
277        );
278        assert!(annotations.is_empty());
279    }
280}