use std::cmp::Ordering;
use serde_json::Value;
use crate::error::JcsError;
pub fn canonicalize_json(value: &Value) -> Result<String, JcsError> {
canonicalize(value, 0)
}
fn canonicalize(value: &Value, depth: usize) -> Result<String, JcsError> {
match value {
Value::Null => Ok("null".to_owned()),
Value::Bool(value) => Ok(value.to_string()),
Value::Number(value) => canonicalize_number(value),
Value::String(value) => {
serde_json::to_string(value).map_err(|_| JcsError::SerializationError)
}
Value::Array(values) => canonicalize_array(values, depth),
Value::Object(values) => {
let child_depth = descend(depth)?;
let mut keys: Vec<&String> = values.keys().collect();
keys.sort_by(|left, right| utf16_cmp(left, right));
let mut output = String::from("{");
for (index, key) in keys.iter().enumerate() {
if index > 0 {
output.push(',');
}
output.push_str(
&serde_json::to_string(key).map_err(|_| JcsError::SerializationError)?,
);
output.push(':');
output.push_str(&canonicalize(&values[*key], child_depth)?);
}
output.push('}');
Ok(output)
}
}
}
fn descend(depth: usize) -> Result<usize, JcsError> {
let next = depth.checked_add(1).ok_or(JcsError::DepthExceeded)?;
if next > crate::MAX_NESTING_DEPTH {
return Err(JcsError::DepthExceeded);
}
Ok(next)
}
fn utf16_cmp(left: &str, right: &str) -> Ordering {
left.encode_utf16().cmp(right.encode_utf16())
}
fn canonicalize_number(value: &serde_json::Number) -> Result<String, JcsError> {
if let Some(unsigned) = value.as_u64() {
return Ok(unsigned.to_string());
}
if let Some(signed) = value.as_i64() {
return Ok(signed.to_string());
}
let float = value.as_f64().ok_or(JcsError::SerializationError)?;
if !float.is_finite() {
return Err(JcsError::NonFiniteNumber);
}
let mut buffer = ryu_js::Buffer::new();
Ok(buffer.format_finite(float).to_owned())
}
fn canonicalize_array(values: &[Value], depth: usize) -> Result<String, JcsError> {
let child_depth = descend(depth)?;
let mut output = String::from("[");
for (index, item) in values.iter().enumerate() {
if index > 0 {
output.push(',');
}
output.push_str(&canonicalize(item, child_depth)?);
}
output.push(']');
Ok(output)
}