relay_event_schema/protocol/
attributes.rs

1use 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    /// Additional arbitrary fields for forwards compatibility.
19    #[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
86/// Determines the `Pii` value for an attribute (or, more exactly, the
87/// attribute's `value` field) by looking it up in `relay-conventions`.
88///
89/// The attribute's value may be addressed by `<key>.value` (for advanced
90/// scrubbing rules) or by just `<key>` (for default scrubbing rules). Therefore,
91/// we iterate backwards through the processing state's segments and try to look
92/// up each in the conventions.
93///
94/// If the attribute is not found in the conventions, this returns `Pii::True`
95/// as a precaution.
96pub 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/// Wrapper struct around a collection of attributes with some convenience methods.
194#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
195pub struct Attributes(pub Object<Attribute>);
196
197impl Attributes {
198    /// Creates an empty collection of attributes.
199    pub fn new() -> Self {
200        Self(Object::new())
201    }
202
203    /// Returns the value of the attribute with the given key.
204    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    /// Returns the attribute with the given key.
213    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    /// Returns the attribute value as annotated.
222    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    /// Inserts an attribute with the given value into this collection.
231    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    /// Inserts an attribute with the given value if it was not already present.
246    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    /// Inserts an annotated attribute into this collection.
257    pub fn insert_raw(&mut self, key: String, attribute: Annotated<Attribute>) {
258        self.0.insert(key, attribute);
259    }
260
261    /// Checks whether this collection contains an attribute with the given key.
262    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    /// Iterates over this collection's attribute keys and values.
271    pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, Annotated<Attribute>> {
272        self.0.iter()
273    }
274
275    /// Iterates mutably over this collection's attribute keys and values.
276    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}