relay_event_schema/protocol/
attributes.rs1use std::borrow::{Borrow, Cow};
2use std::fmt;
3
4use enumset::EnumSet;
5use relay_protocol::{
6 Annotated, Empty, FromValue, IntoValue, Meta, Object, SkipSerialization, Value,
7};
8
9use crate::processor::{
10 Pii, ProcessValue, ProcessingResult, ProcessingState, Processor, ValueType,
11};
12
13#[derive(Clone, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
14pub struct Attribute {
15 #[metastructure(flatten)]
16 pub value: AttributeValue,
17
18 #[metastructure(additional_properties)]
20 pub other: Object<Value>,
21}
22
23impl Attribute {
24 pub fn new(attribute_type: AttributeType, value: Value) -> Self {
25 Self {
26 value: AttributeValue {
27 ty: Annotated::new(attribute_type),
28 value: Annotated::new(value),
29 },
30 other: Object::new(),
31 }
32 }
33}
34
35impl fmt::Debug for Attribute {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("Attribute")
38 .field("value", &self.value.value)
39 .field("type", &self.value.ty)
40 .field("other", &self.other)
41 .finish()
42 }
43}
44
45impl From<AttributeValue> for Attribute {
46 fn from(value: AttributeValue) -> Self {
47 Self {
48 value,
49 other: Default::default(),
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
55pub struct AttributeValue {
56 #[metastructure(field = "type", required = true, trim = false, pii = "false")]
57 pub ty: Annotated<AttributeType>,
58 #[metastructure(required = true, pii = "attribute_pii_from_conventions")]
59 pub value: Annotated<Value>,
60}
61
62macro_rules! impl_from {
63 ($ty:ident, $aty: expr) => {
64 impl From<Annotated<$ty>> for AttributeValue {
65 fn from(value: Annotated<$ty>) -> Self {
66 AttributeValue {
67 ty: Annotated::new($aty),
68 value: value.map_value(Into::into),
69 }
70 }
71 }
72
73 impl From<$ty> for AttributeValue {
74 fn from(value: $ty) -> Self {
75 AttributeValue::from(Annotated::new(value))
76 }
77 }
78 };
79}
80
81impl_from!(String, AttributeType::String);
82impl_from!(i64, AttributeType::Integer);
83impl_from!(f64, AttributeType::Double);
84impl_from!(bool, AttributeType::Boolean);
85
86pub fn attribute_pii_from_conventions(state: &ProcessingState) -> Pii {
97 for key in state.keys() {
98 let Some(info) = relay_conventions::attribute_info(key) else {
99 continue;
100 };
101
102 return match info.pii {
103 relay_conventions::Pii::True => Pii::True,
104 relay_conventions::Pii::False => Pii::False,
105 relay_conventions::Pii::Maybe => Pii::Maybe,
106 };
107 }
108
109 Pii::True
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub enum AttributeType {
114 Boolean,
115 Integer,
116 Double,
117 String,
118 Unknown(String),
119}
120
121impl ProcessValue for AttributeType {}
122
123impl AttributeType {
124 pub fn as_str(&self) -> &str {
125 match self {
126 Self::Boolean => "boolean",
127 Self::Integer => "integer",
128 Self::Double => "double",
129 Self::String => "string",
130 Self::Unknown(value) => value,
131 }
132 }
133
134 pub fn unknown_string() -> String {
135 "unknown".to_owned()
136 }
137}
138
139impl fmt::Display for AttributeType {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 write!(f, "{}", self.as_str())
142 }
143}
144
145impl From<String> for AttributeType {
146 fn from(value: String) -> Self {
147 match value.as_str() {
148 "boolean" => Self::Boolean,
149 "integer" => Self::Integer,
150 "double" => Self::Double,
151 "string" => Self::String,
152 _ => Self::Unknown(value),
153 }
154 }
155}
156
157impl Empty for AttributeType {
158 #[inline]
159 fn is_empty(&self) -> bool {
160 false
161 }
162}
163
164impl FromValue for AttributeType {
165 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
166 match String::from_value(value) {
167 Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
168 Annotated(None, meta) => Annotated(None, meta),
169 }
170 }
171}
172
173impl IntoValue for AttributeType {
174 fn into_value(self) -> Value
175 where
176 Self: Sized,
177 {
178 Value::String(match self {
179 Self::Unknown(s) => s,
180 s => s.to_string(),
181 })
182 }
183
184 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
185 where
186 Self: Sized,
187 S: serde::Serializer,
188 {
189 serde::ser::Serialize::serialize(self.as_str(), s)
190 }
191}
192
193#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
195pub struct Attributes(pub Object<Attribute>);
196
197impl Attributes {
198 pub fn new() -> Self {
200 Self(Object::new())
201 }
202
203 pub fn get_value<Q>(&self, key: &Q) -> Option<&Value>
205 where
206 String: Borrow<Q>,
207 Q: Ord + ?Sized,
208 {
209 self.get_annotated_value(key)?.value()
210 }
211
212 pub fn get_attribute<Q>(&self, key: &Q) -> Option<&Attribute>
214 where
215 String: Borrow<Q>,
216 Q: Ord + ?Sized,
217 {
218 self.0.get(key)?.value()
219 }
220
221 pub fn get_annotated_value<Q>(&self, key: &Q) -> Option<&Annotated<Value>>
223 where
224 String: Borrow<Q>,
225 Q: Ord + ?Sized,
226 {
227 Some(&self.0.get(key)?.value()?.value.value)
228 }
229
230 pub fn insert<K: Into<String>, V: Into<AttributeValue>>(&mut self, key: K, value: V) {
232 fn inner(slf: &mut Attributes, key: String, value: AttributeValue) {
233 let attribute = Annotated::new(Attribute {
234 value,
235 other: Default::default(),
236 });
237 slf.insert_raw(key, attribute);
238 }
239 let value = value.into();
240 if !value.value.is_empty() {
241 inner(self, key.into(), value);
242 }
243 }
244
245 pub fn insert_if_missing<F, V>(&mut self, key: &str, value: F)
247 where
248 F: FnOnce() -> V,
249 V: Into<AttributeValue>,
250 {
251 if !self.0.contains_key(key) {
252 self.insert(key.to_owned(), value());
253 }
254 }
255
256 pub fn insert_raw(&mut self, key: String, attribute: Annotated<Attribute>) {
258 self.0.insert(key, attribute);
259 }
260
261 pub fn contains_key<Q>(&self, key: &Q) -> bool
263 where
264 String: Borrow<Q>,
265 Q: Ord + ?Sized,
266 {
267 self.0.contains_key(key)
268 }
269
270 pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, Annotated<Attribute>> {
272 self.0.iter()
273 }
274
275 pub fn iter_mut(
277 &mut self,
278 ) -> std::collections::btree_map::IterMut<'_, String, Annotated<Attribute>> {
279 self.0.iter_mut()
280 }
281}
282
283impl IntoIterator for Attributes {
284 type Item = (String, Annotated<Attribute>);
285
286 type IntoIter = std::collections::btree_map::IntoIter<String, Annotated<Attribute>>;
287
288 fn into_iter(self) -> Self::IntoIter {
289 self.0.into_iter()
290 }
291}
292
293impl FromIterator<(String, Annotated<Attribute>)> for Attributes {
294 fn from_iter<T: IntoIterator<Item = (String, Annotated<Attribute>)>>(iter: T) -> Self {
295 Self(Object::from_iter(iter))
296 }
297}
298
299impl ProcessValue for Attributes {
300 #[inline]
301 fn value_type(&self) -> EnumSet<ValueType> {
302 EnumSet::only(ValueType::Object)
303 }
304
305 #[inline]
306 fn process_value<P>(
307 &mut self,
308 meta: &mut Meta,
309 processor: &mut P,
310 state: &ProcessingState<'_>,
311 ) -> ProcessingResult
312 where
313 P: Processor,
314 {
315 processor.process_attributes(self, meta, state)
316 }
317
318 fn process_child_values<P>(
319 &mut self,
320 processor: &mut P,
321 state: &ProcessingState<'_>,
322 ) -> ProcessingResult
323 where
324 P: Processor,
325 {
326 let enter_state = state.enter_nothing(Some(Cow::Borrowed(state.attrs())));
327 self.0.process_child_values(processor, &enter_state)
328 }
329}