use serde_json::{Number, Value};
use sha2::{Digest, Sha256};
use crate::error::Error;
fn normalize_numbers(value: Value) -> Value {
const SAFE: u64 = 1u64 << 53;
match value {
Value::Object(map) => Value::Object(
map.into_iter()
.map(|(k, v)| (k, normalize_numbers(v)))
.collect(),
),
Value::Array(arr) => Value::Array(arr.into_iter().map(normalize_numbers).collect()),
Value::Number(n) => {
if let Some(u) = n.as_u64() {
if u > SAFE {
return Number::from_f64(u as f64)
.map(Value::Number)
.unwrap_or(Value::Number(n));
}
} else if let Some(i) = n.as_i64() {
if i.unsigned_abs() > SAFE {
return Number::from_f64(i as f64)
.map(Value::Number)
.unwrap_or(Value::Number(n));
}
}
Value::Number(n)
}
other => other,
}
}
pub fn canonical(value: &Value) -> Result<Vec<u8>, Error> {
let normalized = normalize_numbers(value.clone());
serde_jcs::to_string(&normalized)
.map(|s| s.into_bytes())
.map_err(|e| Error::Invalid(format!("jcs: {e}")))
}
pub fn sha256_hash(value: &Value) -> Result<String, Error> {
let bytes = canonical(value)?;
let digest = Sha256::digest(&bytes);
Ok(format!("sha256:{}", hex_lower(&digest)))
}
pub fn hex_lower(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{:02x}", b));
}
s
}