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