relay_event_normalization/
schema.rs1use relay_event_schema::processor::{
2 ProcessValue, ProcessingAction, ProcessingResult, ProcessingState, Processor,
3};
4use relay_protocol::{Array, Empty, Error, ErrorKind, Meta, Object};
5
6pub struct SchemaProcessor;
8
9impl Processor for SchemaProcessor {
10 fn process_string(
11 &mut self,
12 value: &mut String,
13 meta: &mut Meta,
14 state: &ProcessingState<'_>,
15 ) -> ProcessingResult {
16 value_trim_whitespace(value, meta, state);
17 verify_value_nonempty_string(value, meta, state)?;
18 verify_value_characters(value, meta, state)?;
19 Ok(())
20 }
21
22 fn process_array<T>(
23 &mut self,
24 value: &mut Array<T>,
25 meta: &mut Meta,
26 state: &ProcessingState<'_>,
27 ) -> ProcessingResult
28 where
29 T: ProcessValue,
30 {
31 value.process_child_values(self, state)?;
32 verify_value_nonempty(value, meta, state)?;
33 Ok(())
34 }
35
36 fn process_object<T>(
37 &mut self,
38 value: &mut Object<T>,
39 meta: &mut Meta,
40 state: &ProcessingState<'_>,
41 ) -> ProcessingResult
42 where
43 T: ProcessValue,
44 {
45 value.process_child_values(self, state)?;
46 verify_value_nonempty(value, meta, state)?;
47 Ok(())
48 }
49
50 fn before_process<T: ProcessValue>(
51 &mut self,
52 value: Option<&T>,
53 meta: &mut Meta,
54 state: &ProcessingState<'_>,
55 ) -> ProcessingResult {
56 if value.is_none() && state.attrs().required && !meta.has_errors() {
57 meta.add_error(ErrorKind::MissingAttribute);
58 }
59
60 Ok(())
61 }
62}
63
64fn value_trim_whitespace(value: &mut String, _meta: &mut Meta, state: &ProcessingState<'_>) {
65 if state.attrs().trim_whitespace {
66 let new_value = value.trim().to_owned();
67 value.clear();
68 value.push_str(&new_value);
69 }
70}
71
72fn verify_value_nonempty<T>(
73 value: &T,
74 meta: &mut Meta,
75 state: &ProcessingState<'_>,
76) -> ProcessingResult
77where
78 T: Empty,
79{
80 if state.attrs().nonempty && value.is_empty() {
81 meta.add_error(Error::nonempty());
82 Err(ProcessingAction::DeleteValueHard)
83 } else {
84 Ok(())
85 }
86}
87
88fn verify_value_nonempty_string<T>(
89 value: &T,
90 meta: &mut Meta,
91 state: &ProcessingState<'_>,
92) -> ProcessingResult
93where
94 T: Empty,
95{
96 if state.attrs().nonempty && value.is_empty() {
97 meta.add_error(Error::nonempty_string());
98 Err(ProcessingAction::DeleteValueHard)
99 } else {
100 Ok(())
101 }
102}
103
104fn verify_value_characters(
105 value: &str,
106 meta: &mut Meta,
107 state: &ProcessingState<'_>,
108) -> ProcessingResult {
109 if let Some(ref character_set) = state.attrs().characters {
110 for c in value.chars() {
111 if !(character_set.char_is_valid)(c) {
112 meta.add_error(Error::invalid(format!("invalid character {c:?}")));
113 return Err(ProcessingAction::DeleteValueSoft);
114 }
115 }
116 }
117
118 Ok(())
119}
120
121#[cfg(test)]
122mod tests {
123 use relay_event_schema::processor;
124 use relay_event_schema::protocol::{
125 CError, ClientSdkInfo, Event, MachException, Mechanism, MechanismMeta, PosixSignal,
126 RawStacktrace, User,
127 };
128 use relay_protocol::Annotated;
129 use similar_asserts::assert_eq;
130
131 use super::*;
132
133 #[test]
176 fn test_invalid_email() {
177 let mut user = Annotated::new(User {
178 email: Annotated::new("bananabread".to_owned()),
179 ..Default::default()
180 });
181
182 let expected = user.clone();
183 processor::process_value(&mut user, &mut SchemaProcessor, ProcessingState::root()).unwrap();
184
185 assert_eq!(user, expected);
186 }
187
188 #[test]
189 fn test_client_sdk_missing_attribute() {
190 let mut info = Annotated::new(ClientSdkInfo {
191 name: Annotated::new("sentry.rust".to_string()),
192 ..Default::default()
193 });
194
195 processor::process_value(&mut info, &mut SchemaProcessor, ProcessingState::root()).unwrap();
196
197 let expected = Annotated::new(ClientSdkInfo {
198 name: Annotated::new("sentry.rust".to_string()),
199 version: Annotated::from_error(ErrorKind::MissingAttribute, None),
200 ..Default::default()
201 });
202
203 assert_eq!(info, expected);
204 }
205
206 #[test]
207 fn test_mechanism_missing_attributes() {
208 let mut mechanism = Annotated::new(Mechanism {
209 ty: Annotated::new("mytype".to_string()),
210 meta: Annotated::new(MechanismMeta {
211 errno: Annotated::new(CError {
212 name: Annotated::new("ENOENT".to_string()),
213 ..Default::default()
214 }),
215 mach_exception: Annotated::new(MachException {
216 name: Annotated::new("EXC_BAD_ACCESS".to_string()),
217 ..Default::default()
218 }),
219 signal: Annotated::new(PosixSignal {
220 name: Annotated::new("SIGSEGV".to_string()),
221 ..Default::default()
222 }),
223 ..Default::default()
224 }),
225 ..Default::default()
226 });
227
228 processor::process_value(
229 &mut mechanism,
230 &mut SchemaProcessor,
231 ProcessingState::root(),
232 )
233 .unwrap();
234
235 let expected = Annotated::new(Mechanism {
236 ty: Annotated::new("mytype".to_string()),
237 meta: Annotated::new(MechanismMeta {
238 errno: Annotated::new(CError {
239 number: Annotated::empty(),
240 name: Annotated::new("ENOENT".to_string()),
241 }),
242 mach_exception: Annotated::new(MachException {
243 ty: Annotated::empty(),
244 code: Annotated::empty(),
245 subcode: Annotated::empty(),
246 name: Annotated::new("EXC_BAD_ACCESS".to_string()),
247 }),
248 signal: Annotated::new(PosixSignal {
249 number: Annotated::empty(),
250 code: Annotated::empty(),
251 name: Annotated::new("SIGSEGV".to_string()),
252 code_name: Annotated::empty(),
253 }),
254 ..Default::default()
255 }),
256 ..Default::default()
257 });
258
259 assert_eq!(mechanism, expected);
260 }
261
262 #[test]
263 fn test_stacktrace_missing_attribute() {
264 let mut stack = Annotated::new(RawStacktrace::default());
265
266 processor::process_value(&mut stack, &mut SchemaProcessor, ProcessingState::root())
267 .unwrap();
268
269 let expected = Annotated::new(RawStacktrace {
270 frames: Annotated::from_error(ErrorKind::MissingAttribute, None),
271 ..Default::default()
272 });
273
274 assert_eq!(stack, expected);
275 }
276
277 #[test]
278 fn test_newlines_release() {
279 let mut event = Annotated::new(Event {
280 release: Annotated::new("42\n".to_string().into()),
281 ..Default::default()
282 });
283
284 processor::process_value(&mut event, &mut SchemaProcessor, ProcessingState::root())
285 .unwrap();
286
287 let expected = Annotated::new(Event {
288 release: Annotated::new("42".to_string().into()),
289 ..Default::default()
290 });
291
292 assert_eq!(expected, event);
293 }
294}