rust_okx/api/finance/requests/
flexible_loan.rs1use serde::Serialize;
2
3use crate::model::{
4 RequestValidationError, ValidateRequest, non_empty, one_of, optional_non_empty,
5 optional_positive_decimal_string, optional_unsigned_integer_string, positive_decimal_string,
6 range_u64,
7};
8
9fn validate_order_id(ord_id: Option<&str>) -> Result<(), RequestValidationError> {
10 optional_non_empty("ordId", ord_id)
11}
12
13fn validate_history_page(
14 after: Option<&str>,
15 before: Option<&str>,
16 limit: Option<u32>,
17) -> Result<(), RequestValidationError> {
18 optional_unsigned_integer_string("after", after)?;
19 optional_unsigned_integer_string("before", before)?;
20 if let Some(limit) = limit {
21 range_u64("limit", u64::from(limit), 1, 100)?;
22 }
23 Ok(())
24}
25
26#[derive(Debug, Clone, Default, Serialize)]
28pub struct FlexibleLoanCollateralAssetsRequest {
29 #[serde(skip_serializing_if = "Option::is_none")]
30 ccy: Option<String>,
31 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
32 ord_id: Option<String>,
33}
34
35impl FlexibleLoanCollateralAssetsRequest {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn currency(mut self, value: impl Into<String>) -> Self {
43 self.ccy = Some(value.into());
44 self
45 }
46
47 pub fn order_id(mut self, value: impl Into<String>) -> Self {
49 self.ord_id = Some(value.into());
50 self
51 }
52}
53
54impl ValidateRequest for FlexibleLoanCollateralAssetsRequest {
55 fn validate(&self) -> Result<(), RequestValidationError> {
56 optional_non_empty("ccy", self.ccy.as_deref())?;
57 validate_order_id(self.ord_id.as_deref())
58 }
59}
60
61#[derive(Debug, Clone, Serialize)]
63pub struct FlexibleLoanMaxLoanRequest {
64 #[serde(rename = "borrowCcy")]
65 borrow_ccy: String,
66 #[serde(rename = "collateralCcy", skip_serializing_if = "Option::is_none")]
67 collateral_ccy: Option<String>,
68 #[serde(rename = "collateralAmt", skip_serializing_if = "Option::is_none")]
69 collateral_amt: Option<String>,
70 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
71 ord_id: Option<String>,
72}
73
74impl FlexibleLoanMaxLoanRequest {
75 pub fn new(borrow_ccy: impl Into<String>) -> Self {
77 Self {
78 borrow_ccy: borrow_ccy.into(),
79 collateral_ccy: None,
80 collateral_amt: None,
81 ord_id: None,
82 }
83 }
84
85 pub fn collateral(mut self, ccy: impl Into<String>, amt: impl Into<String>) -> Self {
87 self.collateral_ccy = Some(ccy.into());
88 self.collateral_amt = Some(amt.into());
89 self
90 }
91
92 pub fn order_id(mut self, value: impl Into<String>) -> Self {
94 self.ord_id = Some(value.into());
95 self
96 }
97}
98
99impl ValidateRequest for FlexibleLoanMaxLoanRequest {
100 fn validate(&self) -> Result<(), RequestValidationError> {
101 non_empty("borrowCcy", &self.borrow_ccy)?;
102 optional_non_empty("collateralCcy", self.collateral_ccy.as_deref())?;
103 optional_positive_decimal_string("collateralAmt", self.collateral_amt.as_deref())?;
104 validate_order_id(self.ord_id.as_deref())?;
105
106 if self.collateral_ccy.is_some() != self.collateral_amt.is_some() {
107 return Err(RequestValidationError::RequiredWhen {
108 field: if self.collateral_ccy.is_some() {
109 "collateralAmt"
110 } else {
111 "collateralCcy"
112 },
113 condition: "the other collateral field is present",
114 });
115 }
116 Ok(())
117 }
118}
119
120#[derive(Debug, Clone, Default, Serialize)]
122pub struct FlexibleLoanMaxRedeemRequest {
123 #[serde(skip_serializing_if = "Option::is_none")]
124 ccy: Option<String>,
125 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
126 ord_id: Option<String>,
127}
128
129impl FlexibleLoanMaxRedeemRequest {
130 pub fn new() -> Self {
132 Self::default()
133 }
134
135 pub fn currency(mut self, value: impl Into<String>) -> Self {
137 self.ccy = Some(value.into());
138 self
139 }
140
141 pub fn order_id(mut self, value: impl Into<String>) -> Self {
143 self.ord_id = Some(value.into());
144 self
145 }
146}
147
148impl ValidateRequest for FlexibleLoanMaxRedeemRequest {
149 fn validate(&self) -> Result<(), RequestValidationError> {
150 optional_non_empty("ccy", self.ccy.as_deref())?;
151 validate_order_id(self.ord_id.as_deref())
152 }
153}
154
155#[derive(Debug, Clone, Serialize)]
157pub struct FlexibleLoanAdjustCollateralRequest {
158 #[serde(rename = "ordId")]
159 ord_id: String,
160 #[serde(rename = "collateralCcy")]
161 collateral_ccy: String,
162 amt: String,
163 #[serde(rename = "type")]
164 adjustment_type: String,
165}
166
167impl FlexibleLoanAdjustCollateralRequest {
168 pub fn new(
170 ord_id: impl Into<String>,
171 collateral_ccy: impl Into<String>,
172 amt: impl Into<String>,
173 adjustment_type: impl Into<String>,
174 ) -> Self {
175 Self {
176 ord_id: ord_id.into(),
177 collateral_ccy: collateral_ccy.into(),
178 amt: amt.into(),
179 adjustment_type: adjustment_type.into(),
180 }
181 }
182}
183
184impl ValidateRequest for FlexibleLoanAdjustCollateralRequest {
185 fn validate(&self) -> Result<(), RequestValidationError> {
186 non_empty("ordId", &self.ord_id)?;
187 non_empty("collateralCcy", &self.collateral_ccy)?;
188 positive_decimal_string("amt", &self.amt)?;
189 one_of(
190 "type",
191 &self.adjustment_type,
192 &["add", "reduce"],
193 "add or reduce",
194 )
195 }
196}
197
198#[derive(Debug, Clone, Default, Serialize)]
200pub struct FlexibleLoanInfoRequest {
201 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
202 ord_id: Option<String>,
203}
204
205impl FlexibleLoanInfoRequest {
206 pub fn new() -> Self {
208 Self::default()
209 }
210
211 pub fn order_id(mut self, value: impl Into<String>) -> Self {
213 self.ord_id = Some(value.into());
214 self
215 }
216}
217
218impl ValidateRequest for FlexibleLoanInfoRequest {
219 fn validate(&self) -> Result<(), RequestValidationError> {
220 validate_order_id(self.ord_id.as_deref())
221 }
222}
223
224#[derive(Debug, Clone, Default, Serialize)]
226pub struct FlexibleLoanHistoryRequest {
227 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
228 ord_id: Option<String>,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 after: Option<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 before: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 limit: Option<u32>,
235}
236
237impl FlexibleLoanHistoryRequest {
238 pub fn new() -> Self {
240 Self::default()
241 }
242
243 pub fn order_id(mut self, value: impl Into<String>) -> Self {
245 self.ord_id = Some(value.into());
246 self
247 }
248
249 pub fn after(mut self, value: impl Into<String>) -> Self {
251 self.after = Some(value.into());
252 self
253 }
254
255 pub fn before(mut self, value: impl Into<String>) -> Self {
257 self.before = Some(value.into());
258 self
259 }
260
261 pub fn limit(mut self, value: u32) -> Self {
263 self.limit = Some(value);
264 self
265 }
266}
267
268impl ValidateRequest for FlexibleLoanHistoryRequest {
269 fn validate(&self) -> Result<(), RequestValidationError> {
270 validate_order_id(self.ord_id.as_deref())?;
271 validate_history_page(self.after.as_deref(), self.before.as_deref(), self.limit)
272 }
273}
274
275#[derive(Debug, Clone, Default, Serialize)]
277pub struct FlexibleLoanInterestAccruedRequest {
278 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
279 ord_id: Option<String>,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 ccy: Option<String>,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 after: Option<String>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 before: Option<String>,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 limit: Option<u32>,
288}
289
290impl FlexibleLoanInterestAccruedRequest {
291 pub fn new() -> Self {
293 Self::default()
294 }
295
296 pub fn order_id(mut self, value: impl Into<String>) -> Self {
298 self.ord_id = Some(value.into());
299 self
300 }
301
302 pub fn currency(mut self, value: impl Into<String>) -> Self {
304 self.ccy = Some(value.into());
305 self
306 }
307
308 pub fn after(mut self, value: impl Into<String>) -> Self {
310 self.after = Some(value.into());
311 self
312 }
313
314 pub fn before(mut self, value: impl Into<String>) -> Self {
316 self.before = Some(value.into());
317 self
318 }
319
320 pub fn limit(mut self, value: u32) -> Self {
322 self.limit = Some(value);
323 self
324 }
325}
326
327impl ValidateRequest for FlexibleLoanInterestAccruedRequest {
328 fn validate(&self) -> Result<(), RequestValidationError> {
329 validate_order_id(self.ord_id.as_deref())?;
330 optional_non_empty("ccy", self.ccy.as_deref())?;
331 validate_history_page(self.after.as_deref(), self.before.as_deref(), self.limit)
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn max_loan_requires_complete_collateral_pair() {
341 let request = FlexibleLoanMaxLoanRequest::new("USDT");
342 request.validate().unwrap();
343
344 let mut value = serde_json::to_value(request).unwrap();
345 value["collateralCcy"] = serde_json::Value::String("BTC".into());
346 assert_eq!(value["borrowCcy"], "USDT");
347 }
348
349 #[test]
350 fn adjustment_rejects_unknown_type() {
351 let request = FlexibleLoanAdjustCollateralRequest::new("1", "BTC", "1", "withdraw");
352 assert!(request.validate().is_err());
353 }
354}