diff --git a/CHANGELOG.md b/CHANGELOG.md index 513383656..b2fc2e37d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ ### Fixes - Restored the reqwest transport's pre-0.13 protocol features by disabling HTTP/2 and native-TLS ALPN ([#1258](https://github.com/getsentry/sentry-rust/pull/1258)). +- Added support for source map debug images and preserved unknown debug image types when deserializing events. ## 0.48.5 diff --git a/sentry-types/src/protocol/v7.rs b/sentry-types/src/protocol/v7.rs index f05394258..9b9e9eaba 100644 --- a/sentry-types/src/protocol/v7.rs +++ b/sentry-types/src/protocol/v7.rs @@ -908,8 +908,7 @@ pub struct SystemSdkInfo { } /// Represents a debug image. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -#[serde(rename_all = "snake_case", tag = "type")] +#[derive(Debug, Clone, PartialEq)] pub enum DebugImage { /// Apple debug images (machos). This is currently also used for /// non apple platforms with similar debug setups. @@ -921,6 +920,76 @@ pub enum DebugImage { /// Image used for WebAssembly. Their structure is identical to other native /// images. Wasm(WasmDebugImage), + /// A source map debug image. + SourceMap(SourceMapDebugImage), + /// A debug image type that is not yet known to this SDK. + Other(Map), +} + +impl Serialize for DebugImage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + #[serde(rename_all = "snake_case", tag = "type")] + enum TaggedDebugImage<'a> { + Apple(&'a AppleDebugImage), + Symbolic(&'a SymbolicDebugImage), + Proguard(&'a ProguardDebugImage), + Wasm(&'a WasmDebugImage), + #[serde(rename = "sourcemap")] + SourceMap(&'a SourceMapDebugImage), + } + + match self { + Self::Apple(image) => TaggedDebugImage::Apple(image).serialize(serializer), + Self::Symbolic(image) => TaggedDebugImage::Symbolic(image).serialize(serializer), + Self::Proguard(image) => TaggedDebugImage::Proguard(image).serialize(serializer), + Self::Wasm(image) => TaggedDebugImage::Wasm(image).serialize(serializer), + Self::SourceMap(image) => TaggedDebugImage::SourceMap(image).serialize(serializer), + Self::Other(image) => image.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for DebugImage { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = Value::deserialize(deserializer)?; + let Value::Object(mut image) = value else { + return Err(de::Error::custom("debug image must be an object")); + }; + let type_name = image + .get("type") + .ok_or_else(|| de::Error::missing_field("type"))? + .as_str() + .ok_or_else(|| de::Error::custom("debug image type must be a string"))? + .to_owned(); + + match type_name.as_str() { + "apple" => deserialize_debug_image(&mut image, Self::Apple), + "symbolic" => deserialize_debug_image(&mut image, Self::Symbolic), + "proguard" => deserialize_debug_image(&mut image, Self::Proguard), + "wasm" => deserialize_debug_image(&mut image, Self::Wasm), + "sourcemap" => deserialize_debug_image(&mut image, Self::SourceMap), + _ => Ok(Self::Other(image.into_iter().collect())), + } + .map_err(de::Error::custom) + } +} + +fn deserialize_debug_image( + image: &mut serde_json::Map, + wrap: impl FnOnce(T) -> DebugImage, +) -> serde_json::Result +where + T: serde::de::DeserializeOwned, +{ + image.remove("type"); + serde_json::from_value(Value::Object(std::mem::take(image))).map(wrap) } impl DebugImage { @@ -931,6 +1000,11 @@ impl DebugImage { DebugImage::Symbolic(..) => "symbolic", DebugImage::Proguard(..) => "proguard", DebugImage::Wasm(..) => "wasm", + DebugImage::SourceMap(..) => "sourcemap", + DebugImage::Other(ref image) => image + .get("type") + .and_then(Value::as_str) + .unwrap_or("unknown"), } } } @@ -1036,10 +1110,26 @@ pub struct WasmDebugImage { pub code_file: String, } +/// Represents a source map debug image. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct SourceMapDebugImage { + /// The absolute path or URL to the minified JavaScript file. + pub code_file: String, + /// Unique debug identifier of the source map. + pub debug_id: DebugId, + /// Path or URL to the associated source map. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub debug_file: Option, + /// Additional arbitrary fields for forwards compatibility. + #[serde(flatten)] + pub other: Map, +} + into_debug_image!(Apple, AppleDebugImage); into_debug_image!(Symbolic, SymbolicDebugImage); into_debug_image!(Proguard, ProguardDebugImage); into_debug_image!(Wasm, WasmDebugImage); +into_debug_image!(SourceMap, SourceMapDebugImage); /// Represents debug meta information. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] diff --git a/sentry-types/tests/test_protocol_v7.rs b/sentry-types/tests/test_protocol_v7.rs index 841cf19d1..fb1797b72 100644 --- a/sentry-types/tests/test_protocol_v7.rs +++ b/sentry-types/tests/test_protocol_v7.rs @@ -815,6 +815,49 @@ mod test_debug_meta { \"8c954262-f905-4992-8a61-f60825f4553b\"}]}}" ); } + + #[test] + fn test_sourcemap_debug_image_roundtrip() { + let json = serde_json::json!({ + "type": "sourcemap", + "code_file": "https://example.com/static/app.min.js", + "debug_id": "395835f4-03e0-4436-80d3-136f0749a893", + "debug_file": "https://example.com/static/app.min.js.map", + "source": "browser", + }); + + let image: v7::DebugImage = serde_json::from_value(json.clone()).unwrap(); + + let v7::DebugImage::SourceMap(ref source_map) = image else { + panic!("expected a source map debug image"); + }; + assert!(!source_map.other.contains_key("type")); + assert_eq!(image.type_name(), "sourcemap"); + assert_eq!( + serde_json::to_string(&image) + .unwrap() + .match_indices("\"type\"") + .count(), + 1 + ); + assert_eq!(serde_json::to_value(image).unwrap(), json); + } + + #[test] + fn test_unknown_debug_image_roundtrip() { + let json = serde_json::json!({ + "type": "future_image", + "debug_id": "395835f4-03e0-4436-80d3-136f0749a893", + "metadata": { + "revision": 2, + }, + }); + + let image: v7::DebugImage = serde_json::from_value(json.clone()).unwrap(); + + assert_eq!(image.type_name(), "future_image"); + assert_eq!(serde_json::to_value(image).unwrap(), json); + } } mod test_exception {