rust_okx/api/public_data/requests/
edge.rs1use serde::Serialize;
2
3use crate::model::{
4 RequestValidationError, ValidateRequest, at_least_one, non_empty, one_of, optional_non_empty,
5 optional_one_of, optional_unsigned_integer_string,
6};
7
8#[derive(Debug, Clone, Serialize)]
10pub struct OptionSummaryRequest {
11 #[serde(rename = "instFamily")]
12 inst_family: String,
13 #[serde(rename = "expTime", skip_serializing_if = "Option::is_none")]
14 exp_time: Option<String>,
15}
16
17impl OptionSummaryRequest {
18 pub fn new(inst_family: impl Into<String>) -> Self {
20 Self {
21 inst_family: inst_family.into(),
22 exp_time: None,
23 }
24 }
25
26 pub fn expiration(mut self, value: impl Into<String>) -> Self {
28 self.exp_time = Some(value.into());
29 self
30 }
31}
32
33impl ValidateRequest for OptionSummaryRequest {
34 fn validate(&self) -> Result<(), RequestValidationError> {
35 non_empty("instFamily", &self.inst_family)?;
36 optional_unsigned_integer_string("expTime", self.exp_time.as_deref())
37 }
38}
39
40#[derive(Debug, Clone, Default, Serialize)]
42pub struct InterestRateLoanQuotaRequest {}
43
44impl InterestRateLoanQuotaRequest {
45 pub fn new() -> Self {
47 Self::default()
48 }
49}
50
51impl ValidateRequest for InterestRateLoanQuotaRequest {
52 fn validate(&self) -> Result<(), RequestValidationError> {
53 Ok(())
54 }
55}
56
57#[derive(Debug, Clone, Serialize)]
59pub struct InstrumentTickBandsRequest {
60 #[serde(rename = "instType")]
61 inst_type: String,
62}
63
64impl InstrumentTickBandsRequest {
65 pub fn new(inst_type: impl Into<String>) -> Self {
67 Self {
68 inst_type: inst_type.into(),
69 }
70 }
71}
72
73impl ValidateRequest for InstrumentTickBandsRequest {
74 fn validate(&self) -> Result<(), RequestValidationError> {
75 one_of(
76 "instType",
77 &self.inst_type,
78 &["FUTURES", "OPTION"],
79 "FUTURES or OPTION",
80 )
81 }
82}
83
84#[derive(Debug, Clone, Serialize)]
86pub struct UnderlyingRequest {
87 #[serde(rename = "instType")]
88 inst_type: String,
89}
90
91impl UnderlyingRequest {
92 pub fn new(inst_type: impl Into<String>) -> Self {
94 Self {
95 inst_type: inst_type.into(),
96 }
97 }
98}
99
100impl ValidateRequest for UnderlyingRequest {
101 fn validate(&self) -> Result<(), RequestValidationError> {
102 one_of(
103 "instType",
104 &self.inst_type,
105 &["SWAP", "FUTURES", "OPTION"],
106 "SWAP, FUTURES, or OPTION",
107 )
108 }
109}
110
111#[derive(Debug, Clone, Default, Serialize)]
113pub struct PublicOptionTradesRequest {
114 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
115 inst_id: Option<String>,
116 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
117 inst_family: Option<String>,
118 #[serde(rename = "optType", skip_serializing_if = "Option::is_none")]
119 option_type: Option<String>,
120}
121
122impl PublicOptionTradesRequest {
123 pub fn new() -> Self {
125 Self::default()
126 }
127
128 pub fn inst_id(mut self, value: impl Into<String>) -> Self {
130 self.inst_id = Some(value.into());
131 self
132 }
133
134 pub fn inst_family(mut self, value: impl Into<String>) -> Self {
136 self.inst_family = Some(value.into());
137 self
138 }
139
140 pub fn option_type(mut self, value: impl Into<String>) -> Self {
142 self.option_type = Some(value.into());
143 self
144 }
145}
146
147impl ValidateRequest for PublicOptionTradesRequest {
148 fn validate(&self) -> Result<(), RequestValidationError> {
149 optional_non_empty("instId", self.inst_id.as_deref())?;
150 optional_non_empty("instFamily", self.inst_family.as_deref())?;
151 optional_one_of(
152 "optType",
153 self.option_type.as_deref(),
154 &["C", "P"],
155 "C or P",
156 )?;
157 at_least_one(
158 "instId, instFamily",
159 &[self.inst_id.is_some(), self.inst_family.is_some()],
160 )
161 }
162}
163
164#[derive(Debug, Clone, Default, Serialize)]
166pub struct MarketDataHistoryRequest {
167 #[serde(skip_serializing_if = "Option::is_none")]
168 module: Option<String>,
169 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
170 inst_type: Option<String>,
171 #[serde(rename = "instIdList", skip_serializing_if = "Option::is_none")]
172 inst_id_list: Option<String>,
173 #[serde(rename = "dateAggrType", skip_serializing_if = "Option::is_none")]
174 date_aggr_type: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 begin: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 end: Option<String>,
179}
180
181impl MarketDataHistoryRequest {
182 pub fn new() -> Self {
184 Self::default()
185 }
186
187 pub fn module(mut self, value: impl Into<String>) -> Self {
189 self.module = Some(value.into());
190 self
191 }
192
193 pub fn inst_type(mut self, value: impl Into<String>) -> Self {
195 self.inst_type = Some(value.into());
196 self
197 }
198
199 pub fn inst_id_list(mut self, value: impl Into<String>) -> Self {
201 self.inst_id_list = Some(value.into());
202 self
203 }
204
205 pub fn date_aggregation(mut self, value: impl Into<String>) -> Self {
207 self.date_aggr_type = Some(value.into());
208 self
209 }
210
211 pub fn begin(mut self, value: impl Into<String>) -> Self {
213 self.begin = Some(value.into());
214 self
215 }
216
217 pub fn end(mut self, value: impl Into<String>) -> Self {
219 self.end = Some(value.into());
220 self
221 }
222}
223
224impl ValidateRequest for MarketDataHistoryRequest {
225 fn validate(&self) -> Result<(), RequestValidationError> {
226 optional_non_empty("module", self.module.as_deref())?;
227 optional_one_of(
228 "instType",
229 self.inst_type.as_deref(),
230 &["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"],
231 "SPOT, MARGIN, SWAP, FUTURES, or OPTION",
232 )?;
233 optional_non_empty("instIdList", self.inst_id_list.as_deref())?;
234 optional_non_empty("dateAggrType", self.date_aggr_type.as_deref())?;
235 optional_unsigned_integer_string("begin", self.begin.as_deref())?;
236 optional_unsigned_integer_string("end", self.end.as_deref())?;
237 Ok(())
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn market_history_uses_okx_field_names() {
247 let request = MarketDataHistoryRequest::new()
248 .module("2")
249 .inst_type("SPOT")
250 .inst_id_list("BTC-USDT")
251 .date_aggregation("daily");
252 request.validate().unwrap();
253 let value = serde_json::to_value(request).unwrap();
254 assert_eq!(value["instType"], "SPOT");
255 assert_eq!(value["instIdList"], "BTC-USDT");
256 assert_eq!(value["dateAggrType"], "daily");
257 }
258
259 #[test]
260 fn option_trades_requires_an_instrument_scope() {
261 assert!(PublicOptionTradesRequest::new().validate().is_err());
262 }
263}