coinbase_rs/
request.rs

1use std::collections::HashMap;
2use std::result;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use hmac::{Hmac, Mac};
6use http::{request, Method, Request, Uri, Version};
7use hyper::Body;
8use sha2::Sha256;
9
10#[derive(Debug)]
11pub struct Error {}
12
13pub type Result<T> = result::Result<T, Error>;
14
15type HmacSha256 = Hmac<Sha256>;
16
17const USER_AGENT: &str = concat!("coinbase-rs/", env!("CARGO_PKG_VERSION"));
18
19#[derive(Clone, Debug, Default)]
20pub struct Parts {
21    /// The request's method
22    pub method: Method,
23
24    /// The request's URI
25    pub uri: Uri,
26
27    /// The request's version
28    pub version: Version,
29
30    /// The request's headers
31    pub headers: HashMap<String, String>,
32}
33
34#[derive(Clone, Debug, Default)]
35pub struct Builder {
36    auth: Option<(String, String)>,
37    parts: Parts,
38    body: Vec<u8>,
39}
40
41impl Builder {
42    pub fn new() -> Builder {
43        Builder {
44            auth: None,
45            parts: Parts {
46                method: Method::GET,
47                uri: "/".parse().unwrap(),
48                version: Version::default(),
49                headers: HashMap::new(),
50            },
51            body: Vec::new(),
52        }
53    }
54
55    pub fn new_with_auth(key: &str, secret: &str) -> Builder {
56        Builder {
57            auth: Some((key.to_string(), secret.to_string())),
58            parts: Parts {
59                method: Method::GET,
60                uri: "/".parse().unwrap(),
61                version: Version::default(),
62                headers: HashMap::new(),
63            },
64            body: Vec::new(),
65        }
66    }
67
68    pub fn method(self, method: Method) -> Builder {
69        let mut _self = self;
70        _self.parts.method = method;
71        _self
72    }
73
74    pub fn uri(self, uri: Uri) -> Builder {
75        let mut _self = self;
76        _self.parts.uri = uri;
77        _self
78    }
79
80    pub fn version(self, version: Version) -> Builder {
81        let mut _self = self;
82        _self.parts.version = version;
83        _self
84    }
85
86    pub fn header(self, key: &str, value: &str) -> Builder {
87        let mut _self = self;
88        _self.parts.headers.insert(key.into(), value.into());
89        _self
90    }
91
92    pub fn body(self, body: &Vec<u8>) -> Builder {
93        let mut _self = self;
94        _self.body = body.clone();
95        _self
96    }
97
98    pub fn build(self) -> Request<Body> {
99        let _self = if let Some((ref key, ref secret)) = self.auth {
100            let timestamp = SystemTime::now()
101                .duration_since(UNIX_EPOCH)
102                .expect("leap-second")
103                .as_secs();
104
105            let sign = Self::sign(
106                secret,
107                timestamp,
108                &self.parts.method,
109                self.parts.uri.path_and_query().unwrap().as_str(),
110                &self.body,
111            );
112
113            self.clone()
114                .header("User-Agent", USER_AGENT)
115                .header("Content-Type", "Application/JSON")
116                .header("CB-VERSION", "2021-01-01")
117                .header("CB-ACCESS-KEY", key)
118                .header("CB-ACCESS-SIGN", &sign)
119                .header("CB-ACCESS-TIMESTAMP", &timestamp.to_string())
120        } else {
121            self
122        };
123
124        let mut builder = request::Builder::new()
125            .method(_self.parts.method)
126            .uri(_self.parts.uri);
127        for (key, value) in _self.parts.headers {
128            builder = builder.header(&key, &value);
129        }
130        builder.body(_self.body.into()).unwrap()
131    }
132
133    fn sign(secret: &str, timestamp: u64, method: &Method, path: &str, body: &Vec<u8>) -> String {
134        let mut mac: Hmac<Sha256> =
135            HmacSha256::new_varkey(&secret.as_bytes()).expect("Hmac::new(secret)");
136        let input = timestamp.to_string() + method.as_str() + path;
137        mac.input(input.as_bytes());
138        mac.input(body);
139        format!("{:x}", &mac.result().code())
140    }
141}