Skip to main content

rust_okx/api/finance/requests/
savings.rs

1use serde::Serialize;
2
3use crate::model::{
4    RequestValidationError, ValidateRequest, decimal_string_range, non_empty, one_of,
5    optional_positive_decimal_string, positive_decimal_string, reject_when_present,
6};
7
8/// Request body for `POST /api/v5/finance/savings/purchase-redempt`.
9#[derive(Debug, Clone, Serialize)]
10pub struct SavingsPurchaseRedemptionRequest {
11    ccy: String,
12    amt: String,
13    side: String,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    rate: Option<String>,
16}
17
18impl SavingsPurchaseRedemptionRequest {
19    /// Create a Simple Earn purchase or redemption request.
20    ///
21    /// `side` must be `purchase` or `redempt`.
22    pub fn new(ccy: impl Into<String>, amt: impl Into<String>, side: impl Into<String>) -> Self {
23        Self {
24            ccy: ccy.into(),
25            amt: amt.into(),
26            side: side.into(),
27            rate: None,
28        }
29    }
30
31    /// Set the minimum annual lending rate for a purchase.
32    ///
33    /// OKX documents the accepted range as `0.01` through `3.65` (1%–365%).
34    pub fn rate(mut self, rate: impl Into<String>) -> Self {
35        self.rate = Some(rate.into());
36        self
37    }
38}
39
40impl ValidateRequest for SavingsPurchaseRedemptionRequest {
41    fn validate(&self) -> Result<(), RequestValidationError> {
42        non_empty("ccy", &self.ccy)?;
43        positive_decimal_string("amt", &self.amt)?;
44        one_of(
45            "side",
46            &self.side,
47            &["purchase", "redempt"],
48            "purchase or redempt",
49        )?;
50
51        if self.side == "redempt" {
52            reject_when_present("rate", self.rate.as_ref(), "side is redempt")?;
53        }
54        if let Some(rate) = self.rate.as_deref() {
55            optional_positive_decimal_string("rate", Some(rate))?;
56            decimal_string_range("rate", rate, 0.01, 3.65, "0.01", "3.65")?;
57        }
58        Ok(())
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn redemption_rejects_lending_rate() {
68        let request = SavingsPurchaseRedemptionRequest::new("USDT", "1", "redempt").rate("0.02");
69        assert!(request.validate().is_err());
70    }
71
72    #[test]
73    fn purchase_rate_must_be_in_documented_range() {
74        let request = SavingsPurchaseRedemptionRequest::new("USDT", "1", "purchase").rate("3.66");
75        assert!(request.validate().is_err());
76    }
77}