asterdex-sdk 0.1.5

AsterDex Futures SDK v3 — Rust async client for REST and WebSocket APIs
Documentation
// Shared serde deserialization helpers for AsterDex model types.
//
// The testnet API occasionally returns fields as either JSON strings or JSON numbers
// depending on the endpoint version. These helpers normalise both forms.
//
// Each helper uses a hand-written Visitor instead of deserializing to
// `serde_json::Value` first — avoids the full DOM allocation (heap-alloc per
// field) on every deserialized message.

use serde::Deserializer;

// ---------------------------------------------------------------------------
// string_or_number
// ---------------------------------------------------------------------------

/// Deserialise a field that may be either a JSON string or a JSON number, yielding a `String`.
///
/// Example — both `"200"` and `200` deserialise to `"200"`.
pub(crate) fn string_or_number<'de, D>(d: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    d.deserialize_any(StringOrNumberVisitor)
}

struct StringOrNumberVisitor;

impl<'de> serde::de::Visitor<'de> for StringOrNumberVisitor {
    type Value = String;

    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("a string or number")
    }

    #[inline]
    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<String, E> {
        Ok(v.to_owned())
    }

    #[inline]
    fn visit_string<E: serde::de::Error>(self, v: String) -> Result<String, E> {
        Ok(v)
    }

    #[inline]
    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<String, E> {
        Ok(v.to_string())
    }

    #[inline]
    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<String, E> {
        Ok(v.to_string())
    }

    #[inline]
    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<String, E> {
        Ok(v.to_string())
    }

    #[inline]
    fn visit_bool<E: serde::de::Error>(self, v: bool) -> Result<String, E> {
        Ok(v.to_string())
    }
}

// ---------------------------------------------------------------------------
// opt_string_or_number
// ---------------------------------------------------------------------------

/// Deserialise an optional field that may be a JSON string, number, or null.
///
/// `null` → `None`; string or number → `Some(string_repr)`.
pub(crate) fn opt_string_or_number<'de, D>(d: D) -> Result<Option<String>, D::Error>
where
    D: Deserializer<'de>,
{
    d.deserialize_any(OptStringOrNumberVisitor)
}

struct OptStringOrNumberVisitor;

impl<'de> serde::de::Visitor<'de> for OptStringOrNumberVisitor {
    type Value = Option<String>;

    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("a string, number, or null")
    }

    #[inline]
    fn visit_none<E: serde::de::Error>(self) -> Result<Option<String>, E> {
        Ok(None)
    }

    #[inline]
    fn visit_unit<E: serde::de::Error>(self) -> Result<Option<String>, E> {
        Ok(None)
    }

    fn visit_some<D2: Deserializer<'de>>(self, d: D2) -> Result<Option<String>, D2::Error> {
        string_or_number(d).map(Some)
    }

    // The deserializer may call these directly when the value is not wrapped in
    // an Option (e.g. the field is present but not null).
    #[inline]
    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Option<String>, E> {
        Ok(Some(v.to_owned()))
    }

    #[inline]
    fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Option<String>, E> {
        Ok(Some(v))
    }

    #[inline]
    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Option<String>, E> {
        Ok(Some(v.to_string()))
    }

    #[inline]
    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Option<String>, E> {
        Ok(Some(v.to_string()))
    }

    #[inline]
    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Option<String>, E> {
        Ok(Some(v.to_string()))
    }
}

// ---------------------------------------------------------------------------
// string_or_number_i64
// ---------------------------------------------------------------------------

/// Deserialise a field that may be either a quoted `"123"` string or a bare `123` number,
/// yielding an `i64`.
pub(crate) fn string_or_number_i64<'de, D>(d: D) -> Result<i64, D::Error>
where
    D: Deserializer<'de>,
{
    d.deserialize_any(I64Visitor)
}

struct I64Visitor;

impl<'de> serde::de::Visitor<'de> for I64Visitor {
    type Value = i64;

    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("an i64 as a number or string")
    }

    #[inline]
    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<i64, E> {
        Ok(v)
    }

    #[inline]
    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<i64, E> {
        i64::try_from(v).map_err(|_| E::custom("u64 out of i64 range"))
    }

    #[inline]
    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<i64, E> {
        v.parse::<i64>().map_err(E::custom)
    }

    #[inline]
    fn visit_string<E: serde::de::Error>(self, v: String) -> Result<i64, E> {
        v.parse::<i64>().map_err(E::custom)
    }
}

// ---------------------------------------------------------------------------
// opt_string_or_number_i64
// ---------------------------------------------------------------------------

/// Deserialise an optional field that may be a JSON string, number, or null, yielding `Option<i64>`.
///
/// `null` or missing → `None`; string or number → `Some(i64)`.
/// Mainnet V3 returns `autoAssetExchange` as `"0"` (string), `0` (number), or `null`.
pub(crate) fn opt_string_or_number_i64<'de, D>(d: D) -> Result<Option<i64>, D::Error>
where
    D: Deserializer<'de>,
{
    d.deserialize_any(OptI64Visitor)
}

struct OptI64Visitor;

impl<'de> serde::de::Visitor<'de> for OptI64Visitor {
    type Value = Option<i64>;

    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("an i64 as number, string, or null")
    }

    #[inline]
    fn visit_none<E: serde::de::Error>(self) -> Result<Option<i64>, E> {
        Ok(None)
    }

    #[inline]
    fn visit_unit<E: serde::de::Error>(self) -> Result<Option<i64>, E> {
        Ok(None)
    }

    fn visit_some<D2: Deserializer<'de>>(self, d: D2) -> Result<Option<i64>, D2::Error> {
        string_or_number_i64(d).map(Some)
    }

    #[inline]
    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Option<i64>, E> {
        Ok(Some(v))
    }

    #[inline]
    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Option<i64>, E> {
        i64::try_from(v).map(Some).map_err(|_| E::custom("u64 out of i64 range"))
    }

    #[inline]
    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Option<i64>, E> {
        v.parse::<i64>().map(Some).map_err(E::custom)
    }

    #[inline]
    fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Option<i64>, E> {
        v.parse::<i64>().map(Some).map_err(E::custom)
    }
}