1use std::convert::Infallible;
2use std::fmt;
3use std::ops::{Deref, DerefMut};
4use std::str::FromStr;
5
6use relay_protocol::{
7 Annotated, Array, Empty, ErrorKind, FromValue, IntoValue, Object, SkipSerialization, Value,
8};
9use serde::{Deserialize, Serialize};
10
11use crate::processor::ProcessValue;
12use crate::protocol::{Addr, LockReason, NativeImagePath, RegVal};
13
14#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
19#[metastructure(process_func = "process_frame", value_type = "Frame")]
20pub struct Frame {
21 #[metastructure(max_chars = 512, max_chars_allowance = 20)]
26 #[metastructure(skip_serialization = "empty")]
27 pub function: Annotated<String>,
28
29 #[metastructure(max_chars = 512, max_chars_allowance = 20)]
45 #[metastructure(skip_serialization = "empty")]
46 pub raw_function: Annotated<String>,
47
48 #[metastructure(max_chars = 512)]
56 pub symbol: Annotated<String>,
57
58 #[metastructure(skip_serialization = "empty", pii = "maybe")]
63 pub module: Annotated<String>,
65
66 #[metastructure(skip_serialization = "empty")]
71 pub package: Annotated<String>,
73
74 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
76 #[metastructure(skip_serialization = "empty", pii = "maybe")]
77 pub filename: Annotated<NativeImagePath>,
78
79 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
81 #[metastructure(skip_serialization = "empty", pii = "maybe")]
82 pub abs_path: Annotated<NativeImagePath>,
83
84 #[metastructure(skip_serialization = "null")]
86 pub lineno: Annotated<u64>,
87
88 #[metastructure(skip_serialization = "null")]
90 pub colno: Annotated<u64>,
91
92 #[metastructure(skip_serialization = "empty")]
97 pub platform: Annotated<String>,
98
99 #[metastructure(skip_serialization = "empty")]
101 pub pre_context: Annotated<Array<String>>,
102
103 #[metastructure(skip_serialization = "null")]
105 pub context_line: Annotated<String>,
106
107 #[metastructure(skip_serialization = "null")]
109 pub parent_index: Annotated<i64>,
110
111 #[metastructure(skip_serialization = "null")]
113 pub sample_count: Annotated<u64>,
114
115 #[metastructure(skip_serialization = "empty")]
117 pub post_context: Annotated<Array<String>>,
118
119 #[metastructure(skip_serialization = "null")]
125 pub in_app: Annotated<bool>,
126
127 #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
130 pub vars: Annotated<FrameVars>,
131
132 #[metastructure(omit_from_schema)]
134 #[metastructure(skip_serialization = "empty")]
135 pub data: Annotated<FrameData>,
136
137 #[metastructure(skip_serialization = "null")]
139 pub image_addr: Annotated<Addr>,
140
141 #[metastructure(skip_serialization = "null")]
148 pub instruction_addr: Annotated<Addr>,
149
150 #[metastructure(skip_serialization = "empty")]
160 pub addr_mode: Annotated<String>,
161
162 #[metastructure(skip_serialization = "empty")]
166 pub function_id: Annotated<Addr>,
167
168 #[metastructure(skip_serialization = "null")]
173 pub symbol_addr: Annotated<Addr>,
174
175 #[metastructure(max_chars = 128)]
177 #[metastructure(omit_from_schema)]
178 pub trust: Annotated<String>,
179
180 #[metastructure(max_chars = 128)]
182 #[metastructure(omit_from_schema)]
183 pub lang: Annotated<String>,
184
185 #[metastructure(skip_serialization = "null")]
194 pub stack_start: Annotated<bool>,
195
196 #[metastructure(skip_serialization = "null")]
198 pub lock: Annotated<LockReason>,
199
200 #[metastructure(additional_properties)]
202 pub other: Object<Value>,
203}
204
205#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
207pub struct FrameVars(#[metastructure(skip_serialization = "empty")] pub Object<Value>);
208
209#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
213pub struct FrameData {
214 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
216 sourcemap: Annotated<String>,
217 #[metastructure(max_chars = 256, max_chars_allowance = 20)]
219 orig_function: Annotated<String>,
220 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
222 orig_filename: Annotated<String>,
223 orig_lineno: Annotated<u64>,
225 orig_colno: Annotated<u64>,
227 orig_in_app: Annotated<i64>,
236 #[metastructure(additional_properties)]
238 pub other: Object<Value>,
239}
240
241impl From<Object<Value>> for FrameVars {
242 fn from(value: Object<Value>) -> Self {
243 FrameVars(value)
244 }
245}
246
247impl FromValue for FrameVars {
248 fn from_value(mut value: Annotated<Value>) -> Annotated<FrameVars> {
249 value = value.map_value(|value| {
250 if let Value::Array(value) = value {
251 Value::Object(
252 value
253 .into_iter()
254 .enumerate()
255 .map(|(i, v)| (i.to_string(), v))
256 .collect(),
257 )
258 } else {
259 value
260 }
261 });
262
263 FromValue::from_value(value).map_value(FrameVars)
264 }
265}
266
267#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
336#[metastructure(process_func = "process_raw_stacktrace", value_type = "Stacktrace")]
337pub struct RawStacktrace {
338 #[metastructure(required = true, nonempty = true, skip_serialization = "empty")]
341 pub frames: Annotated<Array<Frame>>,
342
343 pub registers: Annotated<Object<RegVal>>,
348
349 #[metastructure(skip_serialization = "null")]
352 pub instruction_addr_adjustment: Annotated<InstructionAddrAdjustment>,
353
354 #[metastructure(max_chars = 128)]
356 pub lang: Annotated<String>,
357
358 pub snapshot: Annotated<bool>,
368
369 #[metastructure(additional_properties)]
371 pub other: Object<Value>,
372}
373
374#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ProcessValue)]
385#[serde(rename_all = "snake_case")]
386#[derive(Default)]
387pub enum InstructionAddrAdjustment {
388 #[default]
390 Auto,
391
392 AllButFirst,
395
396 All,
399
400 None,
403
404 Unknown(String),
408}
409
410impl InstructionAddrAdjustment {
411 pub fn as_str(&self) -> &str {
413 match self {
414 InstructionAddrAdjustment::Auto => "auto",
415 InstructionAddrAdjustment::AllButFirst => "all_but_first",
416 InstructionAddrAdjustment::All => "all",
417 InstructionAddrAdjustment::None => "none",
418 InstructionAddrAdjustment::Unknown(s) => s,
419 }
420 }
421}
422
423impl FromStr for InstructionAddrAdjustment {
424 type Err = Infallible;
425
426 fn from_str(s: &str) -> Result<Self, Self::Err> {
427 match s {
428 "auto" => Ok(Self::Auto),
429 "all_but_first" => Ok(Self::AllButFirst),
430 "all" => Ok(Self::All),
431 "none" => Ok(Self::None),
432 s => Ok(Self::Unknown(s.to_owned())),
433 }
434 }
435}
436
437impl fmt::Display for InstructionAddrAdjustment {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 f.write_str(self.as_str())
440 }
441}
442
443impl Empty for InstructionAddrAdjustment {
444 #[inline]
445 fn is_empty(&self) -> bool {
446 matches!(self, Self::Auto)
447 }
448}
449
450impl FromValue for InstructionAddrAdjustment {
451 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
452 match String::from_value(value) {
453 Annotated(Some(value), mut meta) => match value.parse() {
454 Ok(adjustment) => Annotated(Some(adjustment), meta),
455 Err(_) => {
456 meta.add_error(ErrorKind::InvalidData);
457 meta.set_original_value(Some(value));
458 Annotated(None, meta)
459 }
460 },
461 Annotated(None, meta) => Annotated(None, meta),
462 }
463 }
464}
465
466impl IntoValue for InstructionAddrAdjustment {
467 fn into_value(self) -> Value
468 where
469 Self: Sized,
470 {
471 Value::String(match self {
472 Self::Unknown(s) => s,
473 _ => self.as_str().to_owned(),
474 })
475 }
476
477 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
478 where
479 Self: Sized,
480 S: serde::Serializer,
481 {
482 serde::Serialize::serialize(self.as_str(), s)
483 }
484}
485
486#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
489#[metastructure(process_func = "process_stacktrace")]
490pub struct Stacktrace(pub RawStacktrace);
491
492impl Deref for Stacktrace {
493 type Target = RawStacktrace;
494
495 fn deref(&self) -> &RawStacktrace {
496 &self.0
497 }
498}
499
500impl DerefMut for Stacktrace {
501 fn deref_mut(&mut self) -> &mut RawStacktrace {
502 &mut self.0
503 }
504}
505
506impl From<RawStacktrace> for Stacktrace {
507 fn from(stacktrace: RawStacktrace) -> Stacktrace {
508 Stacktrace(stacktrace)
509 }
510}
511
512impl From<Stacktrace> for RawStacktrace {
513 fn from(stacktrace: Stacktrace) -> RawStacktrace {
514 stacktrace.0
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use crate::protocol::{LockReasonType, ThreadId};
521 use similar_asserts::assert_eq;
522
523 use super::*;
524
525 #[test]
526 fn test_frame_roundtrip() {
527 let json = r#"{
528 "function": "main@8",
529 "raw_function": "main",
530 "symbol": "_main@8",
531 "module": "app",
532 "package": "/my/app",
533 "filename": "myfile.rs",
534 "abs_path": "/path/to",
535 "lineno": 2,
536 "colno": 42,
537 "platform": "rust",
538 "pre_context": [
539 "fn main() {"
540 ],
541 "context_line": "unimplemented!()",
542 "post_context": [
543 "}"
544 ],
545 "in_app": true,
546 "vars": {
547 "variable": "value"
548 },
549 "data": {
550 "sourcemap": "http://example.com/invalid.map"
551 },
552 "image_addr": "0x400",
553 "instruction_addr": "0x404",
554 "addr_mode": "abs",
555 "symbol_addr": "0x404",
556 "trust": "69",
557 "lang": "rust",
558 "stack_start": true,
559 "lock": {
560 "type": 2,
561 "address": "0x07d7437b",
562 "package_name": "io.sentry.samples",
563 "class_name": "MainActivity",
564 "thread_id": 7
565 },
566 "other": "value"
567}"#;
568 let frame = Annotated::new(Frame {
569 function: Annotated::new("main@8".to_owned()),
570 raw_function: Annotated::new("main".to_owned()),
571 symbol: Annotated::new("_main@8".to_owned()),
572 module: Annotated::new("app".to_owned()),
573 package: Annotated::new("/my/app".to_owned()),
574 filename: Annotated::new("myfile.rs".into()),
575 abs_path: Annotated::new("/path/to".into()),
576 lineno: Annotated::new(2),
577 colno: Annotated::new(42),
578 platform: Annotated::new("rust".to_owned()),
579 pre_context: Annotated::new(vec![Annotated::new("fn main() {".to_owned())]),
580 context_line: Annotated::new("unimplemented!()".to_owned()),
581 post_context: Annotated::new(vec![Annotated::new("}".to_owned())]),
582 in_app: Annotated::new(true),
583 vars: {
584 let mut vars = Object::new();
585 vars.insert(
586 "variable".to_owned(),
587 Annotated::new(Value::String("value".to_owned())),
588 );
589 Annotated::new(vars.into())
590 },
591 data: Annotated::new(FrameData {
592 sourcemap: Annotated::new("http://example.com/invalid.map".to_owned()),
593 ..Default::default()
594 }),
595 image_addr: Annotated::new(Addr(0x400)),
596 instruction_addr: Annotated::new(Addr(0x404)),
597 addr_mode: Annotated::new("abs".into()),
598 function_id: Annotated::empty(),
599 symbol_addr: Annotated::new(Addr(0x404)),
600 trust: Annotated::new("69".into()),
601 lang: Annotated::new("rust".into()),
602 stack_start: Annotated::new(true),
603 lock: Annotated::new(LockReason {
604 ty: Annotated::new(LockReasonType::Waiting),
605 address: Annotated::new("0x07d7437b".to_owned()),
606 package_name: Annotated::new("io.sentry.samples".to_owned()),
607 class_name: Annotated::new("MainActivity".to_owned()),
608 thread_id: Annotated::new(ThreadId::Int(7)),
609 other: Default::default(),
610 }),
611 parent_index: Annotated::empty(),
612 sample_count: Annotated::empty(),
613 other: {
614 let mut vars = Object::new();
615 vars.insert(
616 "other".to_owned(),
617 Annotated::new(Value::String("value".to_owned())),
618 );
619 vars
620 },
621 });
622
623 assert_eq!(frame, Annotated::from_json(json).unwrap());
624 assert_eq!(json, frame.to_json_pretty().unwrap());
625 }
626
627 #[test]
628 fn test_frame_default_values() {
629 let json = "{}";
630 let frame = Annotated::new(Frame::default());
631
632 assert_eq!(frame, Annotated::from_json(json).unwrap());
633 assert_eq!(json, frame.to_json_pretty().unwrap());
634 }
635
636 #[test]
637 fn test_stacktrace_roundtrip() {
638 let json = r#"{
639 "frames": [
640 {
641 "function": "foobar"
642 }
643 ],
644 "registers": {
645 "cspr": "0x20000000",
646 "lr": "0x18a31aadc",
647 "pc": "0x18a310ea4",
648 "sp": "0x16fd75060"
649 },
650 "instruction_addr_adjustment": "all_but_first",
651 "lang": "rust",
652 "snapshot": false,
653 "other": "value"
654}"#;
655 let stack = Annotated::new(RawStacktrace {
656 frames: Annotated::new(vec![Annotated::new(Frame {
657 function: Annotated::new("foobar".to_owned()),
658 ..Default::default()
659 })]),
660 registers: {
661 let mut registers = Object::new();
662 registers.insert("cspr".to_owned(), Annotated::new(RegVal(0x2000_0000)));
663 registers.insert("lr".to_owned(), Annotated::new(RegVal(0x1_8a31_aadc)));
664 registers.insert("pc".to_owned(), Annotated::new(RegVal(0x1_8a31_0ea4)));
665 registers.insert("sp".to_owned(), Annotated::new(RegVal(0x1_6fd7_5060)));
666 Annotated::new(registers)
667 },
668 instruction_addr_adjustment: Annotated::new(InstructionAddrAdjustment::AllButFirst),
669 lang: Annotated::new("rust".into()),
670 snapshot: Annotated::new(false),
671 other: {
672 let mut other = Object::new();
673 other.insert(
674 "other".to_owned(),
675 Annotated::new(Value::String("value".to_owned())),
676 );
677 other
678 },
679 });
680
681 assert_eq!(stack, Annotated::from_json(json).unwrap());
682 assert_eq!(json, stack.to_json_pretty().unwrap());
683 }
684
685 #[test]
686 fn test_stacktrace_default_values() {
687 let json = r#"{
689 "frames": [
690 {}
691 ]
692}"#;
693
694 let stack = Annotated::new(RawStacktrace {
695 frames: Annotated::new(vec![Annotated::new(Frame::default())]),
696 ..Default::default()
697 });
698
699 assert_eq!(stack, Annotated::from_json(json).unwrap());
700 assert_eq!(json, stack.to_json_pretty().unwrap());
701 }
702
703 #[test]
704 fn test_frame_vars_null_preserved() {
705 let json = r#"{
706 "vars": {
707 "despacito": null
708 }
709}"#;
710 let frame = Annotated::new(Frame {
711 vars: Annotated::new({
712 let mut vars = Object::new();
713 vars.insert("despacito".to_owned(), Annotated::empty());
714 vars.into()
715 }),
716 ..Default::default()
717 });
718
719 assert_eq!(Annotated::from_json(json).unwrap(), frame);
720 assert_eq!(json, frame.to_json_pretty().unwrap());
721 }
722
723 #[test]
724 fn test_frame_vars_empty_annotated_is_serialized() {
725 let output = r#"{
726 "vars": {
727 "despacito": null,
728 "despacito2": null
729 }
730}"#;
731 let frame = Annotated::new(Frame {
732 vars: Annotated::new({
733 let mut vars = Object::new();
734 vars.insert("despacito".to_owned(), Annotated::empty());
735 vars.insert("despacito2".to_owned(), Annotated::empty());
736 vars.into()
737 }),
738 ..Default::default()
739 });
740
741 assert_eq!(output, frame.to_json_pretty().unwrap());
742 }
743
744 #[test]
745 fn test_frame_empty_context_lines() {
746 let json = r#"{
747 "pre_context": [
748 ""
749 ],
750 "context_line": "",
751 "post_context": [
752 ""
753 ]
754}"#;
755
756 let frame = Annotated::new(Frame {
757 pre_context: Annotated::new(vec![Annotated::new("".to_owned())]),
758 context_line: Annotated::new("".to_owned()),
759 post_context: Annotated::new(vec![Annotated::new("".to_owned())]),
760 ..Frame::default()
761 });
762
763 assert_eq!(frame, Annotated::from_json(json).unwrap());
764 assert_eq!(json, frame.to_json_pretty().unwrap());
765 }
766
767 #[test]
768 fn test_php_frame_vars() {
769 let input = r#"{
774 "vars": ["foo", "bar", "baz", null]
775}"#;
776
777 let output = r#"{
778 "vars": {
779 "0": "foo",
780 "1": "bar",
781 "2": "baz",
782 "3": null
783 }
784}"#;
785
786 let frame = Annotated::new(Frame {
787 vars: Annotated::new({
788 let mut vars = Object::new();
789 vars.insert("0".to_owned(), Annotated::new("foo".to_owned().into()));
790 vars.insert("1".to_owned(), Annotated::new("bar".to_owned().into()));
791 vars.insert("2".to_owned(), Annotated::new("baz".to_owned().into()));
792 vars.insert("3".to_owned(), Annotated::empty());
793 vars.into()
794 }),
795 ..Default::default()
796 });
797
798 assert_eq!(frame, Annotated::from_json(input).unwrap());
799 assert_eq!(output, frame.to_json_pretty().unwrap());
800 }
801}