spiral-sdk 0.1.10

Rust SDK for Spiral resource and table APIs
Documentation
//! Lossless JSON handling for unsigned 64-bit integers consumed by JavaScript.

use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

const MAX_SAFE_INTEGER: u64 = (1 << 53) - 1;

/// Serde adapter for an unsigned 64-bit integer.
pub(crate) mod u64 {
    use super::*;

    pub fn serialize<S: Serializer>(value: &u64, serializer: S) -> Result<S::Ok, S::Error> {
        if *value <= MAX_SAFE_INTEGER {
            serializer.serialize_u64(*value)
        } else {
            serializer.serialize_str(&value.to_string())
        }
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
        struct UnsignedVisitor;

        impl Visitor<'_> for UnsignedVisitor {
            type Value = u64;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("a decimal uint64 string or a safe JSON integer")
            }

            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
                (value <= MAX_SAFE_INTEGER)
                    .then_some(value)
                    .ok_or_else(|| E::custom("JSON integer exceeds the safe range"))
            }

            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
                u64::try_from(value)
                    .map_err(E::custom)
                    .and_then(|value| self.visit_u64(value))
            }

            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
                if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
                    return Err(E::custom("uint64 string must contain decimal digits"));
                }
                value.parse().map_err(E::custom)
            }
        }

        deserializer.deserialize_any(UnsignedVisitor)
    }
}

struct SafeUnsigned(u64);

impl Serialize for SafeUnsigned {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        u64::serialize(&self.0, serializer)
    }
}

impl<'de> Deserialize<'de> for SafeUnsigned {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        u64::deserialize(deserializer).map(Self)
    }
}

/// Serde adapter for an optional unsigned 64-bit integer.
pub mod option_u64 {
    use super::*;

    pub fn serialize<S: Serializer>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error> {
        value.map(SafeUnsigned).serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Option<u64>, D::Error> {
        Option::<SafeUnsigned>::deserialize(deserializer).map(|value| value.map(|safe| safe.0))
    }
}

/// Serde adapter for optional half-open `u64` ranges, each encoded as `{start, end}`.
pub mod option_ranges {
    use std::ops::Range;

    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    #[derive(Deserialize, Serialize)]
    #[serde(deny_unknown_fields)]
    struct Bounds {
        #[serde(with = "super::u64")]
        start: u64,
        #[serde(with = "super::u64")]
        end: u64,
    }

    pub fn serialize<S: Serializer>(
        value: &Option<Vec<Range<u64>>>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        value
            .as_ref()
            .map(|ranges| {
                ranges
                    .iter()
                    .map(|range| Bounds {
                        start: range.start,
                        end: range.end,
                    })
                    .collect::<Vec<_>>()
            })
            .serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Option<Vec<Range<u64>>>, D::Error> {
        Ok(
            Option::<Vec<Bounds>>::deserialize(deserializer)?.map(|ranges| {
                ranges
                    .into_iter()
                    .map(|bounds| bounds.start..bounds.end)
                    .collect()
            }),
        )
    }
}

#[cfg(test)]
mod tests {
    use serde::{Deserialize, Serialize};
    use serde_json::json;

    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
    struct Counts {
        #[serde(default, with = "super::option_u64")]
        optional: Option<u64>,
    }

    #[test]
    fn large_values_use_strings_and_unsafe_numbers_fail() {
        let counts = Counts {
            optional: Some(u64::MAX),
        };
        let encoded = serde_json::to_value(&counts).expect("encode counts");
        assert_eq!(encoded, json!({"optional":"18446744073709551615"}));
        assert_eq!(
            serde_json::from_value::<Counts>(encoded).expect("decode counts"),
            counts
        );
        assert!(
            serde_json::from_value::<Counts>(json!({"optional":9007199254740992_u64})).is_err()
        );
    }
}