mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-28 12:26:52 -06:00
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:
co-authored by
Claude Sonnet 5.5
parent
503b6d606d
commit
8c08035b89
+133
-109
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user