rust_okx/api/account/requests/
loans.rs1use serde::Serialize;
2
3use crate::model::{
4 RequestValidationError, ValidateRequest, non_empty, one_of, optional_non_empty,
5 optional_one_of, optional_unsigned_integer_string, positive_decimal_string, range_u64,
6};
7
8fn validate_pagination(
9 after: Option<&str>,
10 before: Option<&str>,
11 limit: Option<u32>,
12) -> Result<(), RequestValidationError> {
13 optional_unsigned_integer_string("after", after)?;
14 optional_unsigned_integer_string("before", before)?;
15 if let Some(limit) = limit {
16 range_u64("limit", u64::from(limit), 1, 100)?;
17 }
18 Ok(())
19}
20
21#[derive(Debug, Clone, Serialize)]
23pub struct SpotManualBorrowRepayRequest {
24 ccy: String,
25 side: String,
26 amt: String,
27}
28
29impl SpotManualBorrowRepayRequest {
30 pub fn new(ccy: impl Into<String>, side: impl Into<String>, amt: impl Into<String>) -> Self {
32 Self {
33 ccy: ccy.into(),
34 side: side.into(),
35 amt: amt.into(),
36 }
37 }
38}
39
40impl ValidateRequest for SpotManualBorrowRepayRequest {
41 fn validate(&self) -> Result<(), RequestValidationError> {
42 non_empty("ccy", &self.ccy)?;
43 one_of("side", &self.side, &["borrow", "repay"], "borrow or repay")?;
44 positive_decimal_string("amt", &self.amt)
45 }
46}
47
48#[derive(Debug, Clone, Serialize)]
50pub struct SetAutoRepayRequest {
51 #[serde(rename = "autoRepay")]
52 auto_repay: bool,
53}
54
55impl SetAutoRepayRequest {
56 pub fn new(auto_repay: bool) -> Self {
58 Self { auto_repay }
59 }
60}
61
62impl ValidateRequest for SetAutoRepayRequest {
63 fn validate(&self) -> Result<(), RequestValidationError> {
64 Ok(())
65 }
66}
67
68#[derive(Debug, Clone, Default, Serialize)]
70pub struct SpotBorrowRepayHistoryRequest {
71 #[serde(skip_serializing_if = "Option::is_none")]
72 ccy: Option<String>,
73 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
74 event_type: Option<String>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 after: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 before: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 limit: Option<u32>,
81}
82
83impl SpotBorrowRepayHistoryRequest {
84 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn currency(mut self, value: impl Into<String>) -> Self {
91 self.ccy = Some(value.into());
92 self
93 }
94
95 pub fn event_type(mut self, value: impl Into<String>) -> Self {
97 self.event_type = Some(value.into());
98 self
99 }
100
101 pub fn after(mut self, value: impl Into<String>) -> Self {
103 self.after = Some(value.into());
104 self
105 }
106
107 pub fn before(mut self, value: impl Into<String>) -> Self {
109 self.before = Some(value.into());
110 self
111 }
112
113 pub fn limit(mut self, value: u32) -> Self {
115 self.limit = Some(value);
116 self
117 }
118}
119
120impl ValidateRequest for SpotBorrowRepayHistoryRequest {
121 fn validate(&self) -> Result<(), RequestValidationError> {
122 optional_non_empty("ccy", self.ccy.as_deref())?;
123 optional_one_of(
124 "type",
125 self.event_type.as_deref(),
126 &["auto_borrow", "auto_repay", "manual_borrow", "manual_repay"],
127 "auto_borrow, auto_repay, manual_borrow, or manual_repay",
128 )?;
129 validate_pagination(self.after.as_deref(), self.before.as_deref(), self.limit)
130 }
131}
132
133#[derive(Debug, Clone, Serialize)]
135pub struct SetAutoEarnRequest {
136 #[serde(rename = "earnType")]
137 earn_type: String,
138 ccy: String,
139 action: String,
140}
141
142impl SetAutoEarnRequest {
143 pub fn new(
148 earn_type: impl Into<String>,
149 ccy: impl Into<String>,
150 action: impl Into<String>,
151 ) -> Self {
152 Self {
153 earn_type: earn_type.into(),
154 ccy: ccy.into(),
155 action: action.into(),
156 }
157 }
158}
159
160impl ValidateRequest for SetAutoEarnRequest {
161 fn validate(&self) -> Result<(), RequestValidationError> {
162 one_of("earnType", &self.earn_type, &["0", "1"], "0 or 1")?;
163 non_empty("ccy", &self.ccy)?;
164 one_of(
165 "action",
166 &self.action,
167 &["turn_on", "turn_off"],
168 "turn_on or turn_off",
169 )
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn spot_manual_borrow_repay_rejects_invalid_side() {
179 let request = SpotManualBorrowRepayRequest::new("USDT", "lend", "10");
180 assert!(request.validate().is_err());
181 }
182
183 #[test]
184 fn spot_history_rejects_limit_over_one_hundred() {
185 let request = SpotBorrowRepayHistoryRequest::new().limit(101);
186 assert!(request.validate().is_err());
187 }
188
189 #[test]
190 fn auto_earn_uses_current_wire_fields() {
191 let request = SetAutoEarnRequest::new("0", "BTC", "turn_on");
192 request.validate().unwrap();
193 let value = serde_json::to_value(request).unwrap();
194 assert_eq!(value["earnType"], "0");
195 assert_eq!(value["action"], "turn_on");
196 assert!(value.get("autoEarn").is_none());
197 }
198}