use std::str::FromStr;
use bytes::Bytes;
use rust_decimal::Decimal;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{Error, Unexpected, Visitor},
ser::SerializeSeq,
};
use ustr::Ustr;
pub mod sorted_hashset {
use ahash::AHashSet;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<T, S>(set: &AHashSet<T>, s: S) -> Result<S::Ok, S::Error>
where
T: Serialize + Ord,
S: Serializer,
{
let mut sorted: Vec<&T> = set.iter().collect();
sorted.sort_unstable();
sorted.serialize(s)
}
pub fn deserialize<'de, T, D>(d: D) -> Result<AHashSet<T>, D::Error>
where
T: Deserialize<'de> + Eq + std::hash::Hash,
D: Deserializer<'de>,
{
let vec = Vec::<T>::deserialize(d)?;
Ok(vec.into_iter().collect())
}
}
struct DecimalVisitor;
impl Visitor<'_> for DecimalVisitor {
type Value = Decimal;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a decimal number as string, integer, or float")
}
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
if v.is_empty() {
return Ok(Decimal::ZERO);
}
parse_decimal_str(v).map_err(E::custom)
}
fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
self.visit_str(&v)
}
fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
Ok(Decimal::from(v))
}
fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
Ok(Decimal::from(v))
}
fn visit_i128<E: Error>(self, v: i128) -> Result<Self::Value, E> {
Ok(Decimal::from(v))
}
fn visit_u128<E: Error>(self, v: u128) -> Result<Self::Value, E> {
Ok(Decimal::from(v))
}
fn visit_f64<E: Error>(self, v: f64) -> Result<Self::Value, E> {
if v.is_nan() {
return Err(E::invalid_value(Unexpected::Float(v), &self));
}
if v.is_infinite() {
return Err(E::invalid_value(Unexpected::Float(v), &self));
}
Decimal::try_from(v).map_err(E::custom)
}
fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
Ok(Decimal::ZERO)
}
fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
Ok(Decimal::ZERO)
}
}
struct OptionalDecimalVisitor;
impl Visitor<'_> for OptionalDecimalVisitor {
type Value = Option<Decimal>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("null or a decimal number as string, integer, or float")
}
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
if v.is_empty() {
return Ok(None);
}
DecimalVisitor.visit_str(v).map(Some)
}
fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
self.visit_str(&v)
}
fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
DecimalVisitor.visit_i64(v).map(Some)
}
fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
DecimalVisitor.visit_u64(v).map(Some)
}
fn visit_i128<E: Error>(self, v: i128) -> Result<Self::Value, E> {
DecimalVisitor.visit_i128(v).map(Some)
}
fn visit_u128<E: Error>(self, v: u128) -> Result<Self::Value, E> {
DecimalVisitor.visit_u128(v).map(Some)
}
fn visit_f64<E: Error>(self, v: f64) -> Result<Self::Value, E> {
DecimalVisitor.visit_f64(v).map(Some)
}
fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
}
fn parse_decimal_str(value: &str) -> Result<Decimal, String> {
let parsed = if value.contains('e') || value.contains('E') {
Decimal::from_scientific(value)
} else {
Decimal::from_str(value)
};
match parsed {
Ok(decimal) => Ok(decimal),
Err(e) => {
for scale in (0..=Decimal::MAX_SCALE as usize).rev() {
let clamped =
decimal_string_clamped_to_scale(value, scale).ok_or_else(|| e.to_string())?;
if let Ok(decimal) = Decimal::from_str(&clamped) {
return Ok(decimal);
}
}
Err(e.to_string())
}
}
}
fn decimal_string_clamped_to_scale(value: &str, max_scale: usize) -> Option<String> {
let (coefficient, exponent) = match value.find(['e', 'E']) {
Some(index) => {
let exponent = value[index + 1..].parse::<i32>().ok()?;
(&value[..index], exponent)
}
None => (value, 0),
};
let (sign, unsigned) = match coefficient.as_bytes().first()? {
b'+' => ("", &coefficient[1..]),
b'-' => ("-", &coefficient[1..]),
_ => ("", coefficient),
};
let (integer, fractional) = decimal_components(unsigned)?;
let digits = format!("{integer}{fractional}");
if decimal_digits_are_zero(&digits, "") {
return Some("0".to_string());
}
let point = i32::try_from(integer.len()).ok()?.checked_add(exponent)?;
if decimal_integer_digits_exceed_max(&digits, point) {
return None;
}
let (integer, fractional) = if point <= 0 {
let zero_count = usize::try_from(-point).ok()?.min(max_scale + 1);
(
"0".to_string(),
format!("{}{digits}", "0".repeat(zero_count)),
)
} else {
let point = usize::try_from(point).ok()?;
if point >= digits.len() {
(
format!("{}{}", digits, "0".repeat(point - digits.len())),
String::new(),
)
} else {
(digits[..point].to_string(), digits[point..].to_string())
}
};
let (integer, fractional) = round_decimal_components(integer, fractional, max_scale);
let sign = if sign == "-" && decimal_digits_are_zero(&integer, &fractional) {
""
} else {
sign
};
if fractional.is_empty() {
Some(format!("{sign}{integer}"))
} else {
Some(format!("{sign}{integer}.{fractional}"))
}
}
fn decimal_components(value: &str) -> Option<(&str, &str)> {
let mut split = value.split('.');
let integer = split.next()?;
let fractional = split.next().unwrap_or("");
if split.next().is_some()
|| (integer.is_empty() && fractional.is_empty())
|| !integer.chars().all(|c| c.is_ascii_digit())
|| !fractional.chars().all(|c| c.is_ascii_digit())
{
return None;
}
Some((integer, fractional))
}
fn decimal_integer_digits_exceed_max(digits: &str, point: i32) -> bool {
const DECIMAL_MAX_INTEGER_DIGITS: i32 = 29;
if point <= 0 {
return false;
}
let Some(first_non_zero) = digits.bytes().position(|digit| digit != b'0') else {
return false;
};
let Ok(first_non_zero) = i32::try_from(first_non_zero) else {
return true;
};
point.saturating_sub(first_non_zero) > DECIMAL_MAX_INTEGER_DIGITS
}
fn round_decimal_components(
mut integer: String,
fractional: String,
max_scale: usize,
) -> (String, String) {
if fractional.len() <= max_scale {
return (integer, fractional);
}
let mut rounded = fractional.as_bytes()[..max_scale].to_vec();
if fractional.as_bytes()[max_scale] >= b'5' {
increment_decimal_digits(&mut integer, &mut rounded);
}
(
integer,
String::from_utf8(rounded).expect("decimal digits are ASCII"),
)
}
fn increment_decimal_digits(integer: &mut String, fractional: &mut [u8]) {
for digit in fractional.iter_mut().rev() {
if *digit < b'9' {
*digit += 1;
return;
}
*digit = b'0';
}
let mut integer_digits = integer.as_bytes().to_vec();
for digit in integer_digits.iter_mut().rev() {
if *digit < b'9' {
*digit += 1;
*integer = String::from_utf8(integer_digits).expect("decimal digits are ASCII");
return;
}
*digit = b'0';
}
integer_digits.insert(0, b'1');
*integer = String::from_utf8(integer_digits).expect("decimal digits are ASCII");
}
fn decimal_digits_are_zero(integer: &str, fractional: &str) -> bool {
integer
.bytes()
.chain(fractional.bytes())
.all(|digit| digit == b'0')
}
pub trait Serializable: Serialize + for<'de> Deserialize<'de> {
fn from_json_bytes(data: &[u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(data)
}
fn to_json_bytes(&self) -> Result<Bytes, serde_json::Error> {
serde_json::to_vec(self).map(Bytes::from)
}
}
pub use self::msgpack::{FromMsgPack, MsgPackSerializable, ToMsgPack};
pub mod msgpack {
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use super::Serializable;
pub trait FromMsgPack: for<'de> Deserialize<'de> + Sized {
fn from_msgpack_bytes(data: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
rmp_serde::from_slice(data)
}
}
pub trait ToMsgPack: Serialize {
fn to_msgpack_bytes(&self) -> Result<Bytes, rmp_serde::encode::Error> {
rmp_serde::to_vec_named(self).map(Bytes::from)
}
}
pub trait MsgPackSerializable: Serializable + FromMsgPack + ToMsgPack {}
impl<T> FromMsgPack for T where T: Serializable {}
impl<T> ToMsgPack for T where T: Serializable {}
impl<T> MsgPackSerializable for T where T: Serializable {}
}
#[must_use]
pub const fn default_true() -> bool {
true
}
#[must_use]
pub const fn default_false() -> bool {
false
}
pub fn deserialize_decimal<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(DecimalVisitor)
}
pub fn deserialize_optional_decimal<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(OptionalDecimalVisitor)
}
pub fn serialize_decimal<S: Serializer>(d: &Decimal, s: S) -> Result<S::Ok, S::Error> {
rust_decimal::serde::float::serialize(d, s)
}
pub fn serialize_optional_decimal<S: Serializer>(
d: &Option<Decimal>,
s: S,
) -> Result<S::Ok, S::Error> {
match d {
Some(decimal) => rust_decimal::serde::float::serialize(decimal, s),
None => s.serialize_none(),
}
}
pub fn deserialize_decimal_from_str<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
Decimal::from_str(s.as_ref()).map_err(D::Error::custom)
}
pub fn deserialize_decimal_or_zero<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
if s.is_empty() || s == "0" {
Ok(Decimal::ZERO)
} else {
Decimal::from_str(s.as_ref()).map_err(D::Error::custom)
}
}
pub fn deserialize_optional_decimal_str<'de, D>(
deserializer: D,
) -> Result<Option<Decimal>, D::Error>
where
D: Deserializer<'de>,
{
let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
if s.is_empty() || s == "0" {
Ok(None)
} else {
Decimal::from_str(s.as_ref())
.map(Some)
.map_err(D::Error::custom)
}
}
pub fn deserialize_optional_decimal_from_str<'de, D>(
deserializer: D,
) -> Result<Option<Decimal>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
match opt {
Some(s) if !s.is_empty() => Decimal::from_str(&s).map(Some).map_err(D::Error::custom),
_ => Ok(None),
}
}
pub fn deserialize_optional_decimal_or_zero<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<String> = Deserialize::deserialize(deserializer)?;
match opt {
None => Ok(Decimal::ZERO),
Some(s) if s.is_empty() || s == "0" => Ok(Decimal::ZERO),
Some(s) => Decimal::from_str(&s).map_err(D::Error::custom),
}
}
pub fn deserialize_vec_decimal_from_str<'de, D>(deserializer: D) -> Result<Vec<Decimal>, D::Error>
where
D: Deserializer<'de>,
{
let strings = Vec::<String>::deserialize(deserializer)?;
strings
.into_iter()
.map(|s| Decimal::from_str(&s).map_err(D::Error::custom))
.collect()
}
pub fn serialize_decimal_as_str<S>(decimal: &Decimal, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&decimal.to_string())
}
pub fn serialize_optional_decimal_as_str<S>(
decimal: &Option<Decimal>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match decimal {
Some(d) => serializer.serialize_str(&d.to_string()),
None => serializer.serialize_none(),
}
}
pub fn serialize_vec_decimal_as_str<S>(
decimals: &Vec<Decimal>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(decimals.len()))?;
for decimal in decimals {
seq.serialize_element(&decimal.to_string())?;
}
seq.end()
}
pub fn parse_decimal(s: &str) -> anyhow::Result<Decimal> {
Decimal::from_str(s).map_err(|e| anyhow::anyhow!("Failed to parse decimal from '{s}': {e}"))
}
pub fn parse_optional_decimal(s: &Option<String>) -> anyhow::Result<Option<Decimal>> {
match s {
None => Ok(None),
Some(s) if s.is_empty() => Ok(None),
Some(s) => parse_decimal(s).map(Some),
}
}
pub fn deserialize_empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
Ok(opt.filter(|s| !s.is_empty()))
}
pub fn deserialize_empty_ustr_as_none<'de, D>(deserializer: D) -> Result<Option<Ustr>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<Ustr>::deserialize(deserializer)?;
Ok(opt.filter(|s| !s.is_empty()))
}
pub fn deserialize_string_to_u8<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
D: Deserializer<'de>,
{
let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
if s.is_empty() {
return Ok(0);
}
s.as_ref().parse::<u8>().map_err(D::Error::custom)
}
pub fn deserialize_string_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
if s.is_empty() {
Ok(0)
} else {
s.as_ref().parse::<u64>().map_err(D::Error::custom)
}
}
pub fn deserialize_optional_string_to_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<String> = Option::deserialize(deserializer)?;
match s {
Some(s) if s.is_empty() => Ok(None),
Some(s) => s.parse().map(Some).map_err(D::Error::custom),
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use rstest::*;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use ustr::Ustr;
use super::{
Serializable, deserialize_decimal, deserialize_decimal_from_str,
deserialize_decimal_or_zero, deserialize_empty_string_as_none,
deserialize_empty_ustr_as_none, deserialize_optional_decimal,
deserialize_optional_decimal_or_zero, deserialize_optional_decimal_str,
deserialize_optional_string_to_u64, deserialize_string_to_u8, deserialize_string_to_u64,
deserialize_vec_decimal_from_str,
msgpack::{FromMsgPack, ToMsgPack},
parse_decimal, parse_optional_decimal, serialize_decimal, serialize_decimal_as_str,
serialize_optional_decimal, serialize_optional_decimal_as_str,
serialize_vec_decimal_as_str,
};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct SerializableTestStruct {
id: u32,
name: String,
value: f64,
}
impl Serializable for SerializableTestStruct {}
#[rstest]
fn test_serializable_json_roundtrip() {
let original = SerializableTestStruct {
id: 42,
name: "test".to_string(),
value: std::f64::consts::PI,
};
let json_bytes = original.to_json_bytes().unwrap();
let deserialized = SerializableTestStruct::from_json_bytes(&json_bytes).unwrap();
assert_eq!(original, deserialized);
}
#[rstest]
fn test_serializable_msgpack_roundtrip() {
let original = SerializableTestStruct {
id: 123,
name: "msgpack_test".to_string(),
value: std::f64::consts::E,
};
let msgpack_bytes = original.to_msgpack_bytes().unwrap();
let deserialized = SerializableTestStruct::from_msgpack_bytes(&msgpack_bytes).unwrap();
assert_eq!(original, deserialized);
}
#[rstest]
fn test_serializable_json_invalid_data() {
let invalid_json = b"invalid json data";
let result = SerializableTestStruct::from_json_bytes(invalid_json);
assert!(result.is_err());
}
#[rstest]
fn test_serializable_msgpack_invalid_data() {
let invalid_msgpack = b"invalid msgpack data";
let result = SerializableTestStruct::from_msgpack_bytes(invalid_msgpack);
assert!(result.is_err());
}
#[rstest]
fn test_serializable_json_empty_values() {
let test_struct = SerializableTestStruct {
id: 0,
name: String::new(),
value: 0.0,
};
let json_bytes = test_struct.to_json_bytes().unwrap();
let deserialized = SerializableTestStruct::from_json_bytes(&json_bytes).unwrap();
assert_eq!(test_struct, deserialized);
}
#[rstest]
fn test_serializable_msgpack_empty_values() {
let test_struct = SerializableTestStruct {
id: 0,
name: String::new(),
value: 0.0,
};
let msgpack_bytes = test_struct.to_msgpack_bytes().unwrap();
let deserialized = SerializableTestStruct::from_msgpack_bytes(&msgpack_bytes).unwrap();
assert_eq!(test_struct, deserialized);
}
#[derive(Deserialize)]
struct TestOptionalDecimalStr {
#[serde(deserialize_with = "deserialize_optional_decimal_str")]
value: Option<Decimal>,
}
#[derive(Deserialize)]
struct TestDecimalOrZero {
#[serde(deserialize_with = "deserialize_decimal_or_zero")]
value: Decimal,
}
#[derive(Deserialize)]
struct TestOptionalDecimalOrZero {
#[serde(deserialize_with = "deserialize_optional_decimal_or_zero")]
value: Decimal,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestDecimalRoundtrip {
#[serde(
serialize_with = "serialize_decimal_as_str",
deserialize_with = "deserialize_decimal_from_str"
)]
value: Decimal,
#[serde(
serialize_with = "serialize_optional_decimal_as_str",
deserialize_with = "super::deserialize_optional_decimal_from_str"
)]
optional_value: Option<Decimal>,
}
#[rstest]
#[case(r#"{"value":"123.45"}"#, Some(dec!(123.45)))]
#[case(r#"{"value":"0"}"#, None)]
#[case(r#"{"value":""}"#, None)]
fn test_deserialize_optional_decimal_str(
#[case] json: &str,
#[case] expected: Option<Decimal>,
) {
let result: TestOptionalDecimalStr = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value":"123.45"}"#, dec!(123.45))]
#[case(r#"{"value":"0"}"#, Decimal::ZERO)]
#[case(r#"{"value":""}"#, Decimal::ZERO)]
fn test_deserialize_decimal_or_zero(#[case] json: &str, #[case] expected: Decimal) {
let result: TestDecimalOrZero = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value":"123.45"}"#, dec!(123.45))]
#[case(r#"{"value":"0"}"#, Decimal::ZERO)]
#[case(r#"{"value":null}"#, Decimal::ZERO)]
fn test_deserialize_optional_decimal_or_zero(#[case] json: &str, #[case] expected: Decimal) {
let result: TestOptionalDecimalOrZero = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
fn test_decimal_serialization_roundtrip() {
let original = TestDecimalRoundtrip {
value: dec!(123.456789012345678),
optional_value: Some(dec!(0.000000001)),
};
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("\"123.456789012345678\""));
assert!(json.contains("\"0.000000001\""));
let deserialized: TestDecimalRoundtrip = serde_json::from_str(&json).unwrap();
assert_eq!(original.value, deserialized.value);
assert_eq!(original.optional_value, deserialized.optional_value);
}
#[rstest]
fn test_decimal_optional_none_handling() {
let test_struct = TestDecimalRoundtrip {
value: dec!(42.0),
optional_value: None,
};
let json = serde_json::to_string(&test_struct).unwrap();
assert!(json.contains("null"));
let parsed: TestDecimalRoundtrip = serde_json::from_str(&json).unwrap();
assert_eq!(test_struct.value, parsed.value);
assert_eq!(None, parsed.optional_value);
}
#[derive(Deserialize)]
struct TestEmptyStringAsNone {
#[serde(deserialize_with = "deserialize_empty_string_as_none")]
value: Option<String>,
}
#[rstest]
#[case(r#"{"value":"hello"}"#, Some("hello".to_string()))]
#[case(r#"{"value":""}"#, None)]
#[case(r#"{"value":null}"#, None)]
fn test_deserialize_empty_string_as_none(#[case] json: &str, #[case] expected: Option<String>) {
let result: TestEmptyStringAsNone = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[derive(Deserialize)]
struct TestEmptyUstrAsNone {
#[serde(deserialize_with = "deserialize_empty_ustr_as_none")]
value: Option<Ustr>,
}
#[rstest]
#[case(r#"{"value":"hello"}"#, Some(Ustr::from("hello")))]
#[case(r#"{"value":""}"#, None)]
#[case(r#"{"value":null}"#, None)]
fn test_deserialize_empty_ustr_as_none(#[case] json: &str, #[case] expected: Option<Ustr>) {
let result: TestEmptyUstrAsNone = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestVecDecimal {
#[serde(
serialize_with = "serialize_vec_decimal_as_str",
deserialize_with = "deserialize_vec_decimal_from_str"
)]
values: Vec<Decimal>,
}
#[rstest]
fn test_vec_decimal_roundtrip() {
let original = TestVecDecimal {
values: vec![dec!(1.5), dec!(2.25), dec!(100.001)],
};
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("[\"1.5\",\"2.25\",\"100.001\"]"));
let parsed: TestVecDecimal = serde_json::from_str(&json).unwrap();
assert_eq!(original.values, parsed.values);
}
#[rstest]
fn test_vec_decimal_empty() {
let original = TestVecDecimal { values: vec![] };
let json = serde_json::to_string(&original).unwrap();
let parsed: TestVecDecimal = serde_json::from_str(&json).unwrap();
assert_eq!(original.values, parsed.values);
}
#[derive(Deserialize)]
struct TestStringToU8 {
#[serde(deserialize_with = "deserialize_string_to_u8")]
value: u8,
}
#[rstest]
#[case(r#"{"value":"42"}"#, 42)]
#[case(r#"{"value":"0"}"#, 0)]
#[case(r#"{"value":"255"}"#, 255)]
#[case(r#"{"value":""}"#, 0)]
fn test_deserialize_string_to_u8(#[case] json: &str, #[case] expected: u8) {
let result: TestStringToU8 = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value":"256"}"#)]
#[case(r#"{"value":"999"}"#)]
#[case(r#"{"value":"abc"}"#)]
fn test_deserialize_string_to_u8_invalid(#[case] json: &str) {
let result: Result<TestStringToU8, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[derive(Deserialize)]
struct TestStringToU64 {
#[serde(deserialize_with = "deserialize_string_to_u64")]
value: u64,
}
#[rstest]
#[case(r#"{"value":"12345678901234"}"#, 12_345_678_901_234)]
#[case(r#"{"value":"0"}"#, 0)]
#[case(r#"{"value":"18446744073709551615"}"#, u64::MAX)]
#[case(r#"{"value":""}"#, 0)]
fn test_deserialize_string_to_u64(#[case] json: &str, #[case] expected: u64) {
let result: TestStringToU64 = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value":"18446744073709551616"}"#)]
#[case(r#"{"value":"abc"}"#)]
#[case(r#"{"value":"-1"}"#)]
fn test_deserialize_string_to_u64_invalid(#[case] json: &str) {
let result: Result<TestStringToU64, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[derive(Deserialize)]
struct TestOptionalStringToU64 {
#[serde(deserialize_with = "deserialize_optional_string_to_u64")]
value: Option<u64>,
}
#[rstest]
#[case(r#"{"value":"12345678901234"}"#, Some(12_345_678_901_234))]
#[case(r#"{"value":"0"}"#, Some(0))]
#[case(r#"{"value":""}"#, None)]
#[case(r#"{"value":null}"#, None)]
fn test_deserialize_optional_string_to_u64(#[case] json: &str, #[case] expected: Option<u64>) {
let result: TestOptionalStringToU64 = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case("123.45", dec!(123.45))]
#[case("0", Decimal::ZERO)]
#[case("0.0", Decimal::ZERO)]
fn test_parse_decimal(#[case] input: &str, #[case] expected: Decimal) {
let result = parse_decimal(input).unwrap();
assert_eq!(result, expected);
}
#[rstest]
fn test_parse_decimal_invalid() {
assert!(parse_decimal("invalid").is_err());
assert!(parse_decimal("").is_err());
}
#[rstest]
#[case(&Some("123.45".to_string()), Some(dec!(123.45)))]
#[case(&Some("0".to_string()), Some(Decimal::ZERO))]
#[case(&Some(String::new()), None)]
#[case(&None, None)]
fn test_parse_optional_decimal(
#[case] input: &Option<String>,
#[case] expected: Option<Decimal>,
) {
let result = parse_optional_decimal(input).unwrap();
assert_eq!(result, expected);
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct TestFlexibleDecimal {
#[serde(
serialize_with = "serialize_decimal",
deserialize_with = "deserialize_decimal"
)]
value: Decimal,
#[serde(
serialize_with = "serialize_optional_decimal",
deserialize_with = "deserialize_optional_decimal"
)]
optional_value: Option<Decimal>,
}
#[rstest]
#[case(r#"{"value": 123.456, "optional_value": 789.012}"#, dec!(123.456), Some(dec!(789.012)))]
#[case(r#"{"value": "123.456", "optional_value": "789.012"}"#, dec!(123.456), Some(dec!(789.012)))]
#[case(r#"{"value": 100, "optional_value": null}"#, dec!(100), None)]
#[case(r#"{"value": null, "optional_value": null}"#, Decimal::ZERO, None)]
fn test_deserialize_flexible_decimal(
#[case] json: &str,
#[case] expected_value: Decimal,
#[case] expected_optional: Option<Decimal>,
) {
let result: TestFlexibleDecimal = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected_value);
assert_eq!(result.optional_value, expected_optional);
}
#[rstest]
fn test_flexible_decimal_roundtrip() {
let original = TestFlexibleDecimal {
value: dec!(123.456),
optional_value: Some(dec!(789.012)),
};
let json = serde_json::to_string(&original).unwrap();
let deserialized: TestFlexibleDecimal = serde_json::from_str(&json).unwrap();
assert_eq!(original.value, deserialized.value);
assert_eq!(original.optional_value, deserialized.optional_value);
}
#[rstest]
fn test_flexible_decimal_scientific_notation() {
let json = r#"{"value": 0.00000001, "optional_value": 12345678.12345}"#;
let parsed: TestFlexibleDecimal = serde_json::from_str(json).unwrap();
assert_eq!(parsed.value, dec!(0.00000001));
assert_eq!(parsed.optional_value, Some(dec!(12345678.12345)));
}
#[rstest]
fn test_flexible_decimal_empty_string_optional() {
let json = r#"{"value": 100, "optional_value": ""}"#;
let parsed: TestFlexibleDecimal = serde_json::from_str(json).unwrap();
assert_eq!(parsed.value, dec!(100));
assert_eq!(parsed.optional_value, None);
}
#[derive(Debug, Deserialize)]
struct TestDecimalOnly {
#[serde(deserialize_with = "deserialize_decimal")]
value: Decimal,
}
#[rstest]
#[case(r#"{"value": "1.5e-8"}"#, dec!(0.000000015))]
#[case(r#"{"value": "1E10"}"#, dec!(10000000000))]
#[case(r#"{"value": "-1.23e5"}"#, dec!(-123000))]
fn test_deserialize_decimal_scientific_string(#[case] json: &str, #[case] expected: Decimal) {
let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value": 9223372036854775807}"#, dec!(9223372036854775807))] #[case(r#"{"value": -9223372036854775808}"#, dec!(-9223372036854775808))] #[case(r#"{"value": 0}"#, Decimal::ZERO)]
fn test_deserialize_decimal_large_integers(#[case] json: &str, #[case] expected: Decimal) {
let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value": "-123.456789"}"#, dec!(-123.456789))]
#[case(r#"{"value": -999.99}"#, dec!(-999.99))]
fn test_deserialize_decimal_negative(#[case] json: &str, #[case] expected: Decimal) {
let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
#[rstest]
#[case(r#"{"value": "123456789.123456789012345678"}"#)] fn test_deserialize_decimal_high_precision(#[case] json: &str) {
let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(result.value, dec!(123456789.123456789012345678));
}
#[rstest]
#[case(
r#"{"value": "1.234567890123456789012345678912345e-1"}"#,
"0.1234567890123456789012345679"
)]
#[case(
r#"{"value": "0.1234567890123456789012345678912345"}"#,
"0.1234567890123456789012345679"
)]
#[case(
r#"{"value": "999999999999999999999999999995e-29"}"#,
"10.000000000000000000000000000"
)]
#[case(r#"{"value": "0.5e29"}"#, "50000000000000000000000000000")]
#[case(r#"{"value": "-4e-29"}"#, "0.0000000000000000000000000000")]
#[case(r#"{"value": "1.5e-999999"}"#, "0.0000000000000000000000000000")]
#[case(r#"{"value": "9e-999999"}"#, "0.0000000000000000000000000000")]
#[case(r#"{"value": "0e2000000000"}"#, "0")]
fn test_deserialize_decimal_rounds_high_scale_values(
#[case] json: &str,
#[case] expected: &str,
) {
let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(result.value.to_string(), expected);
}
#[rstest]
#[case(r#"{"value": "8e28"}"#)] #[case(r#"{"value": "1e1000000000"}"#)] #[case(r#"{"value": "not-a-number"}"#)]
fn test_deserialize_decimal_rejects_unrepresentable_values(#[case] json: &str) {
let result: Result<TestDecimalOnly, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[rstest]
fn test_deserialize_optional_decimal_rounds_high_scale_values() {
let json = r#"{"value": "51.234567890123456789012345678912345e-1"}"#;
let result: TestOptionalDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(
result.value.map(|v| v.to_string()),
Some("5.1234567890123456789012345679".into()),
);
}
#[derive(Debug, Deserialize)]
struct TestOptionalDecimalOnly {
#[serde(deserialize_with = "deserialize_optional_decimal")]
value: Option<Decimal>,
}
#[rstest]
#[case(r#"{"value": "1.5e-8"}"#, Some(dec!(0.000000015)))]
#[case(r#"{"value": null}"#, None)]
#[case(r#"{"value": ""}"#, None)]
#[case(r#"{"value": 42}"#, Some(dec!(42)))]
#[case(r#"{"value": -100.5}"#, Some(dec!(-100.5)))]
fn test_deserialize_optional_decimal_various(
#[case] json: &str,
#[case] expected: Option<Decimal>,
) {
let result: TestOptionalDecimalOnly = serde_json::from_str(json).unwrap();
assert_eq!(result.value, expected);
}
use proptest::prelude::*;
use rust_decimal::prelude::ToPrimitive;
fn representable_decimal_strategy() -> impl Strategy<Value = Decimal> {
(
-79_228_162_514_264_337_593_543_950_335i128
..=79_228_162_514_264_337_593_543_950_335i128,
0u32..=28u32,
)
.prop_map(|(mantissa, scale)| Decimal::from_i128_with_scale(mantissa, scale))
}
fn numeric_string_strategy() -> impl Strategy<Value = String> {
(
proptest::bool::ANY,
"[0-9]{1,30}",
proptest::option::of("[0-9]{1,40}"),
proptest::option::of(-40i32..=40),
)
.prop_map(|(negative, integer, fraction, exponent)| {
let mut value = String::new();
if negative {
value.push('-');
}
value.push_str(&integer);
if let Some(fraction) = fraction {
value.push('.');
value.push_str(&fraction);
}
if let Some(exponent) = exponent {
value.push('e');
value.push_str(&exponent.to_string());
}
value
})
}
proptest! {
#[rstest]
fn prop_deserialize_decimal_roundtrips_representable_values(
expected in representable_decimal_strategy()
) {
let json = format!(r#"{{"value": "{expected}"}}"#);
let parsed: TestDecimalOnly = serde_json::from_str(&json).unwrap();
prop_assert_eq!(parsed.value, expected);
}
#[rstest]
fn prop_deserialize_decimal_total_on_arbitrary_strings(value in "\\PC{0,64}") {
let json = serde_json::to_string(&serde_json::json!({"value": value})).unwrap();
let _ = serde_json::from_str::<TestDecimalOnly>(&json);
}
#[rstest]
fn prop_deserialize_decimal_tracks_f64_reference(value in numeric_string_strategy()) {
let json = format!(r#"{{"value": "{value}"}}"#);
if let Ok(parsed) = serde_json::from_str::<TestDecimalOnly>(&json) {
let reference: f64 = value.parse().unwrap();
let decoded = parsed.value.to_f64().unwrap();
prop_assert!(
(decoded - reference).abs() <= reference.abs() * 1e-9 + 1e-27,
"decoded {decoded} diverges from reference {reference} for input {value}",
);
}
}
#[rstest]
fn prop_deserialize_optional_decimal_matches_required(
value in numeric_string_strategy()
) {
let json = format!(r#"{{"value": "{value}"}}"#);
let required = serde_json::from_str::<TestDecimalOnly>(&json);
let optional = serde_json::from_str::<TestOptionalDecimalOnly>(&json);
match (required, optional) {
(Ok(required), Ok(optional)) => {
prop_assert_eq!(optional.value, Some(required.value));
}
(Err(_), Err(_)) => {}
(required, optional) => prop_assert!(
false,
"required and optional decoding disagree for input {}: {:?} vs {:?}",
value,
required.map(|r| r.value),
optional.map(|o| o.value),
),
}
}
}
}