sentinel_core/normalize/
mod.rs1pub mod http;
4#[cfg(test)]
5mod metamorphic;
6pub mod sql;
7
8use std::sync::Arc;
9
10use crate::event::{EventType, MAX_ID_LENGTH, SpanEvent, sanitize_id};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct NormalizedEvent {
18 pub event: SpanEvent,
19 pub template: Arc<str>,
20 pub params: Vec<String>,
21}
22
23#[must_use]
27pub fn normalize(mut event: SpanEvent) -> NormalizedEvent {
28 if event.trace_id.len() > MAX_ID_LENGTH {
30 event.trace_id = sanitize_id(&event.trace_id);
31 }
32 if event.span_id.len() > MAX_ID_LENGTH {
33 event.span_id = sanitize_id(&event.span_id);
34 }
35 if let Some(ref pid) = event.parent_span_id
36 && pid.len() > MAX_ID_LENGTH
37 {
38 event.parent_span_id = Some(sanitize_id(pid));
39 }
40 match event.event_type {
41 EventType::Sql => {
42 let result = sql::normalize_sql(&event.target);
43 NormalizedEvent {
44 event,
45 template: Arc::from(result.template),
46 params: result.params,
47 }
48 }
49 EventType::HttpOut => {
50 let result = http::normalize_http(&event.operation, &event.target);
51 NormalizedEvent {
52 event,
53 template: Arc::from(result.template),
54 params: result.params,
55 }
56 }
57 }
58}
59
60#[must_use]
62pub fn normalize_all(events: Vec<SpanEvent>) -> Vec<NormalizedEvent> {
63 events.into_iter().map(normalize).collect()
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::event::{EventSource, EventType, SpanEvent};
70
71 fn make_sql_event(target: &str) -> SpanEvent {
72 SpanEvent {
73 timestamp: "2025-07-10T14:32:01.123Z".to_string(),
74 trace_id: "trace-1".to_string(),
75 span_id: "span-1".to_string(),
76 parent_span_id: None,
77 service: Arc::from("test"),
78 cloud_region: None,
79 event_type: EventType::Sql,
80 operation: "SELECT".to_string(),
81 target: target.to_string(),
82 duration_us: 100,
83 source: EventSource {
84 endpoint: "GET /test".to_string(),
85 method: "Test::test".to_string(),
86 },
87 status_code: None,
88 response_size_bytes: None,
89 code_function: None,
90 code_filepath: None,
91 code_lineno: None,
92 code_namespace: None,
93 instrumentation_scopes: Vec::new(),
94 }
95 }
96
97 fn make_http_event(method: &str, target: &str) -> SpanEvent {
98 SpanEvent {
99 timestamp: "2025-07-10T14:32:01.123Z".to_string(),
100 trace_id: "trace-1".to_string(),
101 span_id: "span-1".to_string(),
102 parent_span_id: None,
103 service: Arc::from("test"),
104 cloud_region: None,
105 event_type: EventType::HttpOut,
106 operation: method.to_string(),
107 target: target.to_string(),
108 duration_us: 100,
109 source: EventSource {
110 endpoint: "GET /test".to_string(),
111 method: "Test::test".to_string(),
112 },
113 status_code: Some(200),
114 response_size_bytes: None,
115 code_function: None,
116 code_filepath: None,
117 code_lineno: None,
118 code_namespace: None,
119 instrumentation_scopes: Vec::new(),
120 }
121 }
122
123 #[test]
124 fn normalize_dispatches_sql() {
125 let event = make_sql_event("SELECT * FROM users WHERE id = 42");
126 let normalized = normalize(event);
127 assert_eq!(&*normalized.template, "SELECT * FROM users WHERE id = ?");
128 assert_eq!(normalized.params, vec!["42"]);
129 }
130
131 #[test]
132 fn normalize_dispatches_http() {
133 let event = make_http_event("GET", "/api/users/42");
134 let normalized = normalize(event);
135 assert_eq!(&*normalized.template, "GET /api/users/{id}");
136 }
137
138 #[test]
139 fn normalize_all_processes_batch() {
140 let events = vec![
141 make_sql_event("SELECT 1"),
142 make_http_event("POST", "/api/orders/99/submit"),
143 ];
144 let normalized = normalize_all(events);
145 assert_eq!(normalized.len(), 2);
146 assert_eq!(&*normalized[0].template, "SELECT ?");
147 assert_eq!(&*normalized[1].template, "POST /api/orders/{id}/submit");
148 }
149
150 #[test]
151 fn normalize_truncates_oversized_trace_id() {
152 let mut event = make_sql_event("SELECT 1");
153 event.trace_id = "x".repeat(200);
154 event.span_id = "y".repeat(200);
155 event.parent_span_id = Some("z".repeat(200));
156 let normalized = normalize(event);
157 assert_eq!(normalized.event.trace_id.len(), MAX_ID_LENGTH);
158 assert_eq!(normalized.event.span_id.len(), MAX_ID_LENGTH);
159 assert_eq!(
160 normalized.event.parent_span_id.unwrap().len(),
161 MAX_ID_LENGTH
162 );
163 }
164
165 #[test]
166 fn normalize_preserves_normal_ids() {
167 let event = make_sql_event("SELECT 1");
168 let original_trace = event.trace_id.clone();
169 let normalized = normalize(event);
170 assert_eq!(normalized.event.trace_id, original_trace);
171 }
172}