use crate::common::query::QueryWriter;
use crate::transport::pagination::PaginatedRequest;
use super::{Loc, Sort, TimeFrame};
#[derive(Clone, Debug, Default)]
pub struct BarsRequest {
pub symbols: Vec<String>,
pub timeframe: TimeFrame,
pub start: Option<String>,
pub end: Option<String>,
pub limit: Option<u32>,
pub sort: Option<Sort>,
pub loc: Option<Loc>,
pub page_token: Option<String>,
}
#[derive(Clone, Debug, Default)]
pub struct QuotesRequest {
pub symbols: Vec<String>,
pub start: Option<String>,
pub end: Option<String>,
pub limit: Option<u32>,
pub sort: Option<Sort>,
pub loc: Option<Loc>,
pub page_token: Option<String>,
}
#[derive(Clone, Debug, Default)]
pub struct TradesRequest {
pub symbols: Vec<String>,
pub start: Option<String>,
pub end: Option<String>,
pub limit: Option<u32>,
pub sort: Option<Sort>,
pub loc: Option<Loc>,
pub page_token: Option<String>,
}
#[derive(Clone, Debug, Default)]
pub struct LatestBarsRequest {
pub symbols: Vec<String>,
pub loc: Option<Loc>,
}
#[derive(Clone, Debug, Default)]
pub struct LatestQuotesRequest {
pub symbols: Vec<String>,
pub loc: Option<Loc>,
}
#[derive(Clone, Debug, Default)]
pub struct LatestTradesRequest {
pub symbols: Vec<String>,
pub loc: Option<Loc>,
}
#[derive(Clone, Debug, Default)]
pub struct LatestOrderbooksRequest {
pub symbols: Vec<String>,
pub loc: Option<Loc>,
}
#[derive(Clone, Debug, Default)]
pub struct SnapshotsRequest {
pub symbols: Vec<String>,
pub loc: Option<Loc>,
}
impl BarsRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
let mut query = QueryWriter::default();
query.push_csv("symbols", self.symbols);
query.push_opt("timeframe", Some(self.timeframe));
query.push_opt("start", self.start);
query.push_opt("end", self.end);
query.push_opt("limit", self.limit);
query.push_opt("page_token", self.page_token);
query.push_opt("sort", self.sort);
query.finish()
}
}
impl QuotesRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
let mut query = QueryWriter::default();
query.push_csv("symbols", self.symbols);
query.push_opt("start", self.start);
query.push_opt("end", self.end);
query.push_opt("limit", self.limit);
query.push_opt("page_token", self.page_token);
query.push_opt("sort", self.sort);
query.finish()
}
}
impl TradesRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
let mut query = QueryWriter::default();
query.push_csv("symbols", self.symbols);
query.push_opt("start", self.start);
query.push_opt("end", self.end);
query.push_opt("limit", self.limit);
query.push_opt("page_token", self.page_token);
query.push_opt("sort", self.sort);
query.finish()
}
}
impl LatestBarsRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
latest_query(self.symbols)
}
}
impl LatestQuotesRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
latest_query(self.symbols)
}
}
impl LatestTradesRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
latest_query(self.symbols)
}
}
impl LatestOrderbooksRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
latest_query(self.symbols)
}
}
impl SnapshotsRequest {
pub(crate) fn to_query(self) -> Vec<(String, String)> {
latest_query(self.symbols)
}
}
impl PaginatedRequest for BarsRequest {
fn with_page_token(&self, page_token: Option<String>) -> Self {
let mut next = self.clone();
next.page_token = page_token;
next
}
}
impl PaginatedRequest for QuotesRequest {
fn with_page_token(&self, page_token: Option<String>) -> Self {
let mut next = self.clone();
next.page_token = page_token;
next
}
}
impl PaginatedRequest for TradesRequest {
fn with_page_token(&self, page_token: Option<String>) -> Self {
let mut next = self.clone();
next.page_token = page_token;
next
}
}
fn latest_query(symbols: Vec<String>) -> Vec<(String, String)> {
let mut query = QueryWriter::default();
query.push_csv("symbols", symbols);
query.finish()
}
#[cfg(test)]
mod tests {
use crate::transport::pagination::PaginatedRequest;
use super::{
BarsRequest, LatestBarsRequest, LatestOrderbooksRequest, LatestQuotesRequest,
LatestTradesRequest, Loc, QuotesRequest, SnapshotsRequest, Sort, TimeFrame, TradesRequest,
};
#[test]
fn bars_request_serializes_official_query_words_without_loc() {
let query = BarsRequest {
symbols: vec!["BTC/USD".into(), "ETH/USD".into()],
timeframe: TimeFrame::from("1Min"),
start: Some("2026-04-04T00:00:00Z".into()),
end: Some("2026-04-04T00:02:00Z".into()),
limit: Some(2),
sort: Some(Sort::Desc),
loc: Some(Loc::Eu1),
page_token: Some("page-2".into()),
}
.to_query();
assert_eq!(
query,
vec![
("symbols".to_string(), "BTC/USD,ETH/USD".to_string()),
("timeframe".to_string(), "1Min".to_string()),
("start".to_string(), "2026-04-04T00:00:00Z".to_string()),
("end".to_string(), "2026-04-04T00:02:00Z".to_string()),
("limit".to_string(), "2".to_string()),
("page_token".to_string(), "page-2".to_string()),
("sort".to_string(), "desc".to_string()),
]
);
}
#[test]
fn quotes_and_trades_requests_serialize_official_query_words_without_loc() {
let quotes_query = QuotesRequest {
symbols: vec!["BTC/USD".into()],
start: Some("2026-04-04T00:00:00Z".into()),
end: Some("2026-04-04T00:00:05Z".into()),
limit: Some(1),
sort: Some(Sort::Asc),
loc: Some(Loc::Us1),
page_token: Some("page-3".into()),
}
.to_query();
assert_eq!(
quotes_query,
vec![
("symbols".to_string(), "BTC/USD".to_string()),
("start".to_string(), "2026-04-04T00:00:00Z".to_string()),
("end".to_string(), "2026-04-04T00:00:05Z".to_string()),
("limit".to_string(), "1".to_string()),
("page_token".to_string(), "page-3".to_string()),
("sort".to_string(), "asc".to_string()),
]
);
let trades_query = TradesRequest {
symbols: vec!["BTC/USD".into()],
start: Some("2026-04-04T00:01:00Z".into()),
end: Some("2026-04-04T00:01:03Z".into()),
limit: Some(1),
sort: Some(Sort::Desc),
loc: Some(Loc::Us),
page_token: Some("page-4".into()),
}
.to_query();
assert_eq!(
trades_query,
vec![
("symbols".to_string(), "BTC/USD".to_string()),
("start".to_string(), "2026-04-04T00:01:00Z".to_string()),
("end".to_string(), "2026-04-04T00:01:03Z".to_string()),
("limit".to_string(), "1".to_string()),
("page_token".to_string(), "page-4".to_string()),
("sort".to_string(), "desc".to_string()),
]
);
}
#[test]
fn historical_requests_replace_page_token_through_shared_pagination_trait() {
let bars = BarsRequest {
page_token: Some("page-2".into()),
..BarsRequest::default()
};
let quotes = QuotesRequest {
page_token: Some("page-3".into()),
..QuotesRequest::default()
};
let trades = TradesRequest {
page_token: Some("page-4".into()),
..TradesRequest::default()
};
assert_eq!(
bars.with_page_token(Some("page-9".into()))
.page_token
.as_deref(),
Some("page-9")
);
assert_eq!(
quotes
.with_page_token(Some("page-8".into()))
.page_token
.as_deref(),
Some("page-8")
);
assert_eq!(
trades
.with_page_token(Some("page-7".into()))
.page_token
.as_deref(),
Some("page-7")
);
}
#[test]
fn latest_requests_serialize_symbols_only_without_loc() {
let bars_query = LatestBarsRequest {
symbols: vec!["BTC/USD".into(), "ETH/USD".into()],
loc: Some(Loc::Us1),
}
.to_query();
assert_eq!(
bars_query,
vec![("symbols".to_string(), "BTC/USD,ETH/USD".to_string())]
);
let quotes_query = LatestQuotesRequest {
symbols: vec!["BTC/USD".into()],
loc: Some(Loc::Eu1),
}
.to_query();
assert_eq!(
quotes_query,
vec![("symbols".to_string(), "BTC/USD".to_string())]
);
let trades_query = LatestTradesRequest {
symbols: vec!["BTC/USD".into()],
loc: Some(Loc::Us),
}
.to_query();
assert_eq!(
trades_query,
vec![("symbols".to_string(), "BTC/USD".to_string())]
);
let orderbooks_query = LatestOrderbooksRequest {
symbols: vec!["BTC/USD".into()],
loc: Some(Loc::Us1),
}
.to_query();
assert_eq!(
orderbooks_query,
vec![("symbols".to_string(), "BTC/USD".to_string())]
);
}
#[test]
fn snapshots_request_serializes_symbols_only_without_loc() {
let query = SnapshotsRequest {
symbols: vec!["BTC/USD".into(), "ETH/USD".into()],
loc: Some(Loc::Eu1),
}
.to_query();
assert_eq!(
query,
vec![("symbols".to_string(), "BTC/USD,ETH/USD".to_string())]
);
}
}