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 = "empty")]
109 pub post_context: Annotated<Array<String>>,
110
111 #[metastructure(skip_serialization = "null")]
117 pub in_app: Annotated<bool>,
118
119 #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
122 pub vars: Annotated<FrameVars>,
123
124 #[metastructure(omit_from_schema)]
126 #[metastructure(skip_serialization = "empty")]
127 pub data: Annotated<FrameData>,
128
129 #[metastructure(skip_serialization = "null")]
131 pub image_addr: Annotated<Addr>,
132
133 #[metastructure(skip_serialization = "null")]
140 pub instruction_addr: Annotated<Addr>,
141
142 #[metastructure(skip_serialization = "empty")]
152 pub addr_mode: Annotated<String>,
153
154 #[metastructure(skip_serialization = "empty")]
158 pub function_id: Annotated<Addr>,
159
160 #[metastructure(skip_serialization = "null")]
165 pub symbol_addr: Annotated<Addr>,
166
167 #[metastructure(max_chars = 128)]
169 #[metastructure(omit_from_schema)]
170 pub trust: Annotated<String>,
171
172 #[metastructure(max_chars = 128)]
174 #[metastructure(omit_from_schema)]
175 pub lang: Annotated<String>,
176
177 #[metastructure(skip_serialization = "null")]
186 pub stack_start: Annotated<bool>,
187
188 #[metastructure(skip_serialization = "null")]
190 pub lock: Annotated<LockReason>,
191
192 #[metastructure(additional_properties)]
194 pub other: Object<Value>,
195}
196
197#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
199pub struct FrameVars(#[metastructure(skip_serialization = "empty")] pub Object<Value>);
200
201#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
205pub struct FrameData {
206 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
208 sourcemap: Annotated<String>,
209 #[metastructure(max_chars = 256, max_chars_allowance = 20)]
211 orig_function: Annotated<String>,
212 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
214 orig_filename: Annotated<String>,
215 orig_lineno: Annotated<u64>,
217 orig_colno: Annotated<u64>,
219 orig_in_app: Annotated<i64>,
228 #[metastructure(additional_properties)]
230 pub other: Object<Value>,
231}
232
233impl From<Object<Value>> for FrameVars {
234 fn from(value: Object<Value>) -> Self {
235 FrameVars(value)
236 }
237}
238
239impl FromValue for FrameVars {
240 fn from_value(mut value: Annotated<Value>) -> Annotated<FrameVars> {
241 value = value.map_value(|value| {
242 if let Value::Array(value) = value {
243 Value::Object(
244 value
245 .into_iter()
246 .enumerate()
247 .map(|(i, v)| (i.to_string(), v))
248 .collect(),
249 )
250 } else {
251 value
252 }
253 });
254
255 FromValue::from_value(value).map_value(FrameVars)
256 }
257}
258
259#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
328#[metastructure(process_func = "process_raw_stacktrace", value_type = "Stacktrace")]
329pub struct RawStacktrace {
330 #[metastructure(required = true, nonempty = true, skip_serialization = "empty")]
333 pub frames: Annotated<Array<Frame>>,
334
335 pub registers: Annotated<Object<RegVal>>,
340
341 #[metastructure(skip_serialization = "null")]
344 pub instruction_addr_adjustment: Annotated<InstructionAddrAdjustment>,
345
346 #[metastructure(max_chars = 128)]
348 pub lang: Annotated<String>,
349
350 pub snapshot: Annotated<bool>,
360
361 #[metastructure(additional_properties)]
363 pub other: Object<Value>,
364}
365
366#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ProcessValue)]
377#[serde(rename_all = "snake_case")]
378#[derive(Default)]
379pub enum InstructionAddrAdjustment {
380 #[default]
382 Auto,
383
384 AllButFirst,
387
388 All,
391
392 None,
395
396 Unknown(String),
400}
401
402impl InstructionAddrAdjustment {
403 pub fn as_str(&self) -> &str {
405 match self {
406 InstructionAddrAdjustment::Auto => "auto",
407 InstructionAddrAdjustment::AllButFirst => "all_but_first",
408 InstructionAddrAdjustment::All => "all",
409 InstructionAddrAdjustment::None => "none",
410 InstructionAddrAdjustment::Unknown(s) => s,
411 }
412 }
413}
414
415impl FromStr for InstructionAddrAdjustment {
416 type Err = Infallible;
417
418 fn from_str(s: &str) -> Result<Self, Self::Err> {
419 match s {
420 "auto" => Ok(Self::Auto),
421 "all_but_first" => Ok(Self::AllButFirst),
422 "all" => Ok(Self::All),
423 "none" => Ok(Self::None),
424 s => Ok(Self::Unknown(s.to_owned())),
425 }
426 }
427}
428
429impl fmt::Display for InstructionAddrAdjustment {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 f.write_str(self.as_str())
432 }
433}
434
435impl Empty for InstructionAddrAdjustment {
436 #[inline]
437 fn is_empty(&self) -> bool {
438 matches!(self, Self::Auto)
439 }
440}
441
442impl FromValue for InstructionAddrAdjustment {
443 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
444 match String::from_value(value) {
445 Annotated(Some(value), mut meta) => match value.parse() {
446 Ok(adjustment) => Annotated(Some(adjustment), meta),
447 Err(_) => {
448 meta.add_error(ErrorKind::InvalidData);
449 meta.set_original_value(Some(value));
450 Annotated(None, meta)
451 }
452 },
453 Annotated(None, meta) => Annotated(None, meta),
454 }
455 }
456}
457
458impl IntoValue for InstructionAddrAdjustment {
459 fn into_value(self) -> Value
460 where
461 Self: Sized,
462 {
463 Value::String(match self {
464 Self::Unknown(s) => s,
465 _ => self.as_str().to_owned(),
466 })
467 }
468
469 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
470 where
471 Self: Sized,
472 S: serde::Serializer,
473 {
474 serde::Serialize::serialize(self.as_str(), s)
475 }
476}
477
478#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
481#[metastructure(process_func = "process_stacktrace")]
482pub struct Stacktrace(pub RawStacktrace);
483
484impl Deref for Stacktrace {
485 type Target = RawStacktrace;
486
487 fn deref(&self) -> &RawStacktrace {
488 &self.0
489 }
490}
491
492impl DerefMut for Stacktrace {
493 fn deref_mut(&mut self) -> &mut RawStacktrace {
494 &mut self.0
495 }
496}
497
498impl From<RawStacktrace> for Stacktrace {
499 fn from(stacktrace: RawStacktrace) -> Stacktrace {
500 Stacktrace(stacktrace)
501 }
502}
503
504impl From<Stacktrace> for RawStacktrace {
505 fn from(stacktrace: Stacktrace) -> RawStacktrace {
506 stacktrace.0
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use crate::protocol::{LockReasonType, ThreadId};
513 use similar_asserts::assert_eq;
514
515 use super::*;
516
517 #[test]
518 fn test_frame_roundtrip() {
519 let json = r#"{
520 "function": "main@8",
521 "raw_function": "main",
522 "symbol": "_main@8",
523 "module": "app",
524 "package": "/my/app",
525 "filename": "myfile.rs",
526 "abs_path": "/path/to",
527 "lineno": 2,
528 "colno": 42,
529 "platform": "rust",
530 "pre_context": [
531 "fn main() {"
532 ],
533 "context_line": "unimplemented!()",
534 "post_context": [
535 "}"
536 ],
537 "in_app": true,
538 "vars": {
539 "variable": "value"
540 },
541 "data": {
542 "sourcemap": "http://example.com/invalid.map"
543 },
544 "image_addr": "0x400",
545 "instruction_addr": "0x404",
546 "addr_mode": "abs",
547 "symbol_addr": "0x404",
548 "trust": "69",
549 "lang": "rust",
550 "stack_start": true,
551 "lock": {
552 "type": 2,
553 "address": "0x07d7437b",
554 "package_name": "io.sentry.samples",
555 "class_name": "MainActivity",
556 "thread_id": 7
557 },
558 "other": "value"
559}"#;
560 let frame = Annotated::new(Frame {
561 function: Annotated::new("main@8".to_owned()),
562 raw_function: Annotated::new("main".to_owned()),
563 symbol: Annotated::new("_main@8".to_owned()),
564 module: Annotated::new("app".to_owned()),
565 package: Annotated::new("/my/app".to_owned()),
566 filename: Annotated::new("myfile.rs".into()),
567 abs_path: Annotated::new("/path/to".into()),
568 lineno: Annotated::new(2),
569 colno: Annotated::new(42),
570 platform: Annotated::new("rust".to_owned()),
571 pre_context: Annotated::new(vec![Annotated::new("fn main() {".to_owned())]),
572 context_line: Annotated::new("unimplemented!()".to_owned()),
573 post_context: Annotated::new(vec![Annotated::new("}".to_owned())]),
574 in_app: Annotated::new(true),
575 vars: {
576 let mut vars = Object::new();
577 vars.insert(
578 "variable".to_owned(),
579 Annotated::new(Value::String("value".to_owned())),
580 );
581 Annotated::new(vars.into())
582 },
583 data: Annotated::new(FrameData {
584 sourcemap: Annotated::new("http://example.com/invalid.map".to_owned()),
585 ..Default::default()
586 }),
587 image_addr: Annotated::new(Addr(0x400)),
588 instruction_addr: Annotated::new(Addr(0x404)),
589 addr_mode: Annotated::new("abs".into()),
590 function_id: Annotated::empty(),
591 symbol_addr: Annotated::new(Addr(0x404)),
592 trust: Annotated::new("69".into()),
593 lang: Annotated::new("rust".into()),
594 stack_start: Annotated::new(true),
595 lock: Annotated::new(LockReason {
596 ty: Annotated::new(LockReasonType::Waiting),
597 address: Annotated::new("0x07d7437b".to_owned()),
598 package_name: Annotated::new("io.sentry.samples".to_owned()),
599 class_name: Annotated::new("MainActivity".to_owned()),
600 thread_id: Annotated::new(ThreadId::Int(7)),
601 other: Default::default(),
602 }),
603 other: {
604 let mut vars = Object::new();
605 vars.insert(
606 "other".to_owned(),
607 Annotated::new(Value::String("value".to_owned())),
608 );
609 vars
610 },
611 });
612
613 assert_eq!(frame, Annotated::from_json(json).unwrap());
614 assert_eq!(json, frame.to_json_pretty().unwrap());
615 }
616
617 #[test]
618 fn test_frame_default_values() {
619 let json = "{}";
620 let frame = Annotated::new(Frame::default());
621
622 assert_eq!(frame, Annotated::from_json(json).unwrap());
623 assert_eq!(json, frame.to_json_pretty().unwrap());
624 }
625
626 #[test]
627 fn test_stacktrace_roundtrip() {
628 let json = r#"{
629 "frames": [
630 {
631 "function": "foobar"
632 }
633 ],
634 "registers": {
635 "cspr": "0x20000000",
636 "lr": "0x18a31aadc",
637 "pc": "0x18a310ea4",
638 "sp": "0x16fd75060"
639 },
640 "instruction_addr_adjustment": "all_but_first",
641 "lang": "rust",
642 "snapshot": false,
643 "other": "value"
644}"#;
645 let stack = Annotated::new(RawStacktrace {
646 frames: Annotated::new(vec![Annotated::new(Frame {
647 function: Annotated::new("foobar".to_owned()),
648 ..Default::default()
649 })]),
650 registers: {
651 let mut registers = Object::new();
652 registers.insert("cspr".to_owned(), Annotated::new(RegVal(0x2000_0000)));
653 registers.insert("lr".to_owned(), Annotated::new(RegVal(0x1_8a31_aadc)));
654 registers.insert("pc".to_owned(), Annotated::new(RegVal(0x1_8a31_0ea4)));
655 registers.insert("sp".to_owned(), Annotated::new(RegVal(0x1_6fd7_5060)));
656 Annotated::new(registers)
657 },
658 instruction_addr_adjustment: Annotated::new(InstructionAddrAdjustment::AllButFirst),
659 lang: Annotated::new("rust".into()),
660 snapshot: Annotated::new(false),
661 other: {
662 let mut other = Object::new();
663 other.insert(
664 "other".to_owned(),
665 Annotated::new(Value::String("value".to_owned())),
666 );
667 other
668 },
669 });
670
671 assert_eq!(stack, Annotated::from_json(json).unwrap());
672 assert_eq!(json, stack.to_json_pretty().unwrap());
673 }
674
675 #[test]
676 fn test_stacktrace_default_values() {
677 let json = r#"{
679 "frames": [
680 {}
681 ]
682}"#;
683
684 let stack = Annotated::new(RawStacktrace {
685 frames: Annotated::new(vec![Annotated::new(Frame::default())]),
686 ..Default::default()
687 });
688
689 assert_eq!(stack, Annotated::from_json(json).unwrap());
690 assert_eq!(json, stack.to_json_pretty().unwrap());
691 }
692
693 #[test]
694 fn test_frame_vars_null_preserved() {
695 let json = r#"{
696 "vars": {
697 "despacito": null
698 }
699}"#;
700 let frame = Annotated::new(Frame {
701 vars: Annotated::new({
702 let mut vars = Object::new();
703 vars.insert("despacito".to_owned(), Annotated::empty());
704 vars.into()
705 }),
706 ..Default::default()
707 });
708
709 assert_eq!(Annotated::from_json(json).unwrap(), frame);
710 assert_eq!(json, frame.to_json_pretty().unwrap());
711 }
712
713 #[test]
714 fn test_frame_vars_empty_annotated_is_serialized() {
715 let output = r#"{
716 "vars": {
717 "despacito": null,
718 "despacito2": null
719 }
720}"#;
721 let frame = Annotated::new(Frame {
722 vars: Annotated::new({
723 let mut vars = Object::new();
724 vars.insert("despacito".to_owned(), Annotated::empty());
725 vars.insert("despacito2".to_owned(), Annotated::empty());
726 vars.into()
727 }),
728 ..Default::default()
729 });
730
731 assert_eq!(output, frame.to_json_pretty().unwrap());
732 }
733
734 #[test]
735 fn test_frame_empty_context_lines() {
736 let json = r#"{
737 "pre_context": [
738 ""
739 ],
740 "context_line": "",
741 "post_context": [
742 ""
743 ]
744}"#;
745
746 let frame = Annotated::new(Frame {
747 pre_context: Annotated::new(vec![Annotated::new("".to_owned())]),
748 context_line: Annotated::new("".to_owned()),
749 post_context: Annotated::new(vec![Annotated::new("".to_owned())]),
750 ..Frame::default()
751 });
752
753 assert_eq!(frame, Annotated::from_json(json).unwrap());
754 assert_eq!(json, frame.to_json_pretty().unwrap());
755 }
756
757 #[test]
758 fn test_php_frame_vars() {
759 let input = r#"{
764 "vars": ["foo", "bar", "baz", null]
765}"#;
766
767 let output = r#"{
768 "vars": {
769 "0": "foo",
770 "1": "bar",
771 "2": "baz",
772 "3": null
773 }
774}"#;
775
776 let frame = Annotated::new(Frame {
777 vars: Annotated::new({
778 let mut vars = Object::new();
779 vars.insert("0".to_owned(), Annotated::new("foo".to_owned().into()));
780 vars.insert("1".to_owned(), Annotated::new("bar".to_owned().into()));
781 vars.insert("2".to_owned(), Annotated::new("baz".to_owned().into()));
782 vars.insert("3".to_owned(), Annotated::empty());
783 vars.into()
784 }),
785 ..Default::default()
786 });
787
788 assert_eq!(frame, Annotated::from_json(input).unwrap());
789 assert_eq!(output, frame.to_json_pretty().unwrap());
790 }
791}