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