diff --git a/src/protocol/v7.rs b/src/protocol/v7.rs index 11a23006..113ec41b 100644 --- a/src/protocol/v7.rs +++ b/src/protocol/v7.rs @@ -169,7 +169,7 @@ pub struct Stacktrace { #[serde(untagged)] pub enum ThreadId { /// Integer representation for the thread id - Int(i64), + Int(u64), /// String representation for the thread id String(String), } @@ -180,6 +180,36 @@ impl Default for ThreadId { } } +impl<'a> From<&'a str> for ThreadId { + fn from(id: &'a str) -> ThreadId { + ThreadId::String(id.to_string()) + } +} + +impl From for ThreadId { + fn from(id: String) -> ThreadId { + ThreadId::String(id) + } +} + +impl From for ThreadId { + fn from(id: i64) -> ThreadId { + ThreadId::Int(id as u64) + } +} + +impl From for ThreadId { + fn from(id: u32) -> ThreadId { + ThreadId::Int(id as u64) + } +} + +impl From for ThreadId { + fn from(id: u16) -> ThreadId { + ThreadId::Int(id as u64) + } +} + impl fmt::Display for ThreadId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -229,21 +259,33 @@ impl Into for Addr { } } +fn is_false(value: &bool) -> bool { + *value == false +} + /// Represents a single thread. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(default)] pub struct Thread { /// The optional ID of the thread (usually an integer) + #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, /// The optional name of the thread. + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, /// If the thread suspended or crashed a stacktrace can be /// attached here. + #[serde(skip_serializing_if = "Option::is_none")] pub stacktrace: Option, + /// Optional raw stacktrace. + #[serde(skip_serializing_if = "Option::is_none")] + pub raw_stacktrace: Option, /// indicates a crashed thread + #[serde(skip_serializing_if = "is_false")] pub crashed: bool, /// indicates that the thread was not suspended when the /// event was created. + #[serde(skip_serializing_if = "is_false")] pub current: bool, } @@ -263,6 +305,9 @@ pub struct Exception { /// Optionally the stacktrace. #[serde(skip_serializing_if = "Option::is_none")] pub stacktrace: Option, + /// An optional raw stacktrace. + #[serde(skip_serializing_if = "Option::is_none")] + pub raw_stacktrace: Option, } /// Represents the level of severity of an event or breadcrumb diff --git a/tests/test_protocol_v7.rs b/tests/test_protocol_v7.rs index 14baae32..67334a4e 100644 --- a/tests/test_protocol_v7.rs +++ b/tests/test_protocol_v7.rs @@ -358,6 +358,46 @@ fn test_template_info() { ); } +#[test] +fn test_threads() { + let event = v7::Event { + threads: vec![ + v7::Thread { + id: Some("#1".into()), + name: Some("Awesome Thread".into()), + ..Default::default() + } + ], + ..Default::default() + }; + + assert_roundtrip(&event); + assert_eq!( + serde_json::to_string(&event).unwrap(), + "{\"threads\":{\"values\":[{\"id\":\"#1\",\"name\":\"Awesome Thread\"}]}}" + ); + + let event = v7::Event { + threads: vec![ + v7::Thread { + id: Some(42u32.into()), + name: Some("Awesome Thread".into()), + crashed: true, + current: true, + ..Default::default() + } + ], + ..Default::default() + }; + + assert_roundtrip(&event); + assert_eq!( + serde_json::to_string(&event).unwrap(), + "{\"threads\":{\"values\":[{\"id\":42,\"name\":\"Awesome Thread\",\ + \"crashed\":true,\"current\":true}]}}" + ); +} + #[test] fn test_request() { let event = v7::Event { @@ -494,6 +534,7 @@ fn test_minimal_exception_stacktrace() { ], ..Default::default() }), + raw_stacktrace: None, }], ..Default::default() }; @@ -541,6 +582,7 @@ fn test_slightly_larger_exception_stacktrace() { ], ..Default::default() }), + raw_stacktrace: None, }], ..Default::default() }; @@ -597,6 +639,20 @@ fn test_full_exception_stacktrace() { ], frames_omitted: Some((1, 2)), }), + raw_stacktrace: Some(v7::Stacktrace { + frames: vec![ + v7::Frame { + function: Some("main".into()), + instruction_info: v7::InstructionInfo { + image_addr: Some(v7::Addr(0)), + instruction_addr: Some(v7::Addr(0)), + symbol_addr: Some(v7::Addr(0)), + }, + ..Default::default() + }, + ], + frames_omitted: Some((1, 2)), + }), }], ..Default::default() }; @@ -604,15 +660,17 @@ fn test_full_exception_stacktrace() { assert_roundtrip(&event); assert_eq!( serde_json::to_string(&event).unwrap(), - "{\"exception\":{\"values\":[{\"type\":\"DivisionByZero\",\ - \"value\":\"integer division or modulo by zero\",\"module\":\ - \"x\",\"stacktrace\":{\"frames\":[{\"function\":\"main\",\"symbol\":\ - \"main\",\"module\":\"hello\",\"package\":\"hello.whl\",\"filename\":\ - \"hello.py\",\"abs_path\":\"/app/hello.py\",\"lineno\":7,\"\ - colno\":42,\"pre_context\":[\"foo\",\"bar\"],\"context_line\":\ - \"hey hey hey\",\"post_context\":[\"foo\",\"bar\"],\"in_app\":true,\ - \"vars\":{\"var\":\"value\"},\"image_addr\":\"0x0\",\"instruction_addr\":\"0x0\",\ - \"symbol_addr\":\"0x0\"}],\"frames_omitted\":[1,2]}}]}}" + "{\"exception\":{\"values\":[{\"type\":\"DivisionByZero\",\"value\":\ + \"integer division or modulo by zero\",\"module\":\"x\",\"stacktrace\":\ + {\"frames\":[{\"function\":\"main\",\"symbol\":\"main\",\"module\":\ + \"hello\",\"package\":\"hello.whl\",\"filename\":\"hello.py\",\"abs_path\"\ + :\"/app/hello.py\",\"lineno\":7,\"colno\":42,\"pre_context\":[\"foo\",\"\ + bar\"],\"context_line\":\"hey hey hey\",\"post_context\":[\"foo\",\"bar\"]\ + ,\"in_app\":true,\"vars\":{\"var\":\"value\"},\"image_addr\":\"0x0\",\ + \"instruction_addr\":\"0x0\",\"symbol_addr\":\"0x0\"}],\"frames_omitted\":\ + [1,2]},\"raw_stacktrace\":{\"frames\":[{\"function\":\"main\",\ + \"image_addr\":\"0x0\",\"instruction_addr\":\"0x0\",\"symbol_addr\":\"0x0\"}\ + ],\"frames_omitted\":[1,2]}}]}}" ); }