Skip to content
Merged
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
44 changes: 4 additions & 40 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,7 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {

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);
Expand Down Expand Up @@ -307,7 +305,7 @@ fn decode_index(rsm: RawSourceMap) -> Result<SourceMapIndex> {
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<DecodedMap> {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
13 changes: 4 additions & 9 deletions src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
}
Expand Down Expand Up @@ -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(),
}
}
}
Expand Down Expand Up @@ -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(),
}
);
}
Expand Down Expand Up @@ -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(),
}
);
}
Expand Down
99 changes: 92 additions & 7 deletions src/jsontypes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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 {
Expand Down Expand Up @@ -54,12 +55,8 @@ pub struct RawSourceMap {
pub x_metro_module_paths: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x_facebook_sources: FacebookSources,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_id: Option<DebugId>,
// 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<DebugId>,
#[serde(flatten)]
pub debug_id: DebugIdField,
}

#[derive(Deserialize)]
Expand All @@ -75,3 +72,91 @@ pub struct MinimalRawSourceMap {
pub names: Option<IgnoredAny>,
pub mappings: Option<IgnoredAny>,
}

/// 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<DebugId>,
}

impl<'de> Deserialize<'de> for DebugIdField {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<DebugId>,
#[serde(rename = "debug_id")]
legacy: Option<DebugId>,
}

let Helper { camel, legacy } = Helper::deserialize(deserializer)?;
Ok(camel.or(legacy).into())
}
}

impl From<Option<DebugId>> for DebugIdField {
fn from(value: Option<DebugId>) -> Self {
Self { value }
}
}

impl From<DebugIdField> for Option<DebugId> {
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::<RawSourceMap>(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<DebugId> = raw.debug_id.into();
assert_eq!(value, Some(parse_debug_id(camel)));
}
}
4 changes: 2 additions & 2 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,8 +1441,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();
Expand Down
32 changes: 32 additions & 0 deletions tests/test_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = 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
);
}
30 changes: 30 additions & 0 deletions tests/test_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}