app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
The mechanical layer on 1.12.8 with the restart fixes (1.13.0-dev.1, add-in 1.0.23)
2ef0220
15d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
//! Python's `json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)` with its
//! default `ensure_ascii`, byte for byte. Request digests and journal records written by the
//! Python add-in must decode and re-encode identically here (tests/golden/mech_canonical.json).
use serde_json::Value;
pub fn encode(value: &Value) -> Vec<u8> {
let mut out = String::new();
write_value(value, &mut out);
out.into_bytes()
}
fn write_value(value: &Value, out: &mut String) {
match value {
Value::Null => out.push_str("null"),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Number(n) => match (n.as_i64(), n.as_u64(), n.as_f64()) {
(Some(i), _, _) => out.push_str(&i.to_string()),
(None, Some(u), _) => out.push_str(&u.to_string()),
(_, _, f) => out.push_str(&float_repr(f.unwrap_or(0.0))),
},
Value::String(s) => write_str(s, out),
Value::Array(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 { out.push(','); }
write_value(item, out);
}
out.push(']');
}
Value::Object(map) => {
// Python sorts by code point; UTF-8 byte order is the same order.
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
out.push('{');
for (i, key) in keys.iter().enumerate() {
if i > 0 { out.push(','); }
write_str(key, out);
out.push(':');
write_value(&map[*key], out);
}
out.push('}');
}
}
}
/// `ascii_escape_unicode`: printable ASCII stays, the six short escapes, everything else
/// (DEL included) is `\uXXXX` in lowercase hex, astral characters as surrogate pairs.
fn write_str(s: &str, out: &mut String) {
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{8}' => out.push_str("\\b"),
'\u{c}' => out.push_str("\\f"),
' '..='~' => out.push(c),
_ => {
let mut units = [0u16; 2];
for unit in c.encode_utf16(&mut units) { out.push_str(&format!("\\u{unit:04x}")); }
}
}
}
out.push('"');
}
/// `float.__repr__`: the shortest round-trip digits, positional while the decimal point sits
/// within -4 < decpt <= 16, else `d.ddde+XX` with at least two exponent digits.
pub fn float_repr(x: f64) -> String {
if x == 0.0 { return if x.is_sign_negative() { "-0.0".into() } else { "0.0".into() }; }
let sign = if x < 0.0 { "-" } else { "" };
let shortest = format!("{:e}", x.abs());
let (mantissa, exp) = shortest.split_once('e').unwrap_or((&shortest, "0"));
let exp: i32 = exp.parse().unwrap_or(0);
let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
let decpt = exp + 1;
if decpt <= -4 || decpt > 16 {
let rest = if digits.len() > 1 { format!(".{}", &digits[1..]) } else { String::new() };
format!("{sign}{}{rest}e{}{:02}", &digits[..1], if exp < 0 { '-' } else { '+' }, exp.abs())
} else if decpt <= 0 {
format!("{sign}0.{}{digits}", "0".repeat((-decpt) as usize))
} else if decpt as usize >= digits.len() {
format!("{sign}{digits}{}.0", "0".repeat(decpt as usize - digits.len()))
} else {
format!("{sign}{}.{}", &digits[..decpt as usize], &digits[decpt as usize..])
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn floats_follow_python_repr() {
for (x, want) in [(1e-5, "1e-05"), (1e16, "1e+16"), (1e15, "1000000000000000.0"), (0.0001, "0.0001"), (-0.0, "-0.0"), (100.0, "100.0"), (5e-324, "5e-324"), (1.5e16, "1.5e+16")] {
assert_eq!(float_repr(x), want, "{x}");
}
}
#[test]
fn keys_sort_and_strings_escape_like_python() {
assert_eq!(String::from_utf8(encode(&json!({"b": 1, "a": [true, null], "\u{e9}": "\u{7f}\u{1f600}"}))).unwrap(), "{\"a\":[true,null],\"b\":1,\"\\u00e9\":\"\\u007f\\ud83d\\ude00\"}");
}
}