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 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
95pub 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,
127 Integer,
131 Double,
135 String,
139 Array,
143 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#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
225pub struct Attributes(pub Object<Attribute>);
226
227impl Attributes {
228 pub fn new() -> Self {
230 Self(Object::new())
231 }
232
233 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 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 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 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 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 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 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}