use relay_protocol::{Annotated, Empty, FromValue, Getter, IntoValue, Object, Val, Value};
use crate::processor::ProcessValue;
use crate::protocol::{JsonLenientString, Mechanism, RawStacktrace, Stacktrace, ThreadId};
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
#[metastructure(process_func = "process_exception", value_type = "Exception")]
pub struct Exception {
#[metastructure(field = "type", max_chars = 256, max_chars_allowance = 20)]
pub ty: Annotated<String>,
#[metastructure(max_chars = 8192, max_chars_allowance = 200, pii = "true")]
pub value: Annotated<JsonLenientString>,
#[metastructure(max_chars = 256, max_chars_allowance = 20)]
pub module: Annotated<String>,
#[metastructure(
legacy_alias = "sentry.interfaces.Stacktrace",
skip_serialization = "empty"
)]
pub stacktrace: Annotated<Stacktrace>,
#[metastructure(skip_serialization = "empty", omit_from_schema)]
pub raw_stacktrace: Annotated<RawStacktrace>,
#[metastructure(max_chars = 128)]
pub thread_id: Annotated<ThreadId>,
pub mechanism: Annotated<Mechanism>,
#[metastructure(additional_properties)]
pub other: Object<Value>,
}
impl Getter for Exception {
fn get_value(&self, path: &str) -> Option<Val<'_>> {
Some(match path {
"ty" => self.ty.as_str()?.into(),
"value" => self.value.as_str()?.into(),
_ => return None,
})
}
}
#[cfg(test)]
mod tests {
use relay_protocol::Map;
use similar_asserts::assert_eq;
use super::*;
#[test]
fn test_exception_roundtrip() {
let json = r#"{
"type": "mytype",
"value": "myvalue",
"module": "mymodule",
"thread_id": 42,
"other": "value"
}"#;
let exception = Annotated::new(Exception {
ty: Annotated::new("mytype".to_string()),
value: Annotated::new("myvalue".to_string().into()),
module: Annotated::new("mymodule".to_string()),
thread_id: Annotated::new(ThreadId::Int(42)),
other: {
let mut map = Map::new();
map.insert(
"other".to_string(),
Annotated::new(Value::String("value".to_string())),
);
map
},
..Default::default()
});
assert_eq!(exception, Annotated::from_json(json).unwrap());
assert_eq!(json, exception.to_json_pretty().unwrap());
}
#[test]
fn test_exception_default_values() {
let json = r#"{"type":"mytype"}"#;
let exception = Annotated::new(Exception {
ty: Annotated::new("mytype".to_string()),
..Default::default()
});
assert_eq!(exception, Annotated::from_json(json).unwrap());
assert_eq!(json, exception.to_json().unwrap());
}
#[test]
fn test_exception_empty_fields() {
let json = r#"{"type":"","value":""}"#;
let exception = Annotated::new(Exception {
ty: Annotated::new("".to_string()),
value: Annotated::new("".to_string().into()),
..Default::default()
});
assert_eq!(exception, Annotated::from_json(json).unwrap());
assert_eq!(json, exception.to_json().unwrap());
}
#[test]
fn test_coerces_object_value_to_string() {
let input = r#"{"value":{"unauthorized":true}}"#;
let output = r#"{"value":"{\"unauthorized\":true}"}"#;
let exception = Annotated::new(Exception {
value: Annotated::new(r#"{"unauthorized":true}"#.to_string().into()),
..Default::default()
});
assert_eq!(exception, Annotated::from_json(input).unwrap());
assert_eq!(output, exception.to_json().unwrap());
}
#[test]
fn test_explicit_none() {
let json = r#"{
"value": null,
"type": "ZeroDivisionError"
}"#;
let exception = Annotated::new(Exception {
ty: Annotated::new("ZeroDivisionError".to_string()),
..Default::default()
});
assert_eq!(exception, Annotated::from_json(json).unwrap());
assert_eq!(
r#"{"type":"ZeroDivisionError"}"#,
exception.to_json().unwrap()
);
}
}