fix: write non-finite numbers as 0 instead of refusing to save

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-24 05:13:06 +02:00
co-authored by Claude Sonnet 5.5
parent 503b6d606d
commit 8c08035b89
+133 -109
View File
@@ -252,46 +252,38 @@ fn image_index<S: Serializer>(images: &&BTreeMap<String, ImageData>, s: S) -> Re
s.collect_seq(images.iter().map(|(hash, i)| Entry { hash, width: i.width, height: i.height }))
}
/// A serializer that writes nothing and fails on the first NaN or infinite float. JSON has no
/// such numbers (`serde_json` writes `null`, which the reader rejects), so a document holding
/// one is refused instead of saved as an unreadable file.
struct FiniteCheck;
/// A serializer adapter that writes `0` in place of every NaN or infinite float. JSON has no such
/// numbers (`serde_json` writes `null`, which the reader rejects), so one bad value must not stop
/// Save, autosave or recovery copies of the whole document. Everything else passes through.
struct Finite<S>(S);
impl serde::ser::Error for FiniteCheckError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self(msg.to_string())
/// Serializes a value through a [`Finite`] adapter around the serializer it is written with.
struct FiniteValue<'a, T: ?Sized>(&'a T);
impl<T: ?Sized + Serialize> Serialize for FiniteValue<'_, T> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.0.serialize(Finite(s))
}
}
#[derive(Debug)]
struct FiniteCheckError(String);
impl std::fmt::Display for FiniteCheckError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for FiniteCheckError {}
macro_rules! check_skip {
macro_rules! finite_pass {
($($name:ident($ty:ty)),* $(,)?) => {
$(fn $name(self, _: $ty) -> Result<(), FiniteCheckError> { Ok(()) })*
$(fn $name(self, v: $ty) -> Result<S::Ok, S::Error> { self.0.$name(v) })*
};
}
impl Serializer for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
type SerializeSeq = FiniteCheck;
type SerializeTuple = FiniteCheck;
type SerializeTupleStruct = FiniteCheck;
type SerializeTupleVariant = FiniteCheck;
type SerializeMap = FiniteCheck;
type SerializeStruct = FiniteCheck;
type SerializeStructVariant = FiniteCheck;
impl<S: Serializer> Serializer for Finite<S> {
type Ok = S::Ok;
type Error = S::Error;
type SerializeSeq = Finite<S::SerializeSeq>;
type SerializeTuple = Finite<S::SerializeTuple>;
type SerializeTupleStruct = Finite<S::SerializeTupleStruct>;
type SerializeTupleVariant = Finite<S::SerializeTupleVariant>;
type SerializeMap = Finite<S::SerializeMap>;
type SerializeStruct = Finite<S::SerializeStruct>;
type SerializeStructVariant = Finite<S::SerializeStructVariant>;
check_skip!(
finite_pass!(
serialize_bool(bool),
serialize_i8(i8),
serialize_i16(i16),
@@ -308,129 +300,145 @@ impl Serializer for FiniteCheck {
serialize_bytes(&[u8])
);
fn serialize_f32(self, v: f32) -> Result<(), FiniteCheckError> {
if v.is_finite() { Ok(()) } else { Err(FiniteCheckError(format!("cannot save a non-finite number ({v})"))) }
fn serialize_f32(self, v: f32) -> Result<S::Ok, S::Error> {
self.0.serialize_f32(if v.is_finite() { v } else { 0.0 })
}
fn serialize_f64(self, v: f64) -> Result<(), FiniteCheckError> {
if v.is_finite() { Ok(()) } else { Err(FiniteCheckError(format!("cannot save a non-finite number ({v})"))) }
fn serialize_f64(self, v: f64) -> Result<S::Ok, S::Error> {
self.0.serialize_f64(if v.is_finite() { v } else { 0.0 })
}
fn serialize_none(self) -> Result<(), FiniteCheckError> {
Ok(())
fn serialize_none(self) -> Result<S::Ok, S::Error> {
self.0.serialize_none()
}
fn serialize_some<T: ?Sized + Serialize>(self, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(self)
fn serialize_some<T: ?Sized + Serialize>(self, v: &T) -> Result<S::Ok, S::Error> {
self.0.serialize_some(&FiniteValue(v))
}
fn serialize_unit(self) -> Result<(), FiniteCheckError> {
Ok(())
fn serialize_unit(self) -> Result<S::Ok, S::Error> {
self.0.serialize_unit()
}
fn serialize_unit_struct(self, _: &'static str) -> Result<(), FiniteCheckError> {
Ok(())
fn serialize_unit_struct(self, name: &'static str) -> Result<S::Ok, S::Error> {
self.0.serialize_unit_struct(name)
}
fn serialize_unit_variant(self, _: &'static str, _: u32, _: &'static str) -> Result<(), FiniteCheckError> {
Ok(())
fn serialize_unit_variant(self, name: &'static str, idx: u32, variant: &'static str) -> Result<S::Ok, S::Error> {
self.0.serialize_unit_variant(name, idx, variant)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(self)
fn serialize_newtype_struct<T: ?Sized + Serialize>(self, name: &'static str, v: &T) -> Result<S::Ok, S::Error> {
self.0.serialize_newtype_struct(name, &FiniteValue(v))
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(self, _: &'static str, _: u32, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(self)
fn serialize_newtype_variant<T: ?Sized + Serialize>(self, name: &'static str, idx: u32, variant: &'static str, v: &T) -> Result<S::Ok, S::Error> {
self.0.serialize_newtype_variant(name, idx, variant, &FiniteValue(v))
}
fn serialize_seq(self, _: Option<usize>) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, S::Error> {
self.0.serialize_seq(len).map(Finite)
}
fn serialize_tuple(self, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, S::Error> {
self.0.serialize_tuple(len).map(Finite)
}
fn serialize_tuple_struct(self, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeTupleStruct, S::Error> {
self.0.serialize_tuple_struct(name, len).map(Finite)
}
fn serialize_tuple_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_tuple_variant(
self,
name: &'static str,
idx: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant, S::Error> {
self.0.serialize_tuple_variant(name, idx, variant, len).map(Finite)
}
fn serialize_map(self, _: Option<usize>) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, S::Error> {
self.0.serialize_map(len).map(Finite)
}
fn serialize_struct(self, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct, S::Error> {
self.0.serialize_struct(name, len).map(Finite)
}
fn serialize_struct_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result<FiniteCheck, FiniteCheckError> {
Ok(FiniteCheck)
fn serialize_struct_variant(
self,
name: &'static str,
idx: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant, S::Error> {
self.0.serialize_struct_variant(name, idx, variant, len).map(Finite)
}
fn is_human_readable(&self) -> bool {
self.0.is_human_readable()
}
}
macro_rules! check_elements {
macro_rules! finite_elements {
($($trait:ident::$method:ident),* $(,)?) => {
$(impl serde::ser::$trait for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn $method<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
$(impl<S: serde::ser::$trait> serde::ser::$trait for Finite<S> {
type Ok = S::Ok;
type Error = S::Error;
fn $method<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), S::Error> {
self.0.$method(&FiniteValue(v))
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
fn end(self) -> Result<S::Ok, S::Error> {
self.0.end()
}
})*
};
}
check_elements!(
finite_elements!(
SerializeSeq::serialize_element,
SerializeTuple::serialize_element,
SerializeTupleStruct::serialize_field,
SerializeTupleVariant::serialize_field
);
impl serde::ser::SerializeMap for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn serialize_key<T: ?Sized + Serialize>(&mut self, k: &T) -> Result<(), FiniteCheckError> {
k.serialize(FiniteCheck)
impl<S: serde::ser::SerializeMap> serde::ser::SerializeMap for Finite<S> {
type Ok = S::Ok;
type Error = S::Error;
fn serialize_key<T: ?Sized + Serialize>(&mut self, k: &T) -> Result<(), S::Error> {
self.0.serialize_key(&FiniteValue(k))
}
fn serialize_value<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
fn serialize_value<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), S::Error> {
self.0.serialize_value(&FiniteValue(v))
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
fn end(self) -> Result<S::Ok, S::Error> {
self.0.end()
}
}
impl serde::ser::SerializeStruct for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
impl<S: serde::ser::SerializeStruct> serde::ser::SerializeStruct for Finite<S> {
type Ok = S::Ok;
type Error = S::Error;
fn serialize_field<T: ?Sized + Serialize>(&mut self, key: &'static str, v: &T) -> Result<(), S::Error> {
self.0.serialize_field(key, &FiniteValue(v))
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
fn skip_field(&mut self, key: &'static str) -> Result<(), S::Error> {
self.0.skip_field(key)
}
fn end(self) -> Result<S::Ok, S::Error> {
self.0.end()
}
}
impl serde::ser::SerializeStructVariant for FiniteCheck {
type Ok = ();
type Error = FiniteCheckError;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> {
v.serialize(FiniteCheck)
impl<S: serde::ser::SerializeStructVariant> serde::ser::SerializeStructVariant for Finite<S> {
type Ok = S::Ok;
type Error = S::Error;
fn serialize_field<T: ?Sized + Serialize>(&mut self, key: &'static str, v: &T) -> Result<(), S::Error> {
self.0.serialize_field(key, &FiniteValue(v))
}
fn end(self) -> Result<(), FiniteCheckError> {
Ok(())
fn skip_field(&mut self, key: &'static str) -> Result<(), S::Error> {
self.0.skip_field(key)
}
fn end(self) -> Result<S::Ok, S::Error> {
self.0.end()
}
}
/// Fail when the document holds a number JSON cannot represent.
fn ensure_finite(doc: &Document) -> Result<(), FormatError> {
DocumentJson::new(doc).serialize(FiniteCheck).map_err(io_err)
}
/// Serialize the document (without images) to the JSON value stored in `document.json`.
/// NaN and infinite numbers are written as `0`.
pub fn to_json(doc: &Document) -> Result<Value, FormatError> {
ensure_finite(doc)?;
serde_json::to_value(DocumentJson::new(doc)).map_err(io_err)
DocumentJson::new(doc).serialize(Finite(serde_json::value::Serializer)).map_err(io_err)
}
/// Write the document (without images) as `document.json` content to `w`.
/// NaN and infinite numbers are written as `0`.
pub fn write_json(doc: &Document, w: impl Write) -> Result<(), FormatError> {
ensure_finite(doc)?;
serde_json::to_writer(w, &DocumentJson::new(doc)).map_err(io_err)
DocumentJson::new(doc).serialize(Finite(&mut serde_json::Serializer::new(w))).map_err(io_err)
}
/// Parse `document.json` content.
@@ -986,12 +994,28 @@ mod tests {
let r = d.create(p, NodeKind::Rectangle, "R", 1.0, 2.0, 3.0, 4.0).unwrap();
assert!(save_bytes(&d, None).is_ok());
d.get_mut(r).unwrap().width = f64::NAN;
let e = save_bytes(&d, None).unwrap_err();
assert!(e.to_string().contains("non-finite"), "{e}");
d.get_mut(r).unwrap().width = 3.0;
d.get_mut(r).unwrap().opacity = f32::INFINITY;
let e = save_bytes(&d, None).unwrap_err();
assert!(e.to_string().contains("non-finite"), "{e}");
// Saving never fails; non-finite numbers are written as 0 and the file reopens.
let bytes = save_bytes(&d, None).unwrap();
let (back, _) = load_bytes(&bytes).unwrap();
assert_eq!(back.node_count(), d.node_count());
let n = back.get(r).unwrap();
assert_eq!(n.width, 0.0);
assert_eq!(n.opacity, 0.0);
assert_eq!(n.height, 4.0);
let mut out = Vec::new();
write_json(&d, &mut out).unwrap();
let (back, _) = from_json(std::str::from_utf8(&out).unwrap()).unwrap();
let n = back.get(r).unwrap();
assert_eq!(n.width, 0.0);
assert_eq!(n.opacity, 0.0);
assert_eq!(n.height, 4.0);
let v = to_json(&d).unwrap();
let (back, _) = from_json(&v.to_string()).unwrap();
let n = back.get(r).unwrap();
assert_eq!(n.width, 0.0);
assert_eq!(n.opacity, 0.0);
assert_eq!(n.height, 4.0);
}
#[test]