pub mod balances;
pub mod common;
pub mod configuration;
pub mod market_data;
pub mod orders;
pub mod trades;
use serde::{Deserialize, Serialize};
use common::Timestamp;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Page<T> {
pub items: Vec<T>,
pub next_cursor: Option<String>,
pub timestamp: Timestamp,
}
#[cfg(feature = "rest")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct RawPage<T> {
data: Vec<T>,
metadata: RawPageMeta,
}
#[cfg(feature = "rest")]
#[derive(Debug, Clone, serde::Deserialize)]
struct RawPageMeta {
timestamp: Timestamp,
#[serde(default)]
next_cursor: Option<String>,
}
#[cfg(feature = "rest")]
impl<T> From<RawPage<T>> for Page<T> {
fn from(raw: RawPage<T>) -> Self {
Self {
items: raw.data,
next_cursor: raw.metadata.next_cursor.filter(|c| !c.is_empty()),
timestamp: raw.metadata.timestamp,
}
}
}
#[cfg(feature = "rest")]
#[derive(Deserialize)]
pub(crate) struct Data<T> {
pub data: T,
}
#[cfg(feature = "rest")]
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
#[cfg(feature = "rest")]
impl<T> OneOrMany<T> {
pub(crate) fn into_first(self) -> Option<T> {
match self {
Self::One(value) => Some(value),
Self::Many(values) => values.into_iter().next(),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn page_round_trips_through_its_own_serde() {
let page = Page {
items: vec![1_u32, 2, 3],
next_cursor: Some("cursor".to_owned()),
timestamp: Timestamp::from_unix_millis(1_700_000_000_000),
};
let json = serde_json::to_string(&page).unwrap();
let back: Page<u32> = serde_json::from_str(&json).unwrap();
assert_eq!(page, back);
}
#[cfg(feature = "rest")]
#[test]
fn raw_page_envelope_converts_and_normalizes_empty_cursor() {
let envelope =
r#"{"data":[10,20],"metadata":{"timestamp":1700000000000,"next_cursor":""}}"#;
let raw: RawPage<u32> = serde_json::from_str(envelope).unwrap();
let page: Page<u32> = raw.into();
assert_eq!(page.items, [10, 20]);
assert_eq!(page.next_cursor, None, "empty cursor normalizes to None");
}
}