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
use std::sync::Arc;

use reqwest::{
    header::{HeaderMap, HeaderName, HeaderValue},
    Client, Method,
};
use serde::Deserialize;

use crate::{HttpClientConfig, HttpClientError, HttpClientResult, Json, RequestBuilder};

/// LongPort HTTP client
#[derive(Clone)]
pub struct HttpClient {
    pub(crate) http_cli: Client,
    pub(crate) config: Arc<HttpClientConfig>,
    pub(crate) default_headers: HeaderMap,
}

impl HttpClient {
    /// Create a new `HttpClient`
    pub fn new(config: HttpClientConfig) -> Self {
        Self {
            http_cli: Client::new(),
            config: Arc::new(config),
            default_headers: HeaderMap::new(),
        }
    }

    /// Create a new `HttpClient` from the given environment variables
    ///
    /// # Variables
    ///
    /// - LONGPORT_APP_KEY
    /// - LONGPORT_APP_SECRET
    /// - LONGPORT_ACCESS_TOKEN
    /// - LONGPORT_HTTP_URL
    pub fn from_env() -> Result<Self, HttpClientError> {
        Ok(Self::new(HttpClientConfig::from_env()?))
    }

    /// Set the default header
    pub fn header<K, V>(mut self, key: K, value: V) -> Self
    where
        K: TryInto<HeaderName>,
        V: TryInto<HeaderValue>,
    {
        let key = key.try_into();
        let value = value.try_into();
        if let (Ok(key), Ok(value)) = (key, value) {
            self.default_headers.append(key, value);
        }
        self
    }

    /// Create a new request builder
    #[inline]
    pub fn request(&self, method: Method, path: impl Into<String>) -> RequestBuilder<(), (), ()> {
        RequestBuilder::new(self.clone(), method, path)
    }

    /// Get the socket OTP(One Time Password)
    ///
    /// Reference: <https://open.longportapp.com/en/docs/socket-token-api>
    pub async fn get_otp(&self) -> HttpClientResult<String> {
        #[derive(Debug, Deserialize)]
        struct Response {
            otp: String,
        }

        Ok(self
            .request(Method::GET, "/v1/socket/token")
            .response::<Json<Response>>()
            .send()
            .await?
            .0
            .otp)
    }

    /// Get the socket OTP v2(One Time Password)
    ///
    /// Reference: <https://open.longportapp.com/en/docs/socket-token-api>
    pub async fn get_otp_v2(&self) -> HttpClientResult<String> {
        #[derive(Debug, Deserialize)]
        struct Response {
            otp: String,
        }

        Ok(self
            .request(Method::GET, "/v2/socket/token")
            .response::<Json<Response>>()
            .send()
            .await?
            .0
            .otp)
    }
}