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, trim = false)]
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 pub fn into_string(self) -> Option<String> {
36 self.value.value.into_value()?.into_string()
37 }
38}
39
40impl fmt::Debug for Attribute {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.debug_struct("Attribute")
43 .field("value", &self.value.value)
44 .field("type", &self.value.ty)
45 .field("other", &self.other)
46 .finish()
47 }
48}
49
50impl<T> From<T> for Attribute
51where
52 AttributeValue: From<T>,
53{
54 fn from(value: T) -> Self {
55 let value = value.into();
56 Self {
57 value,
58 other: Default::default(),
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
64pub struct AttributeValue {
65 #[metastructure(field = "type", required = true, trim = false, pii = "false")]
66 pub ty: Annotated<AttributeType>,
67 #[metastructure(required = "value_or_meta", pii = "attribute_pii_from_conventions")]
68 pub value: Annotated<Value>,
69}
70
71macro_rules! impl_from {
72 ($ty:ident, $aty: expr) => {
73 impl From<Annotated<$ty>> for AttributeValue {
74 fn from(value: Annotated<$ty>) -> Self {
75 AttributeValue {
76 ty: Annotated::new($aty),
77 value: value.map_value(Into::into),
78 }
79 }
80 }
81
82 impl From<$ty> for AttributeValue {
83 fn from(value: $ty) -> Self {
84 AttributeValue::from(Annotated::new(value))
85 }
86 }
87 };
88}
89
90impl_from!(String, AttributeType::String);
91impl_from!(i64, AttributeType::Integer);
92impl_from!(f64, AttributeType::Double);
93impl_from!(bool, AttributeType::Boolean);
94
95impl From<Annotated<&str>> for AttributeValue {
96 fn from(value: Annotated<&str>) -> Self {
97 Self {
98 ty: Annotated::new(AttributeType::String),
99 value: value.map_value(Into::into),
100 }
101 }
102}
103
104impl From<&str> for AttributeValue {
105 fn from(value: &str) -> Self {
106 Self::from(Annotated::new(value))
107 }
108}
109
110impl From<Vec<String>> for AttributeValue {
111 fn from(value: Vec<String>) -> Self {
112 let values = value
113 .into_iter()
114 .map(|value| Annotated::new(Value::String(value)))
115 .collect();
116
117 Self {
118 ty: Annotated::new(AttributeType::Array),
119 value: Annotated::new(Value::Array(values)),
120 }
121 }
122}
123
124pub fn attribute_pii_from_conventions(state: &ProcessingState) -> Pii {
135 for key in state.keys() {
136 let Some(info) = relay_conventions::attribute_info(key) else {
137 continue;
138 };
139
140 return match info.apply_scrubbing {
141 relay_conventions::ApplyScrubbing::Auto => Pii::True,
142 relay_conventions::ApplyScrubbing::Never => Pii::False,
143 relay_conventions::ApplyScrubbing::Manual => Pii::Maybe,
144 };
145 }
146
147 Pii::True
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum AttributeType {
152 Boolean,
156 Integer,
160 Double,
164 String,
168 Array,
172 Unknown(String),
176}
177
178impl ProcessValue for AttributeType {}
179
180impl AttributeType {
181 pub fn as_str(&self) -> &str {
182 match self {
183 Self::Boolean => "boolean",
184 Self::Integer => "integer",
185 Self::Double => "double",
186 Self::String => "string",
187 Self::Array => "array",
188 Self::Unknown(value) => value,
189 }
190 }
191
192 pub fn unknown_string() -> String {
193 "unknown".to_owned()
194 }
195}
196
197impl fmt::Display for AttributeType {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "{}", self.as_str())
200 }
201}
202
203impl From<String> for AttributeType {
204 fn from(value: String) -> Self {
205 match value.as_str() {
206 "boolean" => Self::Boolean,
207 "integer" => Self::Integer,
208 "double" => Self::Double,
209 "string" => Self::String,
210 "array" => Self::Array,
211 _ => Self::Unknown(value),
212 }
213 }
214}
215
216impl Empty for AttributeType {
217 #[inline]
218 fn is_empty(&self) -> bool {
219 false
220 }
221}
222
223impl FromValue for AttributeType {
224 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
225 match String::from_value(value) {
226 Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
227 Annotated(None, meta) => Annotated(None, meta),
228 }
229 }
230}
231
232impl IntoValue for AttributeType {
233 fn into_value(self) -> Value
234 where
235 Self: Sized,
236 {
237 Value::String(match self {
238 Self::Unknown(s) => s,
239 s => s.to_string(),
240 })
241 }
242
243 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
244 where
245 Self: Sized,
246 S: serde::Serializer,
247 {
248 serde::ser::Serialize::serialize(self.as_str(), s)
249 }
250}
251
252#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
254pub struct Attributes(pub Object<Attribute>);
255
256impl Attributes {
257 pub fn new() -> Self {
259 Self(Object::new())
260 }
261
262 pub fn get_value<Q>(&self, key: &Q) -> Option<&Value>
264 where
265 String: Borrow<Q>,
266 Q: Ord + ?Sized,
267 {
268 self.get_annotated_value(key)?.value()
269 }
270
271 pub fn get_attribute<Q>(&self, key: &Q) -> Option<&Attribute>
273 where
274 String: Borrow<Q>,
275 Q: Ord + ?Sized,
276 {
277 self.0.get(key)?.value()
278 }
279
280 pub fn get_annotated_value<Q>(&self, key: &Q) -> Option<&Annotated<Value>>
282 where
283 String: Borrow<Q>,
284 Q: Ord + ?Sized,
285 {
286 Some(&self.0.get(key)?.value()?.value.value)
287 }
288
289 pub fn get_annotated_value_mut<Q>(&mut self, key: &Q) -> Option<&mut Annotated<Value>>
291 where
292 String: Borrow<Q>,
293 Q: Ord + ?Sized,
294 {
295 Some(&mut self.0.get_mut(key)?.value_mut().as_mut()?.value.value)
296 }
297
298 pub fn insert<K: Into<String>, V: Into<AttributeValue>>(&mut self, key: K, value: V) {
300 fn inner(slf: &mut Attributes, key: String, value: AttributeValue) {
301 let attribute = Annotated::new(Attribute {
302 value,
303 other: Default::default(),
304 });
305 slf.0.insert(key, attribute);
306 }
307 let value = value.into();
308 if !value.value.is_empty() {
309 inner(self, key.into(), value);
310 }
311 }
312
313 pub fn insert_if_missing<F, V>(&mut self, key: &str, value: F)
315 where
316 F: FnOnce() -> V,
317 V: Into<AttributeValue>,
318 {
319 if !self.0.contains_key(key) {
320 self.insert(key.to_owned(), value());
321 }
322 }
323
324 pub fn contains_key<Q>(&self, key: &Q) -> bool
326 where
327 String: Borrow<Q>,
328 Q: Ord + ?Sized,
329 {
330 self.0.contains_key(key)
331 }
332
333 pub fn remove<Q>(&mut self, key: &Q) -> Option<Annotated<Attribute>>
335 where
336 String: Borrow<Q>,
337 Q: Ord + ?Sized,
338 {
339 self.0.remove(key)
340 }
341}
342
343impl IntoIterator for Attributes {
344 type Item = (String, Annotated<Attribute>);
345
346 type IntoIter = std::collections::btree_map::IntoIter<String, Annotated<Attribute>>;
347
348 fn into_iter(self) -> Self::IntoIter {
349 self.0.into_iter()
350 }
351}
352
353impl FromIterator<(String, Annotated<Attribute>)> for Attributes {
354 fn from_iter<T: IntoIterator<Item = (String, Annotated<Attribute>)>>(iter: T) -> Self {
355 Self(Object::from_iter(iter))
356 }
357}
358
359impl<const N: usize> From<[(String, Annotated<Attribute>); N]> for Attributes {
360 fn from(value: [(String, Annotated<Attribute>); N]) -> Self {
361 value.into_iter().collect()
362 }
363}
364
365impl ProcessValue for Attributes {
369 #[inline]
370 fn value_type(&self) -> EnumSet<ValueType> {
371 EnumSet::only(ValueType::Object)
372 }
373
374 #[inline]
375 fn process_value<P>(
376 &mut self,
377 meta: &mut Meta,
378 processor: &mut P,
379 state: &ProcessingState<'_>,
380 ) -> ProcessingResult
381 where
382 P: Processor,
383 {
384 processor.process_attributes(self, meta, state)
385 }
386
387 fn process_child_values<P>(
388 &mut self,
389 processor: &mut P,
390 state: &ProcessingState<'_>,
391 ) -> ProcessingResult
392 where
393 P: Processor,
394 {
395 let enter_state = state.enter_nothing(Some(Cow::Borrowed(state.attrs())));
396 self.0.process_child_values(processor, &enter_state)
397 }
398}