eventuary_core/
metadata.rs1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::Result;
6use crate::field_map::FieldMap;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct Metadata(FieldMap<String>);
11
12impl Metadata {
13 pub fn new() -> Self {
14 Self(FieldMap::new())
15 }
16
17 pub fn with(self, key: impl Into<String>, value: impl Into<String>) -> Result<Self> {
18 Ok(Self(self.0.with(key, value.into())?))
19 }
20
21 pub fn from_map(map: BTreeMap<String, String>) -> Result<Self> {
22 Ok(Self(FieldMap::try_from_map(map)?))
23 }
24
25 pub fn get(&self, key: &str) -> Option<&str> {
26 self.0.get(key).map(|s| s.as_str())
27 }
28
29 pub fn has(&self, key: &str) -> bool {
30 self.0.has(key)
31 }
32
33 pub fn len(&self) -> usize {
34 self.0.len()
35 }
36
37 pub fn is_empty(&self) -> bool {
38 self.0.is_empty()
39 }
40
41 pub fn as_map(&self) -> &BTreeMap<String, String> {
42 self.0.as_map()
43 }
44
45 pub fn into_map(self) -> BTreeMap<String, String> {
46 self.0.into_map()
47 }
48}
49
50impl TryFrom<BTreeMap<String, String>> for Metadata {
51 type Error = crate::error::Error;
52
53 fn try_from(map: BTreeMap<String, String>) -> Result<Self> {
54 Self::from_map(map)
55 }
56}
57
58impl TryFrom<HashMap<String, String>> for Metadata {
59 type Error = crate::error::Error;
60
61 fn try_from(map: HashMap<String, String>) -> Result<Self> {
62 Self::from_map(map.into_iter().collect())
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::error::Error;
70
71 #[test]
72 fn with_inserts_pair() {
73 let m = Metadata::new().with("k", "v").unwrap();
74 assert_eq!(m.get("k"), Some("v"));
75 assert!(m.has("k"));
76 assert_eq!(m.len(), 1);
77 }
78
79 #[test]
80 fn with_rejects_empty_key() {
81 let res = Metadata::new().with("", "v");
82 assert!(matches!(res, Err(Error::InvalidMetadataKey(_))));
83 }
84
85 #[test]
86 fn with_rejects_whitespace_padded_key() {
87 assert!(matches!(
88 Metadata::new().with(" key", "v"),
89 Err(Error::InvalidMetadataKey(_))
90 ));
91 assert!(matches!(
92 Metadata::new().with("key ", "v"),
93 Err(Error::InvalidMetadataKey(_))
94 ));
95 }
96
97 #[test]
98 fn with_rejects_newlines_in_key() {
99 assert!(matches!(
100 Metadata::new().with("k\nv", "v"),
101 Err(Error::InvalidMetadataKey(_))
102 ));
103 }
104
105 #[test]
106 fn from_map_validates_keys() {
107 let mut valid = BTreeMap::new();
108 valid.insert("source".to_owned(), "billing".to_owned());
109 let m = Metadata::from_map(valid).unwrap();
110 assert_eq!(m.get("source"), Some("billing"));
111
112 let mut invalid = BTreeMap::new();
113 invalid.insert(String::new(), "v".to_owned());
114 assert!(matches!(
115 Metadata::from_map(invalid),
116 Err(Error::InvalidMetadataKey(_))
117 ));
118 }
119
120 #[test]
121 fn try_from_hashmap_validates_keys() {
122 let mut invalid = HashMap::new();
123 invalid.insert(String::new(), "v".to_owned());
124 assert!(matches!(
125 Metadata::try_from(invalid),
126 Err(Error::InvalidMetadataKey(_))
127 ));
128 }
129
130 #[test]
131 fn serializes_as_plain_map() {
132 let m = Metadata::new().with("source", "billing").unwrap();
133 let json = serde_json::to_string(&m).unwrap();
134 assert_eq!(json, r#"{"source":"billing"}"#);
135 }
136
137 #[test]
138 fn deserializes_from_plain_map() {
139 let m: Metadata = serde_json::from_str(r#"{"source":"billing"}"#).unwrap();
140 assert_eq!(m.get("source"), Some("billing"));
141 }
142}