orion_error/core/
metadata.rs1use std::collections::BTreeMap;
2
3#[derive(Debug, Clone, PartialEq, Eq, Default)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[cfg_attr(feature = "serde", serde(transparent))]
6pub struct ErrorMetadata {
15 fields: BTreeMap<String, MetadataValue>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[cfg_attr(feature = "serde", serde(untagged))]
21pub enum MetadataValue {
26 String(String),
27 Bool(bool),
28 I64(i64),
29 U64(u64),
30}
31
32impl ErrorMetadata {
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub fn is_empty(&self) -> bool {
38 self.fields.is_empty()
39 }
40
41 pub fn as_map(&self) -> &BTreeMap<String, MetadataValue> {
42 &self.fields
43 }
44
45 pub fn insert<K, V>(&mut self, key: K, value: V)
46 where
47 K: Into<String>,
48 V: Into<MetadataValue>,
49 {
50 let key = key.into();
51 debug_assert!(!key.is_empty(), "metadata key must not be empty");
52 if key.is_empty() {
53 return;
54 }
55
56 self.fields.insert(key, value.into());
57 }
58
59 pub fn get(&self, key: &str) -> Option<&MetadataValue> {
60 self.fields.get(key)
61 }
62
63 pub fn get_str(&self, key: &str) -> Option<&str> {
64 match self.get(key) {
65 Some(MetadataValue::String(value)) => Some(value.as_str()),
66 _ => None,
67 }
68 }
69
70 pub fn iter(&self) -> impl Iterator<Item = (&String, &MetadataValue)> {
71 self.fields.iter()
72 }
73
74 pub fn merge_missing(&mut self, other: &ErrorMetadata) {
84 for (key, value) in other.iter() {
85 self.fields
86 .entry(key.clone())
87 .or_insert_with(|| value.clone());
88 }
89 }
90}
91
92impl From<String> for MetadataValue {
93 fn from(value: String) -> Self {
94 Self::String(value)
95 }
96}
97
98impl From<&str> for MetadataValue {
99 fn from(value: &str) -> Self {
100 Self::String(value.to_string())
101 }
102}
103
104impl From<bool> for MetadataValue {
105 fn from(value: bool) -> Self {
106 Self::Bool(value)
107 }
108}
109
110impl From<i64> for MetadataValue {
111 fn from(value: i64) -> Self {
112 Self::I64(value)
113 }
114}
115
116impl From<i32> for MetadataValue {
117 fn from(value: i32) -> Self {
118 Self::I64(i64::from(value))
119 }
120}
121
122impl From<u64> for MetadataValue {
123 fn from(value: u64) -> Self {
124 Self::U64(value)
125 }
126}
127
128impl From<u32> for MetadataValue {
129 fn from(value: u32) -> Self {
130 Self::U64(u64::from(value))
131 }
132}
133
134impl From<usize> for MetadataValue {
135 fn from(value: usize) -> Self {
136 Self::U64(value as u64)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::{ErrorMetadata, MetadataValue};
143
144 #[test]
145 fn test_metadata_insert_overwrites_duplicate_keys() {
146 let mut metadata = ErrorMetadata::new();
147 metadata.insert("config.kind", "sink_route");
148 metadata.insert("config.kind", "sink_defaults");
149
150 assert_eq!(metadata.get_str("config.kind"), Some("sink_defaults"));
151 }
152
153 #[test]
154 fn test_metadata_merge_missing_keeps_existing_values() {
155 let mut merged = ErrorMetadata::new();
156 merged.insert("config.kind", "sink_defaults");
157
158 let mut outer = ErrorMetadata::new();
159 outer.insert("config.kind", "sink_route");
160 outer.insert("config.group", "infra");
161
162 merged.merge_missing(&outer);
163
164 assert_eq!(merged.get_str("config.kind"), Some("sink_defaults"));
165 assert_eq!(merged.get_str("config.group"), Some("infra"));
166 }
167
168 #[test]
169 fn test_metadata_get_str_only_for_string_values() {
170 let mut metadata = ErrorMetadata::new();
171 metadata.insert("parse.line", 7u32);
172
173 assert_eq!(metadata.get("parse.line"), Some(&MetadataValue::U64(7)));
174 assert_eq!(metadata.get_str("parse.line"), None);
175 }
176}