nautilus_coinapi/http/
query.rs1use std::num::NonZeroUsize;
19
20use jiff::{RoundMode, Timestamp, TimestampRound, Unit};
21use serde::{Deserialize, Serialize, Serializer};
22
23use super::error::Error;
24use crate::common::enums::OhlcvPeriod;
25
26pub const HISTORY_LIMIT_MAX: u32 = 100_000;
28
29#[derive(Debug, Clone, Serialize)]
34pub struct HistoryQuery {
35 #[serde(serialize_with = "serialize_time")]
36 pub time_start: Timestamp,
37 #[serde(serialize_with = "serialize_time")]
38 pub time_end: Timestamp,
39 pub limit: u32,
40}
41
42impl HistoryQuery {
43 pub(super) fn validate(&self) -> Result<(), Error> {
44 if provider_time(self.time_start)? >= provider_time(self.time_end)? {
45 return Err(Error::Validation(
46 "time_start must precede time_end at CoinAPI's 100 ns resolution".into(),
47 ));
48 }
49
50 if !(1..=HISTORY_LIMIT_MAX).contains(&self.limit) {
51 return Err(Error::Validation(format!(
52 "limit must be in [1, {HISTORY_LIMIT_MAX}]"
53 )));
54 }
55
56 Ok(())
57 }
58}
59
60fn serialize_time<S: Serializer>(time: &Timestamp, serializer: S) -> Result<S::Ok, S::Error> {
62 let time = provider_time(*time).map_err(serde::ser::Error::custom)?;
63 serializer.collect_str(&format_args!("{time:.7}"))
64}
65
66pub(super) fn provider_time(time: Timestamp) -> Result<Timestamp, Error> {
69 time.round(
70 TimestampRound::new()
71 .smallest(Unit::Nanosecond)
72 .increment(100)
73 .mode(RoundMode::Ceil),
74 )
75 .map_err(|e| Error::Validation(format!("timestamp out of range: {e}")))
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct HistoryRange {
83 pub time_start: Timestamp,
84 pub time_end: Timestamp,
85 pub limit: Option<NonZeroUsize>,
86}
87
88#[derive(Debug, Clone, Default, Serialize)]
90pub struct ExchangesQuery {
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub filter_exchange_id: Option<String>,
93}
94
95#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97#[serde(deny_unknown_fields)]
98pub struct SymbolsQuery {
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub filter_symbol_id: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub filter_asset_id: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize)]
107pub struct BookQuery {
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub limit_levels: Option<u32>,
110}
111
112#[derive(Debug, Clone, Serialize)]
114pub struct TradesQuery {
115 #[serde(flatten)]
116 pub range: HistoryQuery,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub include_id: Option<bool>,
119}
120
121#[derive(Debug, Clone, Serialize)]
123pub struct OhlcvQuery {
124 pub period_id: OhlcvPeriod,
125 #[serde(flatten)]
126 pub range: HistoryQuery,
127}