jqdata_blocking/
cli.rs

1use crate::error::Error;
2use crate::model::{Request, Response};
3#[cfg(test)]
4use mockito;
5use reqwest::header::{HeaderValue, CONTENT_TYPE};
6use serde_json::json;
7
8#[cfg(not(test))]
9fn jqdata_url() -> String {
10    String::from("https://dataapi.joinquant.com/apis")
11}
12
13#[cfg(test)]
14fn jqdata_url() -> String {
15    mockito::server_url()
16}
17
18#[derive(Debug, Clone)]
19pub struct JqdataClient {
20    token: String,
21}
22
23/// retrieve token with given credential
24fn get_token(mob: &str, pwd: &str, reuse: bool) -> Result<String, Error> {
25    let method = if reuse {
26        "get_current_token"
27    } else {
28        "get_token"
29    };
30    let token_req = json!({
31        "method": method,
32        "mob": mob,
33        "pwd": pwd,
34    });
35    let client = reqwest::blocking::Client::new();
36    let response = client
37        .post(&jqdata_url())
38        .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
39        .body(token_req.to_string())
40        .send()?;
41    let token: String = response.text()?;
42    if token.starts_with("error") {
43        return Err(Error::Server(token));
44    }
45    Ok(token)
46}
47
48impl JqdataClient {
49    pub fn with_credential(mob: &str, pwd: &str) -> Result<Self, Error> {
50        let token = get_token(mob, pwd, true)?;
51        Ok(JqdataClient { token })
52    }
53
54    pub fn with_token(token: &str) -> Result<Self, Error> {
55        Ok(JqdataClient {
56            token: token.to_string(),
57        })
58    }
59
60    pub fn execute<C: Request + Response>(&self, command: C) -> Result<C::Output, Error> {
61        let req_body = command.request(&self.token)?;
62        let client = reqwest::blocking::Client::new();
63        let response = client
64            .post(&jqdata_url())
65            .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
66            .body(req_body)
67            .send()?;
68        let output = command.response(response)?;
69        Ok(output)
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::model::*;
77    use mockito::mock;
78
79    #[test]
80    fn test_error_response() {
81        let response_body = "error: invalid token";
82        let _m = mock("POST", "/")
83            .with_status(200)
84            .with_body(response_body)
85            .create();
86
87        let client = JqdataClient::with_token("abc").unwrap();
88        let response = client.execute(GetAllSecurities {
89            code: SecurityKind::Stock,
90            date: None,
91        });
92        assert!(response.is_err());
93    }
94
95    #[test]
96    fn test_get_all_securities() {
97        let response_body = {
98            let mut s = String::from("code,display_name,name,start_date,end_date,type\n");
99            s.push_str("000001.XSHE,平安银行,PAYH,1991-04-03,2200-01-01,stock\n");
100            s.push_str("000002.XSHE,万科A,WKA,1991-01-29,2200-01-01,stock\n");
101            s
102        };
103        let _m = mock("POST", "/")
104            .with_status(200)
105            .with_body(&response_body)
106            .create();
107
108        let client = JqdataClient::with_token("abc").unwrap();
109        let ss = client
110            .execute(GetAllSecurities {
111                code: SecurityKind::Stock,
112                date: None,
113            })
114            .unwrap();
115        assert_eq!(
116            vec![
117                Security {
118                    code: "000001.XSHE".to_string(),
119                    display_name: "平安银行".to_string(),
120                    name: "PAYH".to_string(),
121                    start_date: "1991-04-03".to_string(),
122                    end_date: "2200-01-01".to_string(),
123                    kind: SecurityKind::Stock,
124                    parent: None,
125                },
126                Security {
127                    code: "000002.XSHE".to_string(),
128                    display_name: "万科A".to_string(),
129                    name: "WKA".to_string(),
130                    start_date: "1991-01-29".to_string(),
131                    end_date: "2200-01-01".to_string(),
132                    kind: SecurityKind::Stock,
133                    parent: None,
134                }
135            ],
136            ss
137        );
138    }
139}