//! Check typed pending intent numbers before JSON could replace NaN/Inf with null. //! This traverses the original Serialize representation without materializing it. use serde::{Serialize, Serializer, ser}; #[derive(Clone, Copy)] struct Finite; pub(crate) fn validate(value: &impl Serialize) -> Result<(), serde_json::Error> { value.serialize(Finite) } macro_rules! scalar { ($($method:ident: $ty:ty),* $(,)?) => {$( fn $method(self, _: $ty) -> Result<(), Self::Error> { Ok(()) } )*}; } impl Serializer for Finite { type Ok = (); type Error = serde_json::Error; type SerializeSeq = Self; type SerializeTuple = Self; type SerializeTupleStruct = Self; type SerializeTupleVariant = Self; type SerializeMap = Self; type SerializeStruct = Self; type SerializeStructVariant = Self; scalar!(serialize_bool: bool, serialize_i8: i8, serialize_i16: i16, serialize_i32: i32, serialize_i64: i64, serialize_i128: i128, serialize_u8: u8, serialize_u16: u16, serialize_u32: u32, serialize_u64: u64, serialize_u128: u128, serialize_char: char, serialize_str: &str, serialize_bytes: &[u8]); fn serialize_f32(self, value: f32) -> Result<(), Self::Error> { self.serialize_f64(f64::from(value)) } fn serialize_f64(self, value: f64) -> Result<(), Self::Error> { if value.is_finite() { Ok(()) } else { Err(ser::Error::custom( "pending strategy intent contains a non-finite number", )) } } fn serialize_none(self) -> Result<(), Self::Error> { Ok(()) } fn serialize_some(self, value: &T) -> Result<(), Self::Error> { value.serialize(self) } fn serialize_unit(self) -> Result<(), Self::Error> { Ok(()) } fn serialize_unit_struct(self, _: &'static str) -> Result<(), Self::Error> { Ok(()) } fn serialize_unit_variant( self, _: &'static str, _: u32, _: &'static str, ) -> Result<(), Self::Error> { Ok(()) } fn serialize_newtype_struct( self, _: &'static str, value: &T, ) -> Result<(), Self::Error> { value.serialize(self) } fn serialize_newtype_variant( self, _: &'static str, _: u32, _: &'static str, value: &T, ) -> Result<(), Self::Error> { value.serialize(self) } fn serialize_seq(self, _: Option) -> Result { Ok(self) } fn serialize_tuple(self, _: usize) -> Result { Ok(self) } fn serialize_tuple_struct(self, _: &'static str, _: usize) -> Result { Ok(self) } fn serialize_tuple_variant( self, _: &'static str, _: u32, _: &'static str, _: usize, ) -> Result { Ok(self) } fn serialize_map(self, _: Option) -> Result { Ok(self) } fn serialize_struct(self, _: &'static str, _: usize) -> Result { Ok(self) } fn serialize_struct_variant( self, _: &'static str, _: u32, _: &'static str, _: usize, ) -> Result { Ok(self) } } macro_rules! sequence { ($trait:ident, $method:ident) => { impl ser::$trait for Finite { type Ok = (); type Error = serde_json::Error; fn $method(&mut self, value: &T) -> Result<(), Self::Error> { value.serialize(*self) } fn end(self) -> Result<(), Self::Error> { Ok(()) } } }; } sequence!(SerializeSeq, serialize_element); sequence!(SerializeTuple, serialize_element); sequence!(SerializeTupleStruct, serialize_field); sequence!(SerializeTupleVariant, serialize_field); impl ser::SerializeMap for Finite { type Ok = (); type Error = serde_json::Error; fn serialize_key(&mut self, value: &T) -> Result<(), Self::Error> { value.serialize(*self) } fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> { value.serialize(*self) } fn end(self) -> Result<(), Self::Error> { Ok(()) } } macro_rules! structure { ($trait:ident) => { impl ser::$trait for Finite { type Ok = (); type Error = serde_json::Error; fn serialize_field( &mut self, _: &'static str, value: &T, ) -> Result<(), Self::Error> { value.serialize(*self) } fn end(self) -> Result<(), Self::Error> { Ok(()) } } }; } structure!(SerializeStruct); structure!(SerializeStructVariant); #[cfg(test)] mod tests { use super::*; use crate::strategy::{OrderIntent, StrategyDecision}; #[test] fn pending_numbers_cannot_be_silently_serialized_as_optional_nulls() { for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { let decision = StrategyDecision { order_intents: vec![ OrderIntent::LimitTargetPercent { symbol: "000001.SZ".into(), target_percent: 0.5, limit_price: value, reason: "test".into(), } .with_time_in_force(crate::strategy::OrderTimeInForce::Day), ], ..Default::default() }; assert!(validate(&decision).is_err()); assert!(validate(&vec![Some(value)]).is_err()); } assert!(validate(&(None::, vec![0., -0., 0.123456789], "NaN")).is_ok()); } }