From 8c08035b8942ab7dfb2890a8f8bf4012ae2343f5 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sat, 24 Oct 2026 05:08:57 +0200 Subject: [PATCH] fix: write non-finite numbers as 0 instead of refusing to save Co-Authored-By: Claude Sonnet 5.5 --- crates/format/src/lib.rs | 242 +++++++++++++++++++++------------------ 1 file changed, 133 insertions(+), 109 deletions(-) diff --git a/crates/format/src/lib.rs b/crates/format/src/lib.rs index 441b6861..ca32eaaa 100644 --- a/crates/format/src/lib.rs +++ b/crates/format/src/lib.rs @@ -252,46 +252,38 @@ fn image_index(images: &&BTreeMap, 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); -impl serde::ser::Error for FiniteCheckError { - fn custom(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 Serialize for FiniteValue<'_, T> { + fn serialize(&self, s: S) -> Result { + 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 { 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 Serializer for Finite { + type Ok = S::Ok; + type Error = S::Error; + type SerializeSeq = Finite; + type SerializeTuple = Finite; + type SerializeTupleStruct = Finite; + type SerializeTupleVariant = Finite; + type SerializeMap = Finite; + type SerializeStruct = Finite; + type SerializeStructVariant = Finite; - 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 { + 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 { + self.0.serialize_f64(if v.is_finite() { v } else { 0.0 }) } - fn serialize_none(self) -> Result<(), FiniteCheckError> { - Ok(()) + fn serialize_none(self) -> Result { + self.0.serialize_none() } - fn serialize_some(self, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(self) + fn serialize_some(self, v: &T) -> Result { + self.0.serialize_some(&FiniteValue(v)) } - fn serialize_unit(self) -> Result<(), FiniteCheckError> { - Ok(()) + fn serialize_unit(self) -> Result { + self.0.serialize_unit() } - fn serialize_unit_struct(self, _: &'static str) -> Result<(), FiniteCheckError> { - Ok(()) + fn serialize_unit_struct(self, name: &'static str) -> Result { + 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 { + self.0.serialize_unit_variant(name, idx, variant) } - fn serialize_newtype_struct(self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(self) + fn serialize_newtype_struct(self, name: &'static str, v: &T) -> Result { + self.0.serialize_newtype_struct(name, &FiniteValue(v)) } - fn serialize_newtype_variant(self, _: &'static str, _: u32, _: &'static str, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(self) + fn serialize_newtype_variant(self, name: &'static str, idx: u32, variant: &'static str, v: &T) -> Result { + self.0.serialize_newtype_variant(name, idx, variant, &FiniteValue(v)) } - fn serialize_seq(self, _: Option) -> Result { - Ok(FiniteCheck) + fn serialize_seq(self, len: Option) -> Result { + self.0.serialize_seq(len).map(Finite) } - fn serialize_tuple(self, _: usize) -> Result { - Ok(FiniteCheck) + fn serialize_tuple(self, len: usize) -> Result { + self.0.serialize_tuple(len).map(Finite) } - fn serialize_tuple_struct(self, _: &'static str, _: usize) -> Result { - Ok(FiniteCheck) + fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result { + self.0.serialize_tuple_struct(name, len).map(Finite) } - fn serialize_tuple_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result { - Ok(FiniteCheck) + fn serialize_tuple_variant( + self, + name: &'static str, + idx: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.0.serialize_tuple_variant(name, idx, variant, len).map(Finite) } - fn serialize_map(self, _: Option) -> Result { - Ok(FiniteCheck) + fn serialize_map(self, len: Option) -> Result { + self.0.serialize_map(len).map(Finite) } - fn serialize_struct(self, _: &'static str, _: usize) -> Result { - Ok(FiniteCheck) + fn serialize_struct(self, name: &'static str, len: usize) -> Result { + self.0.serialize_struct(name, len).map(Finite) } - fn serialize_struct_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result { - Ok(FiniteCheck) + fn serialize_struct_variant( + self, + name: &'static str, + idx: u32, + variant: &'static str, + len: usize, + ) -> Result { + 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(&mut self, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(FiniteCheck) + $(impl serde::ser::$trait for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn $method(&mut self, v: &T) -> Result<(), S::Error> { + self.0.$method(&FiniteValue(v)) } - fn end(self) -> Result<(), FiniteCheckError> { - Ok(()) + fn end(self) -> Result { + 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(&mut self, k: &T) -> Result<(), FiniteCheckError> { - k.serialize(FiniteCheck) +impl serde::ser::SerializeMap for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn serialize_key(&mut self, k: &T) -> Result<(), S::Error> { + self.0.serialize_key(&FiniteValue(k)) } - fn serialize_value(&mut self, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(FiniteCheck) + fn serialize_value(&mut self, v: &T) -> Result<(), S::Error> { + self.0.serialize_value(&FiniteValue(v)) } - fn end(self) -> Result<(), FiniteCheckError> { - Ok(()) + fn end(self) -> Result { + self.0.end() } } -impl serde::ser::SerializeStruct for FiniteCheck { - type Ok = (); - type Error = FiniteCheckError; - fn serialize_field(&mut self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(FiniteCheck) +impl serde::ser::SerializeStruct for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn serialize_field(&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 { + self.0.end() } } -impl serde::ser::SerializeStructVariant for FiniteCheck { - type Ok = (); - type Error = FiniteCheckError; - fn serialize_field(&mut self, _: &'static str, v: &T) -> Result<(), FiniteCheckError> { - v.serialize(FiniteCheck) +impl serde::ser::SerializeStructVariant for Finite { + type Ok = S::Ok; + type Error = S::Error; + fn serialize_field(&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 { + 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 { - 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]