use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CanonicalFailureCode {
NanOrInf,
InvalidValue,
}
impl CanonicalFailureCode {
pub fn as_str(self) -> &'static str {
match self {
CanonicalFailureCode::NanOrInf => "NAN_OR_INF",
CanonicalFailureCode::InvalidValue => "INVALID_VALUE",
}
}
}
#[derive(Debug, Clone)]
pub struct CanonicalFailure {
pub code: CanonicalFailureCode,
pub message: String,
}
impl std::fmt::Display for CanonicalFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code.as_str(), self.message)
}
}
impl std::error::Error for CanonicalFailure {}
fn fail<T>(code: CanonicalFailureCode, message: impl Into<String>) -> Result<T, CanonicalFailure> {
Err(CanonicalFailure {
code,
message: message.into(),
})
}
pub fn py_float_repr(f: f64) -> Result<String, CanonicalFailure> {
if f.is_nan() || f.is_infinite() {
return fail(
CanonicalFailureCode::NanOrInf,
format!("non-finite float cannot be serialized: {f}"),
);
}
if f == 0.0 {
return Ok(if f.is_sign_negative() {
"-0.0".into()
} else {
"0.0".into()
});
}
let (neg, digits, decpt) = shortest_digits(f);
let n = digits.len() as i32;
let out = if decpt <= -4 || decpt > 16 {
let mut mant = String::new();
mant.push(digits.as_bytes()[0] as char);
if n > 1 {
mant.push('.');
mant.push_str(&digits[1..]);
}
let e = decpt - 1;
let esign = if e < 0 { '-' } else { '+' };
let mut eabs = e.abs().to_string();
if eabs.len() < 2 {
eabs = format!("0{eabs}");
}
format!("{mant}e{esign}{eabs}")
} else if decpt <= 0 {
format!("0.{}{}", "0".repeat((-decpt) as usize), digits)
} else if decpt >= n {
format!("{}{}.0", digits, "0".repeat((decpt - n) as usize))
} else {
format!(
"{}.{}",
&digits[..decpt as usize],
&digits[decpt as usize..]
)
};
Ok(if neg { format!("-{out}") } else { out })
}
fn shortest_digits(f: f64) -> (bool, String, i32) {
let mut chosen: Option<String> = None;
for p in 0..=17usize {
let s = format!("{:.*e}", p, f);
if s.parse::<f64>() == Ok(f) {
chosen = Some(s);
break;
}
}
let s = chosen.unwrap_or_else(|| format!("{:.17e}", f));
let mut bytes = s.as_str();
let neg = bytes.starts_with('-');
if neg {
bytes = &bytes[1..];
}
let (mantissa, exp_part) = match bytes.split_once('e') {
Some((m, e)) => (m, e),
None => (bytes, "0"),
};
let exp: i32 = exp_part.parse().expect("f64 {:e} exponent is integer");
let (int_part, frac_part) = match mantissa.split_once('.') {
Some((i, f)) => (i, f),
None => (mantissa, ""),
};
let mut digits: String = format!("{int_part}{frac_part}");
let mut decpt = int_part.len() as i32 + exp;
let lead = digits.len() - digits.trim_start_matches('0').len();
if lead == digits.len() {
return (neg, "0".to_string(), 1);
}
digits = digits[lead..].to_string();
decpt -= lead as i32;
let trimmed = digits.trim_end_matches('0');
let digits = if trimmed.is_empty() {
"0".to_string()
} else {
trimmed.to_string()
};
(neg, digits, decpt)
}
pub fn canonical_value(v: &Value) -> Result<String, CanonicalFailure> {
if let Value::Obj(pairs) = v {
let mut keyed: Vec<&(String, Value)> = pairs.iter().collect();
keyed.sort_by(|a, b| a.0.cmp(&b.0)); let mut parts = Vec::with_capacity(keyed.len());
for (k, val) in keyed {
parts.push(format!("{}:{}", json_string(k), encode_nested(val)?));
}
Ok(format!("{{{}}}", parts.join(",")))
} else {
encode_nested(v)
}
}
pub fn canonical_json(v: &Value) -> Result<String, CanonicalFailure> {
match v {
Value::Obj(pairs) => {
let mut keyed: Vec<&(String, Value)> = pairs.iter().collect();
keyed.sort_by(|a, b| a.0.cmp(&b.0));
let mut parts = Vec::with_capacity(keyed.len());
for (k, val) in keyed {
parts.push(format!("{}:{}", json_string(k), canonical_json(val)?));
}
Ok(format!("{{{}}}", parts.join(",")))
}
Value::Arr(a) => {
let mut parts = Vec::with_capacity(a.len());
for e in a {
parts.push(canonical_json(e)?);
}
Ok(format!("[{}]", parts.join(",")))
}
_ => scalar_json(v),
}
}
fn encode_nested(v: &Value) -> Result<String, CanonicalFailure> {
match v {
Value::Obj(pairs) => {
let mut parts = Vec::with_capacity(pairs.len());
for (k, val) in pairs {
parts.push(format!("{}:{}", json_string(k), encode_nested(val)?));
}
Ok(format!("{{{}}}", parts.join(",")))
}
Value::Arr(a) => {
let mut parts = Vec::with_capacity(a.len());
for e in a {
parts.push(encode_nested(e)?);
}
Ok(format!("[{}]", parts.join(",")))
}
_ => scalar_json(v),
}
}
fn scalar_json(v: &Value) -> Result<String, CanonicalFailure> {
match v {
Value::Null => Ok("null".into()),
Value::Bool(b) => Ok(if *b { "true".into() } else { "false".into() }),
Value::Str(s) => Ok(json_string(s)),
Value::Int(i) => Ok(i.to_string()),
Value::Float(f) => py_float_repr(*f),
_ => fail(
CanonicalFailureCode::InvalidValue,
"cannot serialize value of unknown type",
),
}
}
fn json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
out
}