1use std::ops::Range;
2
3use chrono::{DateTime, Duration, Utc};
4use relay_base_schema::events::EventType;
5use relay_common::time::UnixTimestamp;
6use relay_event_schema::processor::{
7 self, ProcessingAction, ProcessingResult, ProcessingState, Processor,
8};
9use relay_event_schema::protocol::{Event, Span, Timestamp, TraceContext};
10use relay_protocol::{Annotated, ErrorKind, Meta};
11
12use crate::{ClockDriftProcessor, TimestampProcessor};
13
14#[derive(Debug, Default)]
16pub struct EventValidationConfig {
17 pub received_at: Option<DateTime<Utc>>,
21
22 pub max_secs_in_past: Option<i64>,
26
27 pub max_secs_in_future: Option<i64>,
31
32 pub transaction_timestamp_range: Option<Range<UnixTimestamp>>,
37
38 pub is_validated: bool,
44}
45
46pub fn validate_event(
54 event: &mut Annotated<Event>,
55 config: &EventValidationConfig,
56) -> ProcessingResult {
57 if config.is_validated {
58 return Ok(());
59 }
60
61 let Annotated(Some(event), meta) = event else {
62 return Ok(());
63 };
64
65 normalize_timestamps(
68 event,
69 meta,
70 config.received_at,
71 config.max_secs_in_past,
72 config.max_secs_in_future,
73 );
74 TimestampProcessor.process_event(event, meta, ProcessingState::root())?;
75
76 if event.ty.value() == Some(&EventType::Transaction) {
77 validate_transaction_timestamps(event, config)?;
78 validate_trace_context(event)?;
79 end_all_spans(event);
82 validate_spans(event, None)?;
83 }
84
85 Ok(())
86}
87
88fn normalize_timestamps(
90 event: &mut Event,
91 meta: &mut Meta,
92 received_at: Option<DateTime<Utc>>,
93 max_secs_in_past: Option<i64>,
94 max_secs_in_future: Option<i64>,
95) {
96 let received_at = received_at.unwrap_or_else(Utc::now);
97
98 let mut sent_at = None;
99 let mut error_kind = ErrorKind::ClockDrift;
100
101 let _ = processor::apply(&mut event.timestamp, |timestamp, _meta| {
102 if let Some(secs) = max_secs_in_future
103 && *timestamp > received_at + Duration::seconds(secs)
104 {
105 error_kind = ErrorKind::FutureTimestamp;
106 sent_at = Some(*timestamp);
107 return Ok(());
108 }
109
110 if let Some(secs) = max_secs_in_past
111 && *timestamp < received_at - Duration::seconds(secs)
112 {
113 error_kind = ErrorKind::PastTimestamp;
114 sent_at = Some(*timestamp);
115 return Ok(());
116 }
117
118 Ok(())
119 });
120
121 let _ = ClockDriftProcessor::new(sent_at.map(|ts| ts.into_inner()), received_at)
122 .error_kind(error_kind)
123 .process_event(event, meta, ProcessingState::root());
124
125 event.received = Annotated::new(received_at.into());
127
128 if event.timestamp.value().is_none() {
129 event.timestamp.set_value(Some(received_at.into()));
130 }
131
132 let _ = processor::apply(&mut event.time_spent, |time_spent, _| {
133 validate_bounded_integer_field(*time_spent)
134 });
135}
136
137fn validate_transaction_timestamps(
139 transaction_event: &Event,
140 config: &EventValidationConfig,
141) -> ProcessingResult {
142 match (
143 transaction_event.start_timestamp.value(),
144 transaction_event.timestamp.value(),
145 ) {
146 (Some(start), Some(end)) => {
147 validate_timestamps(start, end, config.transaction_timestamp_range.as_ref())?;
148 Ok(())
149 }
150 (_, None) => Err(ProcessingAction::InvalidTransaction(
151 "timestamp hard-required for transaction events",
152 )),
153 (None, _) => Err(ProcessingAction::InvalidTransaction(
155 "start_timestamp hard-required for transaction events",
156 )),
157 }
158}
159
160fn validate_timestamps(
162 start: &Timestamp,
163 end: &Timestamp,
164 valid_range: Option<&Range<UnixTimestamp>>,
165) -> ProcessingResult {
166 if end < start {
167 return Err(ProcessingAction::InvalidTransaction(
168 "end timestamp is smaller than start timestamp",
169 ));
170 }
171
172 let Some(range) = valid_range else {
173 return Ok(());
174 };
175
176 let Some(timestamp) = UnixTimestamp::from_datetime(end.into_inner()) else {
177 return Err(ProcessingAction::InvalidTransaction(
178 "invalid unix timestamp",
179 ));
180 };
181 if !range.contains(×tamp) {
182 return Err(ProcessingAction::InvalidTransaction(
183 "timestamp is out of the valid range for metrics",
184 ));
185 }
186
187 Ok(())
188}
189
190fn validate_trace_context(transaction: &Event) -> ProcessingResult {
198 let Some(trace_context) = transaction.context::<TraceContext>() else {
199 return Err(ProcessingAction::InvalidTransaction(
200 "missing valid trace context",
201 ));
202 };
203
204 if trace_context.trace_id.value().is_none() {
205 return Err(ProcessingAction::InvalidTransaction(
206 "trace context is missing trace_id",
207 ));
208 }
209
210 if trace_context.span_id.value().is_none() {
211 return Err(ProcessingAction::InvalidTransaction(
212 "trace context is missing span_id",
213 ));
214 }
215
216 Ok(())
217}
218
219fn end_all_spans(event: &mut Event) {
221 let spans = event.spans.value_mut().get_or_insert_with(Vec::new);
222 for span in spans {
223 if let Some(span) = span.value_mut()
224 && span.timestamp.value().is_none()
225 {
226 span.timestamp.set_value(event.timestamp.value().cloned());
228 span.status = Annotated::new(relay_base_schema::spans::SpanStatus::DeadlineExceeded);
229 }
230 }
231}
232
233fn validate_spans(
238 transaction: &Event,
239 timestamp_range: Option<&Range<UnixTimestamp>>,
240) -> ProcessingResult {
241 let Some(spans) = transaction.spans.value() else {
242 return Ok(());
243 };
244
245 for span in spans {
246 if let Some(span) = span.value() {
247 validate_span(span, timestamp_range)?;
248 } else {
249 return Err(ProcessingAction::InvalidTransaction(
250 "spans must be valid in transaction event",
251 ));
252 }
253 }
254
255 Ok(())
256}
257
258pub fn validate_span(
268 span: &Span,
269 timestamp_range: Option<&Range<UnixTimestamp>>,
270) -> ProcessingResult {
271 match (span.start_timestamp.value(), span.timestamp.value()) {
272 (Some(start), Some(end)) => {
273 validate_timestamps(start, end, timestamp_range)?;
274 }
275 (_, None) => {
276 return Err(ProcessingAction::InvalidTransaction(
278 "span is missing timestamp",
279 ));
280 }
281 (None, _) => {
282 return Err(ProcessingAction::InvalidTransaction(
284 "span is missing start_timestamp",
285 ));
286 }
287 }
288
289 if span.trace_id.value().is_none() {
290 return Err(ProcessingAction::InvalidTransaction(
291 "span is missing trace_id",
292 ));
293 }
294
295 if span.span_id.value().is_none() {
296 return Err(ProcessingAction::InvalidTransaction(
297 "span is missing span_id",
298 ));
299 }
300
301 Ok(())
302}
303
304fn validate_bounded_integer_field(value: u64) -> ProcessingResult {
306 if value < 2_147_483_647 {
307 Ok(())
308 } else {
309 Err(ProcessingAction::DeleteValueHard)
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use chrono::TimeZone;
316 use relay_base_schema::spans::SpanStatus;
317 use relay_event_schema::protocol::Contexts;
318 use relay_protocol::{Object, get_value};
319
320 use super::*;
321
322 fn new_test_event() -> Annotated<Event> {
323 let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
324 let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap();
325 Annotated::new(Event {
326 ty: Annotated::new(EventType::Transaction),
327 transaction: Annotated::new("/".to_owned()),
328 start_timestamp: Annotated::new(start.into()),
329 timestamp: Annotated::new(end.into()),
330 contexts: {
331 let mut contexts = Contexts::new();
332 contexts.add(TraceContext {
333 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
334 span_id: Annotated::new("fa90fdead5f74053".parse().unwrap()),
335 op: Annotated::new("http.server".to_owned()),
336 ..Default::default()
337 });
338 Annotated::new(contexts)
339 },
340 spans: Annotated::new(vec![Annotated::new(Span {
341 start_timestamp: Annotated::new(start.into()),
342 timestamp: Annotated::new(end.into()),
343 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
344 span_id: Annotated::new("fa90fdead5f74053".parse().unwrap()),
345 op: Annotated::new("db.statement".to_owned()),
346 ..Default::default()
347 })]),
348 ..Default::default()
349 })
350 }
351
352 #[test]
353 fn test_timestamp_added_if_missing() {
354 let mut event = Annotated::new(Event::default());
355 assert!(get_value!(event.timestamp).is_none());
356 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_ok());
357 assert!(get_value!(event.timestamp).is_some());
358 }
359
360 #[test]
361 fn test_discards_when_timestamp_out_of_range() {
362 let mut event = new_test_event();
363
364 assert!(matches!(
365 validate_event(
366 &mut event,
367 &EventValidationConfig {
368 transaction_timestamp_range: Some(UnixTimestamp::now()..UnixTimestamp::now()),
369 is_validated: false,
370 ..Default::default()
371 }
372 ),
373 Err(ProcessingAction::InvalidTransaction(
374 "timestamp is out of the valid range for metrics"
375 ))
376 ));
377 }
378
379 #[test]
380 fn test_discards_when_missing_start_timestamp() {
381 let mut event = Annotated::new(Event {
382 ty: Annotated::new(EventType::Transaction),
383 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
384 ..Default::default()
385 });
386
387 assert_eq!(
388 validate_event(&mut event, &EventValidationConfig::default()),
389 Err(ProcessingAction::InvalidTransaction(
390 "start_timestamp hard-required for transaction events"
391 ))
392 );
393 }
394
395 #[test]
396 fn test_discards_on_missing_contexts_map() {
397 let mut event = Annotated::new(Event {
398 ty: Annotated::new(EventType::Transaction),
399 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
400 start_timestamp: Annotated::new(
401 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
402 ),
403 ..Default::default()
404 });
405
406 assert_eq!(
407 validate_event(
408 &mut event,
409 &EventValidationConfig {
410 transaction_timestamp_range: None,
411 is_validated: false,
412 ..Default::default()
413 }
414 ),
415 Err(ProcessingAction::InvalidTransaction(
416 "missing valid trace context"
417 ))
418 );
419 }
420
421 #[test]
422 fn test_discards_on_missing_context() {
423 let mut event = Annotated::new(Event {
424 ty: Annotated::new(EventType::Transaction),
425 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
426 start_timestamp: Annotated::new(
427 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
428 ),
429 contexts: Annotated::new(Contexts::new()),
430 ..Default::default()
431 });
432
433 assert_eq!(
434 validate_event(
435 &mut event,
436 &EventValidationConfig {
437 transaction_timestamp_range: None,
438 is_validated: false,
439 ..Default::default()
440 }
441 ),
442 Err(ProcessingAction::InvalidTransaction(
443 "missing valid trace context"
444 ))
445 );
446 }
447
448 #[test]
449 fn test_discards_on_null_context() {
450 let mut event = Annotated::new(Event {
451 ty: Annotated::new(EventType::Transaction),
452 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
453 start_timestamp: Annotated::new(
454 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
455 ),
456 contexts: Annotated::new(Contexts({
457 let mut contexts = Object::new();
458 contexts.insert("trace".to_owned(), Annotated::empty());
459 contexts
460 })),
461 ..Default::default()
462 });
463
464 assert_eq!(
465 validate_event(&mut event, &EventValidationConfig::default()),
466 Err(ProcessingAction::InvalidTransaction(
467 "missing valid trace context"
468 ))
469 );
470 }
471
472 #[test]
473 fn test_discards_on_missing_trace_id_in_context() {
474 let mut event = Annotated::new(Event {
475 ty: Annotated::new(EventType::Transaction),
476 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
477 start_timestamp: Annotated::new(
478 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
479 ),
480 contexts: {
481 let mut contexts = Contexts::new();
482 contexts.add(TraceContext::default());
483 Annotated::new(contexts)
484 },
485 ..Default::default()
486 });
487
488 assert_eq!(
489 validate_event(&mut event, &EventValidationConfig::default()),
490 Err(ProcessingAction::InvalidTransaction(
491 "trace context is missing trace_id"
492 ))
493 );
494 }
495
496 #[test]
497 fn test_discards_on_missing_span_id_in_context() {
498 let mut event = Annotated::new(Event {
499 ty: Annotated::new(EventType::Transaction),
500 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
501 start_timestamp: Annotated::new(
502 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
503 ),
504 contexts: {
505 let mut contexts = Contexts::new();
506 contexts.add(TraceContext {
507 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
508 ..Default::default()
509 });
510 Annotated::new(contexts)
511 },
512 ..Default::default()
513 });
514
515 assert_eq!(
516 validate_event(&mut event, &EventValidationConfig::default()),
517 Err(ProcessingAction::InvalidTransaction(
518 "trace context is missing span_id"
519 ))
520 );
521 }
522
523 #[test]
524 fn test_discards_transaction_event_with_nulled_out_span() {
525 let mut event = Annotated::new(Event {
526 ty: Annotated::new(EventType::Transaction),
527 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
528 start_timestamp: Annotated::new(
529 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
530 ),
531 contexts: {
532 let mut contexts = Contexts::new();
533 contexts.add(TraceContext {
534 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
535 span_id: Annotated::new("fa90fdead5f74053".parse().unwrap()),
536 op: Annotated::new("http.server".to_owned()),
537 ..Default::default()
538 });
539 Annotated::new(contexts)
540 },
541 spans: Annotated::new(vec![Annotated::empty()]),
542 ..Default::default()
543 });
544
545 assert_eq!(
546 validate_event(&mut event, &EventValidationConfig::default()),
547 Err(ProcessingAction::InvalidTransaction(
548 "spans must be valid in transaction event"
549 ))
550 );
551 }
552
553 #[test]
554 fn test_discards_transaction_event_with_span_with_missing_start_timestamp() {
555 let mut event = Annotated::new(Event {
556 ty: Annotated::new(EventType::Transaction),
557 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
558 start_timestamp: Annotated::new(
559 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
560 ),
561 contexts: {
562 let mut contexts = Contexts::new();
563 contexts.add(TraceContext {
564 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
565 span_id: Annotated::new("fa90fdead5f74053".parse().unwrap()),
566 op: Annotated::new("http.server".to_owned()),
567 ..Default::default()
568 });
569 Annotated::new(contexts)
570 },
571 spans: Annotated::new(vec![Annotated::new(Span {
572 timestamp: Annotated::new(
573 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
574 ),
575 ..Default::default()
576 })]),
577 ..Default::default()
578 });
579
580 assert_eq!(
581 validate_event(&mut event, &EventValidationConfig::default()),
582 Err(ProcessingAction::InvalidTransaction(
583 "span is missing start_timestamp"
584 ))
585 );
586 }
587
588 #[test]
589 fn test_discards_transaction_event_with_span_with_missing_trace_id() {
590 let mut event = Annotated::new(Event {
591 ty: Annotated::new(EventType::Transaction),
592 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
593 start_timestamp: Annotated::new(
594 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
595 ),
596 contexts: {
597 let mut contexts = Contexts::new();
598 contexts.add(TraceContext {
599 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
600 span_id: Annotated::new("fa90fdead5f74053".parse().unwrap()),
601 op: Annotated::new("http.server".to_owned()),
602 ..Default::default()
603 });
604 Annotated::new(contexts)
605 },
606 spans: Annotated::new(vec![Annotated::new(Span {
607 timestamp: Annotated::new(
608 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
609 ),
610 start_timestamp: Annotated::new(
611 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
612 ),
613 ..Default::default()
614 })]),
615 ..Default::default()
616 });
617
618 assert_eq!(
619 validate_event(&mut event, &EventValidationConfig::default()),
620 Err(ProcessingAction::InvalidTransaction(
621 "span is missing trace_id"
622 ))
623 );
624 }
625
626 #[test]
627 fn test_discards_transaction_event_with_span_with_missing_span_id() {
628 let mut event = Annotated::new(Event {
629 ty: Annotated::new(EventType::Transaction),
630 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
631 start_timestamp: Annotated::new(
632 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
633 ),
634 contexts: {
635 let mut contexts = Contexts::new();
636 contexts.add(TraceContext {
637 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
638 span_id: Annotated::new("fa90fdead5f74053".parse().unwrap()),
639 op: Annotated::new("http.server".to_owned()),
640 ..Default::default()
641 });
642 Annotated::new(contexts)
643 },
644 spans: Annotated::new(vec![Annotated::new(Span {
645 timestamp: Annotated::new(
646 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
647 ),
648 start_timestamp: Annotated::new(
649 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
650 ),
651 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
652 ..Default::default()
653 })]),
654 ..Default::default()
655 });
656
657 assert_eq!(
658 validate_event(&mut event, &EventValidationConfig::default()),
659 Err(ProcessingAction::InvalidTransaction(
660 "span is missing span_id"
661 ))
662 );
663 }
664
665 #[test]
666 fn test_reject_stale_transaction() {
667 let json = r#"{
668 "event_id": "52df9022835246eeb317dbd739ccd059",
669 "start_timestamp": -2,
670 "timestamp": -1
671}"#;
672 let mut transaction = Annotated::<Event>::from_json(json).unwrap();
673 let res = validate_event(&mut transaction, &EventValidationConfig::default());
674 assert_eq!(
675 res.unwrap_err().to_string(),
676 "invalid transaction event: timestamp is too stale"
677 );
678 }
679
680 #[test]
683 fn test_accept_recent_transactions_with_stale_timestamps() {
684 let config = EventValidationConfig {
685 received_at: Some(Utc::now()),
686 max_secs_in_past: Some(2),
687 max_secs_in_future: Some(1),
688 is_validated: false,
689 ..Default::default()
690 };
691
692 let json = r#"{
693 "event_id": "52df9022835246eeb317dbd739ccd059",
694 "transaction": "I have a stale timestamp, but I'm recent!",
695 "start_timestamp": -2,
696 "timestamp": -1
697}"#;
698 let mut event = Annotated::<Event>::from_json(json).unwrap();
699
700 assert!(validate_event(&mut event, &config).is_ok());
701 }
702
703 #[test]
709 fn test_reject_stale_transactions_after_timestamp_normalization() {
710 let now = Utc::now();
711 let config = EventValidationConfig {
712 received_at: Some(now),
713 max_secs_in_past: Some(2),
714 max_secs_in_future: Some(1),
715 is_validated: false,
716 ..Default::default()
717 };
718
719 let json = format!(
720 r#"{{
721 "event_id": "52df9022835246eeb317dbd739ccd059",
722 "transaction": "clockdrift is not enough to accept me :(",
723 "start_timestamp": -62135811111,
724 "timestamp": {}
725 }}"#,
726 now.timestamp()
727 );
728 let mut event = Annotated::<Event>::from_json(json.as_str()).unwrap();
729
730 assert_eq!(
731 validate_event(&mut event, &config).unwrap_err().to_string(),
732 "invalid transaction event: timestamp is too stale"
733 );
734 }
735
736 #[test]
738 fn test_accept_transactions_with_unfinished_spans() {
739 let json = r#"{
740 "event_id": "52df9022835246eeb317dbd739ccd059",
741 "type": "transaction",
742 "transaction": "I have a stale timestamp, but I'm recent!",
743 "start_timestamp": 1,
744 "timestamp": 2,
745 "contexts": {
746 "trace": {
747 "trace_id": "ff62a8b040f340bda5d830223def1d81",
748 "span_id": "bd429c44b67a3eb4"
749 }
750 },
751 "spans": [
752 {
753 "span_id": "bd429c44b67a3eb4",
754 "start_timestamp": 1,
755 "timestamp": null,
756 "trace_id": "ff62a8b040f340bda5d830223def1d81"
757 }
758 ]
759}"#;
760 let mut event = Annotated::<Event>::from_json(json).unwrap();
761
762 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_ok());
763
764 let event = get_value!(event!);
765 let spans = &event.spans;
766 let span = get_value!(spans[0]!);
767
768 assert_eq!(span.timestamp, event.timestamp);
769 assert_eq!(span.status.value().unwrap(), &SpanStatus::DeadlineExceeded);
770 }
771
772 #[test]
774 fn test_finish_spans_with_normalized_transaction_end_timestamp() {
775 let json = r#"{
776 "event_id": "52df9022835246eeb317dbd739ccd059",
777 "type": "transaction",
778 "transaction": "I have a stale timestamp, but I'm recent!",
779 "start_timestamp": 946731000,
780 "timestamp": 946731555,
781 "contexts": {
782 "trace": {
783 "trace_id": "ff62a8b040f340bda5d830223def1d81",
784 "span_id": "bd429c44b67a3eb4"
785 }
786 },
787 "spans": [
788 {
789 "span_id": "bd429c44b67a3eb4",
790 "start_timestamp": 946731000,
791 "timestamp": null,
792 "trace_id": "ff62a8b040f340bda5d830223def1d81"
793 }
794 ]
795}"#;
796 let mut event = Annotated::<Event>::from_json(json).unwrap();
797
798 validate_event(
799 &mut event,
800 &EventValidationConfig {
801 received_at: Some(Utc::now()),
802 max_secs_in_past: Some(2),
803 max_secs_in_future: Some(1),
804 is_validated: false,
805 ..Default::default()
806 },
807 )
808 .unwrap();
809 validate_event(&mut event, &EventValidationConfig::default()).unwrap();
810
811 let event = get_value!(event!);
812 let spans = &event.spans;
813 let span = get_value!(spans[0]!);
814
815 assert_eq!(span.timestamp.value(), event.timestamp.value());
816 }
817
818 #[test]
819 fn test_skip_transaction_validation_on_renormalization() {
820 let json = r#"{
821 "event_id": "52df9022835246eeb317dbd739ccd059",
822 "type": "transaction",
823 "transaction": "I'm invalid because I don't have any timestamps!"
824}"#;
825 let mut event = Annotated::<Event>::from_json(json).unwrap();
826
827 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_err());
828 assert!(
829 validate_event(
830 &mut event,
831 &EventValidationConfig {
832 is_validated: true,
833 ..Default::default()
834 }
835 )
836 .is_ok()
837 );
838 }
839
840 #[test]
841 fn test_skip_event_timestamp_validation_on_renormalization() {
842 let json = r#"{
843 "event_id": "52df9022835246eeb317dbd739ccd059",
844 "transaction": "completely outdated transaction",
845 "start_timestamp": -2,
846 "timestamp": -1
847}"#;
848 let mut event = Annotated::<Event>::from_json(json).unwrap();
849
850 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_err());
851 assert!(
852 validate_event(
853 &mut event,
854 &EventValidationConfig {
855 is_validated: true,
856 ..Default::default()
857 }
858 )
859 .is_ok()
860 );
861 }
862}