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    Boolean,
124    Integer,
125    Double,
126    String,
127    Unknown(String),
128}
129
130impl ProcessValue for AttributeType {}
131
132impl AttributeType {
133    pub fn as_str(&self) -> &str {
134        match self {
135            Self::Boolean => "boolean",
136            Self::Integer => "integer",
137            Self::Double => "double",
138            Self::String => "string",
139            Self::Unknown(value) => value,
140        }
141    }
142
143    pub fn unknown_string() -> String {
144        "unknown".to_owned()
145    }
146}
147
148impl fmt::Display for AttributeType {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        write!(f, "{}", self.as_str())
151    }
152}
153
154impl From<String> for AttributeType {
155    fn from(value: String) -> Self {
156        match value.as_str() {
157            "boolean" => Self::Boolean,
158            "integer" => Self::Integer,
159            "double" => Self::Double,
160            "string" => Self::String,
161            _ => Self::Unknown(value),
162        }
163    }
164}
165
166impl Empty for AttributeType {
167    #[inline]
168    fn is_empty(&self) -> bool {
169        false
170    }
171}
172
173impl FromValue for AttributeType {
174    fn from_value(value: Annotated<Value>) -> Annotated<Self> {
175        match String::from_value(value) {
176            Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
177            Annotated(None, meta) => Annotated(None, meta),
178        }
179    }
180}
181
182impl IntoValue for AttributeType {
183    fn into_value(self) -> Value
184    where
185        Self: Sized,
186    {
187        Value::String(match self {
188            Self::Unknown(s) => s,
189            s => s.to_string(),
190        })
191    }
192
193    fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
194    where
195        Self: Sized,
196        S: serde::Serializer,
197    {
198        serde::ser::Serialize::serialize(self.as_str(), s)
199    }
200}
201
202/// Wrapper struct around a collection of attributes with some convenience methods.
203#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
204pub struct Attributes(pub Object<Attribute>);
205
206impl Attributes {
207    /// Creates an empty collection of attributes.
208    pub fn new() -> Self {
209        Self(Object::new())
210    }
211
212    /// Returns the value of the attribute with the given key.
213    pub fn get_value<Q>(&self, key: &Q) -> Option<&Value>
214    where
215        String: Borrow<Q>,
216        Q: Ord + ?Sized,
217    {
218        self.get_annotated_value(key)?.value()
219    }
220
221    /// Returns the attribute with the given key.
222    pub fn get_attribute<Q>(&self, key: &Q) -> Option<&Attribute>
223    where
224        String: Borrow<Q>,
225        Q: Ord + ?Sized,
226    {
227        self.0.get(key)?.value()
228    }
229
230    /// Returns the attribute value as annotated.
231    pub fn get_annotated_value<Q>(&self, key: &Q) -> Option<&Annotated<Value>>
232    where
233        String: Borrow<Q>,
234        Q: Ord + ?Sized,
235    {
236        Some(&self.0.get(key)?.value()?.value.value)
237    }
238
239    /// Inserts an attribute with the given value into this collection.
240    pub fn insert<K: Into<String>, V: Into<AttributeValue>>(&mut self, key: K, value: V) {
241        fn inner(slf: &mut Attributes, key: String, value: AttributeValue) {
242            let attribute = Annotated::new(Attribute {
243                value,
244                other: Default::default(),
245            });
246            slf.0.insert(key, attribute);
247        }
248        let value = value.into();
249        if !value.value.is_empty() {
250            inner(self, key.into(), value);
251        }
252    }
253
254    /// Inserts an attribute with the given value if it was not already present.
255    pub fn insert_if_missing<F, V>(&mut self, key: &str, value: F)
256    where
257        F: FnOnce() -> V,
258        V: Into<AttributeValue>,
259    {
260        if !self.0.contains_key(key) {
261            self.insert(key.to_owned(), value());
262        }
263    }
264
265    /// Checks whether this collection contains an attribute with the given key.
266    pub fn contains_key<Q>(&self, key: &Q) -> bool
267    where
268        String: Borrow<Q>,
269        Q: Ord + ?Sized,
270    {
271        self.0.contains_key(key)
272    }
273
274    /// Removes an attribute from this collection.
275    pub fn remove<Q>(&mut self, key: &Q) -> Option<Annotated<Attribute>>
276    where
277        String: Borrow<Q>,
278        Q: Ord + ?Sized,
279    {
280        self.0.remove(key)
281    }
282}
283
284impl IntoIterator for Attributes {
285    type Item = (String, Annotated<Attribute>);
286
287    type IntoIter = std::collections::btree_map::IntoIter<String, Annotated<Attribute>>;
288
289    fn into_iter(self) -> Self::IntoIter {
290        self.0.into_iter()
291    }
292}
293
294impl FromIterator<(String, Annotated<Attribute>)> for Attributes {
295    fn from_iter<T: IntoIterator<Item = (String, Annotated<Attribute>)>>(iter: T) -> Self {
296        Self(Object::from_iter(iter))
297    }
298}
299
300impl<const N: usize> From<[(String, Annotated<Attribute>); N]> for Attributes {
301    fn from(value: [(String, Annotated<Attribute>); N]) -> Self {
302        value.into_iter().collect()
303    }
304}
305
306impl ProcessValue for Attributes {
307    #[inline]
308    fn value_type(&self) -> EnumSet<ValueType> {
309        EnumSet::only(ValueType::Object)
310    }
311
312    #[inline]
313    fn process_value<P>(
314        &mut self,
315        meta: &mut Meta,
316        processor: &mut P,
317        state: &ProcessingState<'_>,
318    ) -> ProcessingResult
319    where
320        P: Processor,
321    {
322        processor.process_attributes(self, meta, state)
323    }
324
325    fn process_child_values<P>(
326        &mut self,
327        processor: &mut P,
328        state: &ProcessingState<'_>,
329    ) -> ProcessingResult
330    where
331        P: Processor,
332    {
333        let enter_state = state.enter_nothing(Some(Cow::Borrowed(state.attrs())));
334        self.0.process_child_values(processor, &enter_state)
335    }
336}