1use relay_conventions::attributes::*;
2use relay_event_schema::protocol::{Attributes, SpanKind};
3use relay_protocol::Annotated;
4
5pub fn derive_op_for_v2_span(attributes: &Annotated<Attributes>) -> String {
12 let op = String::from("default");
14
15 let Some(attributes) = attributes.value() else {
16 return op;
17 };
18
19 if attributes.contains_key(HTTP__REQUEST__METHOD) {
20 let kind = attributes.get_value(SENTRY__KIND).and_then(|v| v.as_str());
21 return match kind {
22 Some(kind) if kind == SpanKind::Client.as_str() => String::from("http.client"),
23 Some(kind) if kind == SpanKind::Server.as_str() => String::from("http.server"),
24 _ => {
25 if attributes.contains_key(SENTRY__HTTP__PREFETCH) {
26 String::from("http.prefetch")
27 } else {
28 String::from("http")
29 }
30 }
31 };
32 }
33
34 if attributes.contains_key(DB__SYSTEM__NAME) {
35 return String::from("db");
36 }
37
38 if let Some(operation) = attributes
39 .get_value(GEN_AI__OPERATION__NAME)
40 .and_then(|v| v.as_str())
41 {
42 return format!("gen_ai.{operation}");
43 }
44
45 if attributes.contains_key(GEN_AI__PROVIDER__NAME) {
46 return String::from("gen_ai");
47 }
48
49 if attributes.contains_key(RPC__SERVICE) {
50 return String::from("rpc");
51 }
52
53 if attributes.contains_key(MESSAGING__SYSTEM) {
54 return String::from("message");
55 }
56
57 if let Some(faas_trigger) = attributes.get_value(FAAS__TRIGGER).and_then(|v| v.as_str()) {
58 return faas_trigger.to_owned();
59 }
60
61 op
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 fn op_for(pairs: &[(&str, &str)]) -> String {
69 let attributes = Attributes::from_iter(
70 pairs
71 .iter()
72 .map(|(k, v)| ((*k).to_owned(), Annotated::new((*v).to_owned().into()))),
73 );
74 derive_op_for_v2_span(&Annotated::new(attributes))
75 }
76
77 #[test]
78 fn test_gen_ai_op_operation_name_takes_precedence_over_provider() {
79 assert_eq!(
80 op_for(&[
81 (GEN_AI__OPERATION__NAME, "chat"),
82 (GEN_AI__PROVIDER__NAME, "openai"),
83 ]),
84 "gen_ai.chat"
85 );
86 assert_eq!(
87 op_for(&[
88 (GEN_AI__OPERATION__NAME, "invoke_agent"),
89 (GEN_AI__PROVIDER__NAME, "openai"),
90 ]),
91 "gen_ai.invoke_agent"
92 );
93 }
94
95 #[test]
96 fn test_gen_ai_op_execute_tool_without_provider() {
97 assert_eq!(
98 op_for(&[(GEN_AI__OPERATION__NAME, "execute_tool")]),
99 "gen_ai.execute_tool"
100 );
101 }
102
103 #[test]
104 fn test_gen_ai_op_falls_back_to_provider() {
105 assert_eq!(op_for(&[(GEN_AI__PROVIDER__NAME, "openai")]), "gen_ai");
106 }
107}