1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use super::*;
use reqwest::Client;

/// Get asset list.
pub mod assets;
mod auth;
/// Cancel an order.
pub mod cancel_order;
/// Cancel multiple orders.
pub mod cancel_orders;
/// Create an order.
pub mod create_order;
/// Get deposit history.
pub mod deposit_history;
/// Get active orders.
pub mod fetch_active_orders;
/// Get an order information.
pub mod fetch_order;
/// Get multiple orders.
pub mod fetch_orders;
/// Get trade history.
pub mod trade_history;

pub use auth::Credential;

struct ApiExec {
    cred: auth::Credential,
}
impl ApiExec {
    /// path: /v1/x/y/z
    /// params: a=b&c=d
    async fn get<R: serde::de::DeserializeOwned>(
        self,
        path: impl Into<String>,
        params: String,
    ) -> anyhow::Result<R> {
        let path = path.into();
        let params = if params.len() > 0 {
            format!("?{params}")
        } else {
            format!("")
        };

        let auth_headers = auth::GetAuth {
            path: path.clone(),
            params: params.clone(),
        }
        .create(self.cred)?;

        let url = format!("https://api.bitbank.cc{path}{params}");
        let (cli, req) = Client::new().get(url).headers(auth_headers).build_split();
        let resp: Response = cli.execute(req?).await?.json().await?;
        let data = resp.result()?;
        let data: R = serde_json::from_value(data)?;
        Ok(data)
    }

    /// path: /v1/x/y/z
    /// body: json
    async fn post<R: serde::de::DeserializeOwned>(
        self,
        path: impl Into<String>,
        body: String,
    ) -> anyhow::Result<R> {
        let path = path.into();
        let auth_headers = auth::PostAuth { body: body.clone() }.create(self.cred)?;
        let url = format!("https://api.bitbank.cc{path}");
        let (cli, req) = Client::new()
            .post(url)
            .headers(auth_headers)
            .body(body)
            .build_split();
        let resp: Response = cli.execute(req?).await?.json().await?;
        let data = resp.result()?;
        let data: R = serde_json::from_value(data)?;
        Ok(data)
    }
}

#[serde_as]
#[derive(Deserialize, Debug)]
pub struct OrderInfo {
    pub order_id: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub pair: Pair,
    #[serde_as(as = "DisplayFromStr")]
    pub side: Side,
    #[serde_as(as = "DisplayFromStr")]
    #[serde(rename = "type")]
    pub order_type: OrderType,
    #[serde_as(as = "DisplayFromStr")]
    pub start_amount: f64,
    #[serde_as(as = "DisplayFromStr")]
    pub remaining_amount: f64,
    #[serde_as(as = "DisplayFromStr")]
    pub executed_amount: f64,
    #[serde_as(as = "DisplayFromStr")]
    pub price: f64,
    /// post_only only exists iff the order-type is limit otherwise omitted.
    #[serde_as(deserialize_as = "DefaultOnNull")]
    pub post_only: Option<bool>,
    #[serde_as(as = "DisplayFromStr")]
    pub average_price: f64,
    #[serde_as(as = "TimestampMilliSeconds")]
    pub ordered_at: NaiveDateTime,
    #[serde_as(as = "Option<TimestampMilliSeconds>")]
    pub expire_at: Option<NaiveDateTime>,
    #[serde_as(as = "Option<TimestampMilliSeconds>")]
    pub triggered_at: Option<NaiveDateTime>,
    #[serde_as(as = "Option<DisplayFromStr>")]
    pub trigger_price: Option<f64>,
    #[serde_as(as = "Option<TimestampMilliSeconds>")]
    pub canceled_at: Option<NaiveDateTime>,
    #[serde_as(as = "DisplayFromStr")]
    pub status: OrderStatus,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_url_parse() -> anyhow::Result<()> {
        let url = format!("https://api.bitbank.cc/v1/some_api?a=b&c=d");
        let cli = Client::new();
        let req = cli.get(url).build()?;
        dbg!(&req);
        Ok(())
    }
}