mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-24 09:58:06 +00:00
feat(book): align /book response fields
This commit is contained in:
+14
-2
@@ -1914,7 +1914,11 @@ mod tests {
|
|||||||
],
|
],
|
||||||
"asks": [
|
"asks": [
|
||||||
{"price": "0.76", "size": "50.0"}
|
{"price": "0.76", "size": "50.0"}
|
||||||
]
|
],
|
||||||
|
"min_order_size": "1",
|
||||||
|
"neg_risk": false,
|
||||||
|
"tick_size": "0.01",
|
||||||
|
"last_trade_price": "0.755"
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
let mock = server
|
let mock = server
|
||||||
@@ -1935,6 +1939,10 @@ mod tests {
|
|||||||
assert_eq!(book.market, "0x123");
|
assert_eq!(book.market, "0x123");
|
||||||
assert_eq!(book.bids.len(), 1);
|
assert_eq!(book.bids.len(), 1);
|
||||||
assert_eq!(book.asks.len(), 1);
|
assert_eq!(book.asks.len(), 1);
|
||||||
|
assert_eq!(book.min_order_size, Decimal::from_str("1").unwrap());
|
||||||
|
assert!(!book.neg_risk);
|
||||||
|
assert_eq!(book.tick_size, Decimal::from_str("0.01").unwrap());
|
||||||
|
assert_eq!(book.last_trade_price, Some(Decimal::from_str("0.755").unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@@ -2349,7 +2357,11 @@ mod tests {
|
|||||||
"hash": "test-hash",
|
"hash": "test-hash",
|
||||||
"timestamp": "1234567890",
|
"timestamp": "1234567890",
|
||||||
"bids": [{"price": "0.75", "size": "100.0"}],
|
"bids": [{"price": "0.75", "size": "100.0"}],
|
||||||
"asks": [{"price": "0.76", "size": "50.0"}]
|
"asks": [{"price": "0.76", "size": "50.0"}],
|
||||||
|
"min_order_size": "1",
|
||||||
|
"neg_risk": false,
|
||||||
|
"tick_size": "0.01",
|
||||||
|
"last_trade_price": null
|
||||||
}
|
}
|
||||||
]"#;
|
]"#;
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,71 @@ pub mod deserializers {
|
|||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deserialize a vec that may be `null` (treat `null` as empty vec).
|
||||||
|
pub fn vec_from_null<'de, D, T>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
T: serde::Deserialize<'de>,
|
||||||
|
{
|
||||||
|
Ok(Option::<Vec<T>>::deserialize(deserializer)?.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize an optional Decimal from string/number/null.
|
||||||
|
///
|
||||||
|
/// - `null` => `None`
|
||||||
|
/// - `""` => `None`
|
||||||
|
/// - invalid values => error
|
||||||
|
pub fn optional_decimal_from_string<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Option<Decimal>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value = serde_json::Value::deserialize(deserializer)?;
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => Ok(None),
|
||||||
|
serde_json::Value::String(s) => {
|
||||||
|
let s = s.trim();
|
||||||
|
if s.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
s.parse::<Decimal>()
|
||||||
|
.map(Some)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::Number(n) => Decimal::from_str(&n.to_string())
|
||||||
|
.map(Some)
|
||||||
|
.map_err(serde::de::Error::custom),
|
||||||
|
other => Err(serde::de::Error::custom(format!(
|
||||||
|
"Expected decimal as string/number/null, got {other}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `optional_decimal_from_string`, but returns `None` on parse errors.
|
||||||
|
pub fn optional_decimal_from_string_default_on_error<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Option<Decimal>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value = serde_json::Value::deserialize(deserializer)?;
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => Ok(None),
|
||||||
|
serde_json::Value::String(s) => {
|
||||||
|
let s = s.trim();
|
||||||
|
if s.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(s.parse::<Decimal>().ok())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::Number(n) => Ok(Decimal::from_str(&n.to_string()).ok()),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Raw API response types for efficient parsing
|
/// Raw API response types for efficient parsing
|
||||||
|
|||||||
+13
-2
@@ -1024,14 +1024,25 @@ pub struct BookParams {
|
|||||||
pub struct OrderBookSummary {
|
pub struct OrderBookSummary {
|
||||||
pub market: String,
|
pub market: String,
|
||||||
pub asset_id: String,
|
pub asset_id: String,
|
||||||
pub hash: String,
|
#[serde(default)]
|
||||||
|
pub hash: Option<String>,
|
||||||
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
|
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
|
#[serde(default, deserialize_with = "crate::decode::deserializers::vec_from_null")]
|
||||||
pub bids: Vec<OrderSummary>,
|
pub bids: Vec<OrderSummary>,
|
||||||
|
#[serde(default, deserialize_with = "crate::decode::deserializers::vec_from_null")]
|
||||||
pub asks: Vec<OrderSummary>,
|
pub asks: Vec<OrderSummary>,
|
||||||
|
pub min_order_size: Decimal,
|
||||||
|
pub neg_risk: bool,
|
||||||
|
pub tick_size: Decimal,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
|
||||||
|
)]
|
||||||
|
pub last_trade_price: Option<Decimal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct OrderSummary {
|
pub struct OrderSummary {
|
||||||
#[serde(with = "rust_decimal::serde::str")]
|
#[serde(with = "rust_decimal::serde::str")]
|
||||||
pub price: Decimal,
|
pub price: Decimal,
|
||||||
|
|||||||
Reference in New Issue
Block a user