use serde_json::Value;
use sha2::{Digest, Sha256};
use crate::error::Error;
pub fn canonical_json(value: &Value) -> Vec<u8> {
let mut out = Vec::with_capacity(64);
write_value(&mut out, value);
out
}
pub fn sha256_hex(value: &Value) -> String {
let bytes = canonical_json(value);
let digest = Sha256::digest(&bytes);
let mut s = String::with_capacity(64);
for b in digest {
s.push_str(&format!("{:02x}", b));
}
s
}
pub fn parse(json: &str) -> Result<Value, Error> {
serde_json::from_str(json).map_err(|e| Error::InvalidJson(e.to_string()))
}
fn write_value(out: &mut Vec<u8>, value: &Value) {
match value {
Value::Null => out.extend_from_slice(b"null"),
Value::Bool(true) => out.extend_from_slice(b"true"),
Value::Bool(false) => out.extend_from_slice(b"false"),
Value::Number(n) => out.extend_from_slice(n.to_string().as_bytes()),
Value::String(s) => write_string(out, s),
Value::Array(arr) => {
out.push(b'[');
for (i, v) in arr.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_value(out, v);
}
out.push(b']');
}
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort_by(|a, b| utf16_cmp(a, b));
out.push(b'{');
for (i, k) in keys.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_string(out, k);
out.push(b':');
write_value(out, &map[k.as_str()]);
}
out.push(b'}');
}
}
}
fn utf16_cmp(a: &str, b: &str) -> std::cmp::Ordering {
let mut ai = a.encode_utf16();
let mut bi = b.encode_utf16();
loop {
match (ai.next(), bi.next()) {
(Some(x), Some(y)) => match x.cmp(&y) {
std::cmp::Ordering::Equal => continue,
non_eq => return non_eq,
},
(Some(_), None) => return std::cmp::Ordering::Greater,
(None, Some(_)) => return std::cmp::Ordering::Less,
(None, None) => return std::cmp::Ordering::Equal,
}
}
}
fn write_string(out: &mut Vec<u8>, s: &str) {
out.push(b'"');
for ch in s.chars() {
match ch {
'"' => out.extend_from_slice(b"\\\""),
'\\' => out.extend_from_slice(b"\\\\"),
'\u{0008}' => out.extend_from_slice(b"\\b"),
'\u{0009}' => out.extend_from_slice(b"\\t"),
'\u{000A}' => out.extend_from_slice(b"\\n"),
'\u{000C}' => out.extend_from_slice(b"\\f"),
'\u{000D}' => out.extend_from_slice(b"\\r"),
c if (c as u32) < 0x20 => {
let buf = format!("\\u{:04x}", c as u32);
out.extend_from_slice(buf.as_bytes());
}
c => {
let mut buf = [0u8; 4];
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
}
}
out.push(b'"');
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn empty_object() {
assert_eq!(canonical_json(&json!({})), b"{}");
}
#[test]
fn sorts_keys() {
assert_eq!(
canonical_json(&json!({"b": 2, "a": 1})),
br#"{"a":1,"b":2}"#
);
}
#[test]
fn unicode_literal() {
let v = json!({"topic": "café"});
assert_eq!(canonical_json(&v), "{\"topic\":\"café\"}".as_bytes());
}
#[test]
fn string_escapes() {
let v = json!({"s": "line1\nline2\t\"quoted\""});
assert_eq!(canonical_json(&v), br#"{"s":"line1\nline2\t\"quoted\""}"#);
}
}