diff --git a/CHANGELOG.md b/CHANGELOG.md index 396ed04..0ed3c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 9.3.0 + +### New Features ✨ + +- feat(debugId): Serialize source maps with `debugId`, not `debug_id` by @szokeasaurusrex in [#134](https://github.com/getsentry/rust-sourcemap/pull/134) + +### Build / dependencies / internal 🔧 + +- chore: Fix 1.88.0 clippy lints by @loewenheim in [#130](https://github.com/getsentry/rust-sourcemap/pull/130) + +### Other + +- Store SourceView linecache as offsets rather than pointers by @coolreader18 in [#133](https://github.com/getsentry/rust-sourcemap/pull/133) + ## 9.2.2 ### Various fixes & improvements diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5878c26..ad276e7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cli" -version = "9.2.2" +version = "9.3.0" authors = ["Armin Ronacher "] edition = "2018" diff --git a/src/decoder.rs b/src/decoder.rs index ca9eac2..4d82a1b 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -267,9 +267,7 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result { let mut sm = SourceMap::new(file, tokens, names, sources, source_content); sm.set_source_root(rsm.source_root); - // Use _debug_id_new (from "debugId" key) only if debug_id - // from ( "debug_id" key) is unset - sm.set_debug_id(rsm.debug_id.or(rsm._debug_id_new)); + sm.set_debug_id(rsm.debug_id.into()); if let Some(ignore_list) = rsm.ignore_list { for idx in ignore_list { sm.add_to_ignore_list(idx); @@ -307,7 +305,7 @@ fn decode_index(rsm: RawSourceMap) -> Result { rsm.x_facebook_offsets, rsm.x_metro_module_paths, ) - .with_debug_id(rsm._debug_id_new.or(rsm.debug_id))) + .with_debug_id(rsm.debug_id.into())) } fn decode_common(rsm: RawSourceMap) -> Result { @@ -419,8 +417,7 @@ mod tests { x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, - debug_id: None, - _debug_id_new: None, + debug_id: None.into(), }; let decoded = decode_common(raw).expect("should decoded"); @@ -448,40 +445,7 @@ mod tests { x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, - debug_id: None, - _debug_id_new: Some(DEBUG_ID.parse().expect("valid debug id")), - }; - - let decoded = decode_common(raw).expect("should decode"); - assert_eq!( - decoded, - DecodedMap::Index( - SourceMapIndex::new(Some("test.js".into()), vec![]) - .with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id"))) - ) - ); - } - - #[test] - fn test_decode_sourcemap_index_debug_id_from_legacy_key() { - const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef"; - - let raw = RawSourceMap { - version: Some(3), - file: Some("test.js".into()), - sources: None, - source_root: None, - sources_content: None, - sections: Some(vec![]), - names: None, - range_mappings: None, - mappings: None, - ignore_list: None, - x_facebook_offsets: None, - x_metro_module_paths: None, - x_facebook_sources: None, - debug_id: Some(DEBUG_ID.parse().expect("valid debug id")), - _debug_id_new: None, + debug_id: Some(DEBUG_ID.parse().expect("valid debug id")).into(), }; let decoded = decode_common(raw).expect("should decode"); diff --git a/src/encoder.rs b/src/encoder.rs index 042e5c3..f6f9529 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -178,8 +178,7 @@ impl Encodable for SourceMap { x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, - debug_id: self.get_debug_id(), - _debug_id_new: None, + debug_id: self.get_debug_id().into(), } } } @@ -213,9 +212,7 @@ impl Encodable for SourceMapIndex { x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, - debug_id: None, - // Put the debug ID on _debug_id_new to serialize it to the debugId field. - _debug_id_new: self.debug_id(), + debug_id: self.debug_id().into(), } } } @@ -278,8 +275,7 @@ mod tests { x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, - debug_id: None, - _debug_id_new: None, + debug_id: None.into(), } ); } @@ -308,8 +304,7 @@ mod tests { x_facebook_offsets: None, x_metro_module_paths: None, x_facebook_sources: None, - debug_id: None, - _debug_id_new: Some(DEBUG_ID.parse().expect("valid debug id")), + debug_id: Some(DEBUG_ID.parse().expect("valid debug id")).into(), } ); } diff --git a/src/jsontypes.rs b/src/jsontypes.rs index 35f896a..4692ff1 100644 --- a/src/jsontypes.rs +++ b/src/jsontypes.rs @@ -1,8 +1,9 @@ use bytes_str::BytesStr; use debugid::DebugId; use serde::de::IgnoredAny; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; +use std::fmt::Debug; #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct RawSectionOffset { @@ -55,12 +56,8 @@ pub struct RawSourceMap { pub x_metro_module_paths: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub x_facebook_sources: FacebookSources, - #[serde(skip_serializing_if = "Option::is_none")] - pub debug_id: Option, - // This field only exists to be able to deserialize from "debugId" keys - // if "debug_id" is unset. - #[serde(skip_serializing_if = "Option::is_none", rename = "debugId")] - pub(crate) _debug_id_new: Option, + #[serde(flatten)] + pub debug_id: DebugIdField, } #[derive(Deserialize)] @@ -76,3 +73,91 @@ pub struct MinimalRawSourceMap { pub names: Option, pub mappings: Option, } + +/// This struct represents a `RawSourceMap`'s debug ID fields. +/// +/// The reason this exists as a seperate struct is so that we can have custom deserialization +/// logic, which can read both the legacy snake_case debug_id and the new camelCase debugId +/// fields. In case both are provided, the camelCase field takes precedence. +/// +/// The field is always serialized as `debugId`. +#[derive(Serialize, Clone, PartialEq, Debug, Default)] +pub(crate) struct DebugIdField { + #[serde(rename = "debugId", skip_serializing_if = "Option::is_none")] + value: Option, +} + +impl<'de> Deserialize<'de> for DebugIdField { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // We cannot use serde(alias), as that would cause an error when both fields are present. + + #[derive(Deserialize)] + struct Helper { + #[serde(rename = "debugId")] + camel: Option, + #[serde(rename = "debug_id")] + legacy: Option, + } + + let Helper { camel, legacy } = Helper::deserialize(deserializer)?; + Ok(camel.or(legacy).into()) + } +} + +impl From> for DebugIdField { + fn from(value: Option) -> Self { + Self { value } + } +} + +impl From for Option { + fn from(value: DebugIdField) -> Self { + value.value + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn parse_debug_id(input: &str) -> DebugId { + input.parse().expect("valid debug id") + } + + fn empty_sourcemap() -> RawSourceMap { + serde_json::from_value::(serde_json::json!({})) + .expect("can deserialize empty JSON to RawSourceMap") + } + + #[test] + fn raw_sourcemap_serializes_camel_case_debug_id() { + let camel = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + let raw = RawSourceMap { + debug_id: Some(parse_debug_id(camel)).into(), + ..empty_sourcemap() + }; + + let value = serde_json::to_value(raw).expect("should serialize without error"); + let obj = value.as_object().expect("should be an object"); + assert!(obj.get("debug_id").is_none()); + assert_eq!(obj.get("debugId"), Some(&json!(parse_debug_id(camel)))); + } + + #[test] + fn raw_sourcemap_prefers_camel_case_on_deserialize() { + let legacy = "ffffffffffffffffffffffffffffffff"; + let camel = "00000000000000000000000000000000"; + let json = serde_json::json!({ + "debug_id": legacy, + "debugId": camel + }); + let raw: RawSourceMap = + serde_json::from_value(json).expect("can deserialize as RawSourceMap"); + let value: Option = raw.debug_id.into(); + assert_eq!(value, Some(parse_debug_id(camel))); + } +} diff --git a/src/types.rs b/src/types.rs index c2a8b04..0b1de26 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1665,8 +1665,8 @@ mod tests { "sources":["coolstuff.js"], "names":["x","alert"], "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM", - "debug_id":"00000000-0000-0000-0000-000000000000", - "debugId": "11111111-1111-1111-1111-111111111111" + "debug_id": "11111111-1111-1111-1111-111111111111", + "debugId":"00000000-0000-0000-0000-000000000000" }"#; let sm = SourceMap::from_slice(input).unwrap(); diff --git a/tests/test_encoder.rs b/tests/test_encoder.rs index 26c594d..dc6600f 100644 --- a/tests/test_encoder.rs +++ b/tests/test_encoder.rs @@ -62,3 +62,35 @@ fn test_empty_range() { let out = String::from_utf8(out).unwrap(); assert!(!out.contains("rangeMappings")); } + +#[test] +fn test_sourcemap_serializes_camel_case_debug_id() { + const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef"; + let input = format!( + r#"{{ + "version": 3, + "sources": [], + "names": [], + "mappings": "", + "debug_id": "{}" + }}"#, + DEBUG_ID + ); + + let sm = SourceMap::from_reader(input.as_bytes()).unwrap(); + let expected = sm.get_debug_id().expect("debug id parsed").to_string(); + let mut out: Vec = vec![]; + sm.to_writer(&mut out).unwrap(); + let serialized = String::from_utf8(out).unwrap(); + + assert!( + serialized.contains(&format!(r#""debugId":"{}""#, expected)), + "expected camelCase debugId in {}", + serialized + ); + assert!( + !serialized.contains("debug_id"), + "unexpected snake_case key in {}", + serialized + ); +} diff --git a/tests/test_index.rs b/tests/test_index.rs index 45358d0..a485adc 100644 --- a/tests/test_index.rs +++ b/tests/test_index.rs @@ -205,3 +205,33 @@ fn test_flatten_indexed_sourcemap_with_ignore_list() { vec![1] ); } + +#[test] +fn test_sourcemap_index_serializes_camel_case_debug_id() { + const DEBUG_ID: &str = "fedcba9876543210fedcba9876543210"; + let input = format!( + r#"{{ + "version": 3, + "file": "bundle.js", + "sections": [], + "debugId": "{}" + }}"#, + DEBUG_ID + ); + + let smi = SourceMapIndex::from_reader(input.as_bytes()).unwrap(); + let mut out = Vec::new(); + smi.to_writer(&mut out).unwrap(); + let serialized = String::from_utf8(out).unwrap(); + + assert!( + serialized.contains(r#""debugId":"#), + "expected camelCase debugId in {}", + serialized + ); + assert!( + !serialized.contains("debug_id"), + "unexpected snake_case key in {}", + serialized + ); +}