use crate::error::{Error, Result};
use serde::Serialize;
use serde_json::Value;
use std::collections::BTreeMap;
use std::fmt::Write as _;
fn format_f64(x: f64) -> String {
const GRID_RESOLUTION_LIMIT: f64 = 45_035_996.273_704_96;
let x = if x == 0.0 { 0.0 } else { x };
if x.abs() >= GRID_RESOLUTION_LIMIT {
return format!("{x}");
}
let mut s = format!("{x:.8}");
if s.contains('.') {
while s.ends_with('0') {
s.pop();
}
if s.ends_with('.') {
s.pop();
}
}
if s == "-0" {
s.clear();
s.push('0');
}
s
}
fn write_number(out: &mut String, n: &serde_json::Number) {
if let Some(i) = n.as_i64() {
write!(out, "{i}").expect("writing to a String is infallible");
} else if let Some(u) = n.as_u64() {
write!(out, "{u}").expect("writing to a String is infallible");
} else {
let f = n.as_f64().unwrap_or(0.0);
out.push_str(&format_f64(f));
}
}
fn write_string(out: &mut String, s: &str) {
let encoded = Value::String(s.to_string()).to_string();
out.push_str(&encoded);
}
fn write_value(out: &mut String, value: &Value) {
match value {
Value::Null => out.push_str("null"),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Number(n) => write_number(out, n),
Value::String(s) => write_string(out, s),
Value::Array(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_value(out, item);
}
out.push(']');
}
Value::Object(map) => {
let sorted: BTreeMap<&String, &Value> = map.iter().collect();
out.push('{');
for (i, (key, val)) in sorted.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_string(out, key);
out.push(':');
write_value(out, val);
}
out.push('}');
}
}
}
fn canonicalize_value(value: &Value) -> String {
let mut out = String::new();
write_value(&mut out, value);
out
}
pub fn canonicalize<T: Serialize>(value: &T) -> Result<String> {
let json = serde_json::to_value(value).map_err(|e| Error::BadCase(e.to_string()))?;
Ok(canonicalize_value(&json))
}
#[must_use]
pub fn hash(canonical: &str) -> String {
blake3::hash(canonical.as_bytes()).to_hex().to_string()
}
pub fn hash_report<T: Serialize>(value: &T) -> Result<String> {
Ok(hash(&canonicalize(value)?))
}