1use relay_conventions::name_for_op_and_attributes;
2use relay_event_schema::protocol::Span;
3use relay_protocol::{Getter, GetterIter, Val};
4
5pub fn name_for_span(span: &Span) -> Option<String> {
7 let op = span.op.value()?;
8
9 let Some(data) = span.data.value() else {
10 return Some(name_for_op_and_attributes(op, &EmptyGetter {}));
11 };
12
13 Some(name_for_op_and_attributes(
14 op,
15 &EscapedGetter(data),
19 ))
20}
21
22struct EmptyGetter {}
23
24impl Getter for EmptyGetter {
25 fn get_value(&self, _path: &str) -> Option<Val<'_>> {
26 None
27 }
28}
29
30struct EscapedGetter<'a, T: Getter>(&'a T);
31
32impl<'a, T: Getter> Getter for EscapedGetter<'a, T> {
33 fn get_value(&self, path: &str) -> Option<Val<'_>> {
34 self.0.get_value(&path.replace(".", "\\."))
35 }
36
37 fn get_iter(&self, path: &str) -> Option<GetterIter<'_>> {
38 self.0.get_iter(&path.replace(".", "\\."))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use relay_event_schema::protocol::SpanData;
45 use relay_protocol::{Annotated, Object, Value};
46
47 use super::*;
48
49 #[test]
50 fn falls_back_to_op_when_no_templates_defined() {
51 let span = Span {
52 op: Annotated::new("foo".to_owned()),
53 ..Default::default()
54 };
55 assert_eq!(name_for_span(&span), Some("foo".to_owned()));
56 }
57
58 #[test]
59 fn uses_the_first_matching_template() {
60 let span = Span {
61 op: Annotated::new("db".to_owned()),
62 data: Annotated::new(SpanData {
63 other: Object::from([
64 (
65 "db.query.summary".into(),
66 Value::String("SELECT users".into()).into(),
67 ),
68 (
69 "db.operation.name".into(),
70 Value::String("INSERT".into()).into(),
71 ),
72 (
73 "db.collection.name".into(),
74 Value::String("widgets".into()).into(),
75 ),
76 ]),
77 ..Default::default()
78 }),
79 ..Default::default()
80 };
81 assert_eq!(name_for_span(&span), Some("SELECT users".to_owned()));
82 }
83
84 #[test]
85 fn uses_fallback_templates_when_data_is_missing() {
86 let span = Span {
87 op: Annotated::new("db".to_owned()),
88 data: Annotated::new(SpanData {
89 other: Object::from([
90 (
91 "db.operation.name".into(),
92 Value::String("INSERT".into()).into(),
93 ),
94 (
95 "db.collection.name".into(),
96 Value::String("widgets".into()).into(),
97 ),
98 ]),
99 ..Default::default()
100 }),
101 ..Default::default()
102 };
103 assert_eq!(name_for_span(&span), Some("INSERT widgets".to_owned()));
104 }
105
106 #[test]
107 fn falls_back_to_hardcoded_name_when_nothing_matches() {
108 let span = Span {
109 op: Annotated::new("db".to_owned()),
110 ..Default::default()
111 };
112 assert_eq!(name_for_span(&span), Some("Database operation".to_owned()));
113 }
114}