use serde_json::{Map, Number, Value};
const UTF16: &str = "$utf16";
const F64: &str = "$f64";
const REPEAT: &str = "$repeat";
pub(crate) fn decode_f64(value: Value) -> Value {
try_decode_f64(value).unwrap_or_else(|e| panic!("{e}"))
}
pub(crate) fn decode_utf16(value: Value) -> Value {
try_decode_utf16(value).unwrap_or_else(|e| panic!("{e}"))
}
pub(crate) fn expand(value: Value) -> Value {
try_expand(value).unwrap_or_else(|e| panic!("{e}"))
}
pub(super) fn try_decode_f64(value: Value) -> Result<Value, String> {
rewrite(value, F64, &|marker| {
let bits = marker
.as_str()
.filter(|hex| {
hex.len() == 16 && hex.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
})
.and_then(|hex| u64::from_str_radix(hex, 16).ok())
.ok_or("16 lowercase hex digits")?;
Number::from_f64(f64::from_bits(bits))
.map(Value::Number)
.ok_or_else(|| "a finite number".to_owned())
})
}
fn try_decode_utf16(value: Value) -> Result<Value, String> {
rewrite(value, UTF16, &|marker| {
let units = marker
.as_array()
.and_then(|items| {
items
.iter()
.map(|unit| unit.as_u64().and_then(|n| u16::try_from(n).ok()))
.collect::<Option<Vec<u16>>>()
})
.ok_or("an array of UTF-16 code units")?;
Ok(Value::String(
char::decode_utf16(units).filter_map(Result::ok).collect(),
))
})
}
pub(super) fn try_expand(value: Value) -> Result<Value, String> {
rewrite(
value,
REPEAT,
&|marker| match marker.as_array().map(Vec::as_slice) {
Some([Value::String(unit), count]) => count
.as_u64()
.and_then(|n| usize::try_from(n).ok())
.map(|n| Value::String(unit.repeat(n)))
.ok_or_else(|| "a non-negative integer count".to_owned()),
_ => Err("[\"<unit>\", <count>]".to_owned()),
},
)
}
fn rewrite(
value: Value,
marker: &str,
decode: &dyn Fn(&Value) -> Result<Value, String>,
) -> Result<Value, String> {
match value {
Value::Array(items) => items
.into_iter()
.map(|item| rewrite(item, marker, decode))
.collect::<Result<_, _>>()
.map(Value::Array),
Value::Object(map) if map.contains_key(marker) => {
let content = match map.len() {
1 => map.get(marker),
_ => None,
};
content
.ok_or_else(|| "it has other keys".to_owned())
.and_then(decode)
.map_err(|why| malformed(marker, &map, &why))
}
Value::Object(map) => map
.into_iter()
.map(|(key, item)| Ok((key, rewrite(item, marker, decode)?)))
.collect::<Result<Map<_, _>, String>>()
.map(Value::Object),
other => Ok(other),
}
}
fn malformed(marker: &str, map: &Map<String, Value>, why: &str) -> String {
let text = Value::Object(map.clone()).to_string();
let shown: String = text.chars().take(200).collect();
format!("malformed {marker} marker {shown}: expected {why}")
}
mod tests {
use serde_json::{Value, json};
use super::{decode_f64, decode_utf16, expand};
fn f64_of(value: &Value) -> f64 {
value.as_f64().unwrap()
}
#[test]
fn decode_f64_gives_negative_zero() {
let out = decode_f64(json!({"$f64": "8000000000000000"}));
assert!(out.is_f64(), "{out}");
assert_eq!(f64_of(&out).to_bits(), (-0.0f64).to_bits());
}
#[test]
fn decode_f64_gives_the_exact_bits() {
let out = decode_f64(json!({"$f64": "3fd3333333333334"}));
assert_eq!(f64_of(&out).to_bits(), (0.1f64 + 0.2).to_bits());
}
#[test]
fn decode_f64_recurses_and_leaves_other_values_alone() {
let out = decode_f64(json!({
"a": [1, {"$f64": "8000000000000000"}, "x"],
"b": {"c": {"$f64": "3fd3333333333334"}},
"u": {"$utf16": [97, 55357]},
"r": {"$repeat": ["ab", 2]},
}));
assert_eq!(f64_of(&out["a"][1]).to_bits(), (-0.0f64).to_bits());
assert_eq!(f64_of(&out["b"]["c"]).to_bits(), (0.1f64 + 0.2).to_bits());
assert_eq!(out["a"][0], json!(1));
assert_eq!(out["a"][2], json!("x"));
assert_eq!(out["u"], json!({"$utf16": [97, 55357]}));
assert_eq!(out["r"], json!({"$repeat": ["ab", 2]}));
}
#[test]
fn decode_utf16_drops_an_unpaired_surrogate() {
assert_eq!(decode_utf16(json!({"$utf16": [97, 55357]})), json!("a"));
}
#[test]
fn decode_utf16_keeps_a_surrogate_pair() {
assert_eq!(
decode_utf16(json!({"$utf16": [55357, 56832, 98]})),
json!("😀b")
);
}
#[test]
fn decode_utf16_drops_every_unpaired_surrogate_whatever_its_position() {
let units = [56832, 97, 55357, 98, 55357, 56832, 55357];
assert_eq!(decode_utf16(json!({"$utf16": units})), json!("ab😀"));
}
#[test]
fn decode_utf16_recurses_and_leaves_other_values_alone() {
let out = decode_utf16(json!({
"a": [{"$utf16": [97, 55357]}, 2],
"b": {"c": {"$utf16": [56832]}},
"f": {"$f64": "8000000000000000"},
}));
assert_eq!(
out,
json!({"a": ["a", 2], "b": {"c": ""}, "f": {"$f64": "8000000000000000"}})
);
}
#[test]
fn expand_repeats_the_unit() {
assert_eq!(
expand(json!({"a": [{"$repeat": ["ab", 3]}]})),
json!({"a": ["ababab"]})
);
}
#[test]
fn expand_recurses_and_leaves_other_values_alone() {
let out = expand(json!([
{"$repeat": ["😀", 2]},
{"b": {"$repeat": ["x", 0]}},
{"$f64": "8000000000000000"},
"plain",
]));
assert_eq!(
out,
json!(["😀😀", {"b": ""}, {"$f64": "8000000000000000"}, "plain"])
);
}
#[test]
fn decoding_keeps_the_key_order() {
let input = json!({"z": {"$f64": "8000000000000000"}, "a": {"$utf16": [97]}, "m": {"$repeat": ["q", 1]}});
let keys = |v: &Value| v.as_object().unwrap().keys().cloned().collect::<Vec<_>>();
for out in [
decode_f64(input.clone()),
decode_utf16(input.clone()),
expand(input),
] {
assert_eq!(keys(&out), ["z", "a", "m"]);
}
}
#[test]
#[should_panic(expected = "$f64")]
fn a_malformed_f64_marker_panics() {
let _ = decode_f64(json!({"$f64": "-0"}));
}
#[test]
#[should_panic(expected = "$utf16")]
fn a_malformed_utf16_marker_panics() {
let _ = decode_utf16(json!({"$utf16": [65536]}));
}
#[test]
#[should_panic(expected = "$repeat")]
fn a_malformed_repeat_marker_panics() {
let _ = expand(json!({"$repeat": ["ab", -1]}));
}
}