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    /// Returns the string value of this attribute.
35    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 = true, 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
95/// Determines the `Pii` value for an attribute (or, more exactly, the
96/// attribute's `value` field) by looking it up in `relay-conventions`.
97///
98/// The attribute's value may be addressed by `<key>.value` (for advanced
99/// scrubbing rules) or by just `<key>` (for default scrubbing rules). Therefore,
100/// we iterate backwards through the processing state's segments and try to look
101/// up each in the conventions.
102///
103/// If the attribute is not found in the conventions, this returns `Pii::True`
104/// as a precaution.
105pub fn attribute_pii_from_conventions(state: &ProcessingState) -> Pii {
106    for key in state.keys() {
107        let Some(info) = relay_conventions::attribute_info(key) else {
108            continue;
109        };
110
111        return match info.pii {
112            relay_conventions::Pii::True => Pii::True,
113            relay_conventions::Pii::False => Pii::False,
114            relay_conventions::Pii::Maybe => Pii::Maybe,
115        };
116    }
117
118    Pii::True
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub enum AttributeType {
123    /// A boolean.
124    ///
125    /// The respective value must be of type [`Value::Bool`].
126    Boolean,
127    /// An integer type.
128    ///
129    /// The respective value must be of type [`Value::I64`] or [`Value::U64`].
130    Integer,
131    /// A floating point/double type.
132    ///
133    /// The respective value must be of type [`Value::F64`].
134    Double,
135    /// A string type.
136    ///
137    /// The respective value must be of type [`Value::String`].
138    String,
139    /// A string type.
140    ///
141    /// The respective value must be of type [`Value::Array`].
142    Array,
143    /// An unknown type.
144    ///
145    /// Kept for forward compatibility.
146    Unknown(String),
147}
148
149impl ProcessValue for AttributeType {}
150
151impl AttributeType {
152    pub fn as_str(&self) -> &str {
153        match self {
154            Self::Boolean => "boolean",
155            Self::Integer => "integer",
156            Self::Double => "double",
157            Self::String => "string",
158            Self::Array => "array",
159            Self::Unknown(value) => value,
160        }
161    }
162
163    pub fn unknown_string() -> String {
164        "unknown".to_owned()
165    }
166}
167
168impl fmt::Display for AttributeType {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "{}", self.as_str())
171    }
172}
173
174impl From<String> for AttributeType {
175    fn from(value: String) -> Self {
176        match value.as_str() {
177            "boolean" => Self::Boolean,
178            "integer" => Self::Integer,
179            "double" => Self::Double,
180            "string" => Self::String,
181            "array" => Self::Array,
182            _ => Self::Unknown(value),
183        }
184    }
185}
186
187impl Empty for AttributeType {
188    #[inline]
189    fn is_empty(&self) -> bool {
190        false
191    }
192}
193
194impl FromValue for AttributeType {
195    fn from_value(value: Annotated<Value>) -> Annotated<Self> {
196        match String::from_value(value) {
197            Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
198            Annotated(None, meta) => Annotated(None, meta),
199        }
200    }
201}
202
203impl IntoValue for AttributeType {
204    fn into_value(self) -> Value
205    where
206        Self: Sized,
207    {
208        Value::String(match self {
209            Self::Unknown(s) => s,
210            s => s.to_string(),
211        })
212    }
213
214    fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
215    where
216        Self: Sized,
217        S: serde::Serializer,
218    {
219        serde::ser::Serialize::serialize(self.as_str(), s)
220    }
221}
222
223/// Wrapper struct around a collection of attributes with some convenience methods.
224#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
225pub struct Attributes(pub Object<Attribute>);
226
227impl Attributes {
228    /// Creates an empty collection of attributes.
229    pub fn new() -> Self {
230        Self(Object::new())
231    }
232
233    /// Returns the value of the attribute with the given key.
234    pub fn get_value<Q>(&self, key: &Q) -> Option<&Value>
235    where
236        String: Borrow<Q>,
237        Q: Ord + ?Sized,
238    {
239        self.get_annotated_value(key)?.value()
240    }
241
242    /// Returns the attribute with the given key.
243    pub fn get_attribute<Q>(&self, key: &Q) -> Option<&Attribute>
244    where
245        String: Borrow<Q>,
246        Q: Ord + ?Sized,
247    {
248        self.0.get(key)?.value()
249    }
250
251    /// Returns the attribute value as annotated.
252    pub fn get_annotated_value<Q>(&self, key: &Q) -> Option<&Annotated<Value>>
253    where
254        String: Borrow<Q>,
255        Q: Ord + ?Sized,
256    {
257        Some(&self.0.get(key)?.value()?.value.value)
258    }
259
260    /// Inserts an attribute with the given value into this collection.
261    pub fn insert<K: Into<String>, V: Into<AttributeValue>>(&mut self, key: K, value: V) {
262        fn inner(slf: &mut Attributes, key: String, value: AttributeValue) {
263            let attribute = Annotated::new(Attribute {
264                value,
265                other: Default::default(),
266            });
267            slf.0.insert(key, attribute);
268        }
269        let value = value.into();
270        if !value.value.is_empty() {
271            inner(self, key.into(), value);
272        }
273    }
274
275    /// Inserts an attribute with the given value if it was not already present.
276    pub fn insert_if_missing<F, V>(&mut self, key: &str, value: F)
277    where
278        F: FnOnce() -> V,
279        V: Into<AttributeValue>,
280    {
281        if !self.0.contains_key(key) {
282            self.insert(key.to_owned(), value());
283        }
284    }
285
286    /// Checks whether this collection contains an attribute with the given key.
287    pub fn contains_key<Q>(&self, key: &Q) -> bool
288    where
289        String: Borrow<Q>,
290        Q: Ord + ?Sized,
291    {
292        self.0.contains_key(key)
293    }
294
295    /// Removes an attribute from this collection.
296    pub fn remove<Q>(&mut self, key: &Q) -> Option<Annotated<Attribute>>
297    where
298        String: Borrow<Q>,
299        Q: Ord + ?Sized,
300    {
301        self.0.remove(key)
302    }
303}
304
305impl IntoIterator for Attributes {
306    type Item = (String, Annotated<Attribute>);
307
308    type IntoIter = std::collections::btree_map::IntoIter<String, Annotated<Attribute>>;
309
310    fn into_iter(self) -> Self::IntoIter {
311        self.0.into_iter()
312    }
313}
314
315impl FromIterator<(String, Annotated<Attribute>)> for Attributes {
316    fn from_iter<T: IntoIterator<Item = (String, Annotated<Attribute>)>>(iter: T) -> Self {
317        Self(Object::from_iter(iter))
318    }
319}
320
321impl<const N: usize> From<[(String, Annotated<Attribute>); N]> for Attributes {
322    fn from(value: [(String, Annotated<Attribute>); N]) -> Self {
323        value.into_iter().collect()
324    }
325}
326
327impl ProcessValue for Attributes {
328    #[inline]
329    fn value_type(&self) -> EnumSet<ValueType> {
330        EnumSet::only(ValueType::Object)
331    }
332
333    #[inline]
334    fn process_value<P>(
335        &mut self,
336        meta: &mut Meta,
337        processor: &mut P,
338        state: &ProcessingState<'_>,
339    ) -> ProcessingResult
340    where
341        P: Processor,
342    {
343        processor.process_attributes(self, meta, state)
344    }
345
346    fn process_child_values<P>(
347        &mut self,
348        processor: &mut P,
349        state: &ProcessingState<'_>,
350    ) -> ProcessingResult
351    where
352        P: Processor,
353    {
354        let enter_state = state.enter_nothing(Some(Cow::Borrowed(state.attrs())));
355        self.0.process_child_values(processor, &enter_state)
356    }
357}