Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
94 changes: 92 additions & 2 deletions sentry-types/src/protocol/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<String, Value>),
}

impl Serialize for DebugImage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<T>(
image: &mut serde_json::Map<String, Value>,
wrap: impl FnOnce(T) -> DebugImage,
) -> serde_json::Result<DebugImage>
where
T: serde::de::DeserializeOwned,
{
image.remove("type");
serde_json::from_value(Value::Object(std::mem::take(image))).map(wrap)
}

impl DebugImage {
Expand All @@ -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"),
}
}
}
Expand Down Expand Up @@ -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<String>,
/// Additional arbitrary fields for forwards compatibility.
#[serde(flatten)]
pub other: Map<String, Value>,
}

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)]
Expand Down
43 changes: 43 additions & 0 deletions sentry-types/tests/test_protocol_v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading