relay_event_schema/protocol/
breakdowns.rs1use std::ops::{Deref, DerefMut};
2
3use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, Object, Value};
4
5use crate::processor::ProcessValue;
6use crate::protocol::Measurements;
7
8#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
13pub struct Breakdowns(pub Object<Measurements>);
14
15impl Breakdowns {
16 pub fn is_valid_breakdown_name(name: &str) -> bool {
17 !name.is_empty()
18 && name.starts_with(|c: char| c.is_ascii_alphabetic())
19 && name
20 .chars()
21 .all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '.'))
22 }
23}
24
25impl FromValue for Breakdowns {
26 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
27 let mut processing_errors = Vec::new();
28
29 let mut breakdowns = Object::from_value(value).map_value(|breakdowns| {
30 let breakdowns = breakdowns
31 .into_iter()
32 .filter_map(|(name, object)| {
33 let name = name.trim();
34
35 if Breakdowns::is_valid_breakdown_name(name) {
36 return Some((name.into(), object));
37 } else {
38 processing_errors.push(Error::invalid(format!(
39 "breakdown name '{name}' can contain only characters a-z0-9._"
40 )));
41 }
42
43 None
44 })
45 .collect();
46
47 Self(breakdowns)
48 });
49
50 for error in processing_errors {
51 breakdowns.meta_mut().add_error(error);
52 }
53
54 breakdowns
55 }
56}
57
58impl Deref for Breakdowns {
59 type Target = Object<Measurements>;
60
61 fn deref(&self) -> &Self::Target {
62 &self.0
63 }
64}
65
66impl DerefMut for Breakdowns {
67 fn deref_mut(&mut self) -> &mut Self::Target {
68 &mut self.0
69 }
70}