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 = 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
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
110pub fn attribute_pii_from_conventions(state: &ProcessingState) -> Pii {
121 for key in state.keys() {
122 let Some(info) = relay_conventions::attribute_info(key) else {
123 continue;
124 };
125
126 return match info.pii {
127 relay_conventions::Pii::True => Pii::True,
128 relay_conventions::Pii::False => Pii::False,
129 relay_conventions::Pii::Maybe => Pii::Maybe,
130 };
131 }
132
133 Pii::True
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub enum AttributeType {
138 Boolean,
142 Integer,
146 Double,
150 String,
154 Array,
158 Unknown(String),
162}
163
164impl ProcessValue for AttributeType {}
165
166impl AttributeType {
167 pub fn as_str(&self) -> &str {
168 match self {
169 Self::Boolean => "boolean",
170 Self::Integer => "integer",
171 Self::Double => "double",
172 Self::String => "string",
173 Self::Array => "array",
174 Self::Unknown(value) => value,
175 }
176 }
177
178 pub fn unknown_string() -> String {
179 "unknown".to_owned()
180 }
181}
182
183impl fmt::Display for AttributeType {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "{}", self.as_str())
186 }
187}
188
189impl From<String> for AttributeType {
190 fn from(value: String) -> Self {
191 match value.as_str() {
192 "boolean" => Self::Boolean,
193 "integer" => Self::Integer,
194 "double" => Self::Double,
195 "string" => Self::String,
196 "array" => Self::Array,
197 _ => Self::Unknown(value),
198 }
199 }
200}
201
202impl Empty for AttributeType {
203 #[inline]
204 fn is_empty(&self) -> bool {
205 false
206 }
207}
208
209impl FromValue for AttributeType {
210 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
211 match String::from_value(value) {
212 Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
213 Annotated(None, meta) => Annotated(None, meta),
214 }
215 }
216}
217
218impl IntoValue for AttributeType {
219 fn into_value(self) -> Value
220 where
221 Self: Sized,
222 {
223 Value::String(match self {
224 Self::Unknown(s) => s,
225 s => s.to_string(),
226 })
227 }
228
229 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
230 where
231 Self: Sized,
232 S: serde::Serializer,
233 {
234 serde::ser::Serialize::serialize(self.as_str(), s)
235 }
236}
237
238#[derive(Debug, Clone, Default, PartialEq, Empty, FromValue, IntoValue)]
240pub struct Attributes(pub Object<Attribute>);
241
242impl Attributes {
243 pub fn new() -> Self {
245 Self(Object::new())
246 }
247
248 pub fn get_value<Q>(&self, key: &Q) -> Option<&Value>
250 where
251 String: Borrow<Q>,
252 Q: Ord + ?Sized,
253 {
254 self.get_annotated_value(key)?.value()
255 }
256
257 pub fn get_attribute<Q>(&self, key: &Q) -> Option<&Attribute>
259 where
260 String: Borrow<Q>,
261 Q: Ord + ?Sized,
262 {
263 self.0.get(key)?.value()
264 }
265
266 pub fn get_annotated_value<Q>(&self, key: &Q) -> Option<&Annotated<Value>>
268 where
269 String: Borrow<Q>,
270 Q: Ord + ?Sized,
271 {
272 Some(&self.0.get(key)?.value()?.value.value)
273 }
274
275 pub fn insert<K: Into<String>, V: Into<AttributeValue>>(&mut self, key: K, value: V) {
277 fn inner(slf: &mut Attributes, key: String, value: AttributeValue) {
278 let attribute = Annotated::new(Attribute {
279 value,
280 other: Default::default(),
281 });
282 slf.0.insert(key, attribute);
283 }
284 let value = value.into();
285 if !value.value.is_empty() {
286 inner(self, key.into(), value);
287 }
288 }
289
290 pub fn insert_if_missing<F, V>(&mut self, key: &str, value: F)
292 where
293 F: FnOnce() -> V,
294 V: Into<AttributeValue>,
295 {
296 if !self.0.contains_key(key) {
297 self.insert(key.to_owned(), value());
298 }
299 }
300
301 pub fn contains_key<Q>(&self, key: &Q) -> bool
303 where
304 String: Borrow<Q>,
305 Q: Ord + ?Sized,
306 {
307 self.0.contains_key(key)
308 }
309
310 pub fn remove<Q>(&mut self, key: &Q) -> Option<Annotated<Attribute>>
312 where
313 String: Borrow<Q>,
314 Q: Ord + ?Sized,
315 {
316 self.0.remove(key)
317 }
318}
319
320impl IntoIterator for Attributes {
321 type Item = (String, Annotated<Attribute>);
322
323 type IntoIter = std::collections::btree_map::IntoIter<String, Annotated<Attribute>>;
324
325 fn into_iter(self) -> Self::IntoIter {
326 self.0.into_iter()
327 }
328}
329
330impl FromIterator<(String, Annotated<Attribute>)> for Attributes {
331 fn from_iter<T: IntoIterator<Item = (String, Annotated<Attribute>)>>(iter: T) -> Self {
332 Self(Object::from_iter(iter))
333 }
334}
335
336impl<const N: usize> From<[(String, Annotated<Attribute>); N]> for Attributes {
337 fn from(value: [(String, Annotated<Attribute>); N]) -> Self {
338 value.into_iter().collect()
339 }
340}
341
342impl ProcessValue for Attributes {
346 #[inline]
347 fn value_type(&self) -> EnumSet<ValueType> {
348 EnumSet::only(ValueType::Object)
349 }
350
351 #[inline]
352 fn process_value<P>(
353 &mut self,
354 meta: &mut Meta,
355 processor: &mut P,
356 state: &ProcessingState<'_>,
357 ) -> ProcessingResult
358 where
359 P: Processor,
360 {
361 processor.process_attributes(self, meta, state)
362 }
363
364 fn process_child_values<P>(
365 &mut self,
366 processor: &mut P,
367 state: &ProcessingState<'_>,
368 ) -> ProcessingResult
369 where
370 P: Processor,
371 {
372 let enter_state = state.enter_nothing(Some(Cow::Borrowed(state.attrs())));
373 self.0.process_child_values(processor, &enter_state)
374 }
375}