use std::collections::HashMap;
use std::fmt::Write;
use std::time::{SystemTime, UNIX_EPOCH};
use reqwest::{header::{HeaderMap, HeaderValue}, Url};
use ring::hmac::{sign, Key, HMAC_SHA384};
use serde::de::DeserializeOwned;
use serde_json;
const X_MKT_APIKEY: &'static str = "X-MKT-APIKEY";
const X_MKT_SIGNATURE: &'static str = "X-MKT-SIGNATURE";
const X_MKT_TIMESTAMP: &'static str = "X-MKT-TIMESTAMP";
use crate::internal::errors::{CryptoMktErrorType, CryptoMktResult};
use crate::internal::request::HttpRequest;
#[derive(Debug, Clone)]
pub struct Api<R>
where
R: HttpRequest<Result=CryptoMktResult<String>>
{
api_key: String,
secret_key: String,
domain: String,
api_version: String,
req: Box<R>,
}
impl<R> Api<R>
where
R: HttpRequest<Result=CryptoMktResult<String>>
{
pub fn new<'a>(api_key: &'a str, secret_key: &'a str, http_transport: Box<R>) -> Self {
Api {
api_key: api_key.to_string(),
secret_key: secret_key.to_string(),
domain: "https://api.cryptomkt.com/".to_string(),
api_version: "v1".to_string(),
req: http_transport,
}
}
pub fn domain(&self) -> String {
self.domain.clone()
}
pub fn api_version(&self) -> String {
self.api_version.clone()
}
pub fn build_url<'a>(&self, endpoint: &'a str, params: &HashMap<String, String>) -> Url {
let mut api_url = Url::parse(&self.domain).unwrap();
api_url = api_url
.join(format!("{}/", &self.api_version).as_str())
.unwrap();
api_url = api_url.join(endpoint).unwrap();
for (key, value) in params {
api_url
.query_pairs_mut()
.append_pair(key.as_str(), value.as_str());
}
api_url
}
pub fn get_edge<'a, T>(
&self,
endpoint: &'a str,
params: HashMap<String, String>,
is_public: bool,
) -> CryptoMktResult<T>
where
T: DeserializeOwned,
{
let api_url = self.build_url(endpoint, ¶ms);
let headers = self.build_headers(endpoint, ¶ms, is_public, true);
let result = self.req.get(api_url, headers)?;
match serde_json::from_str(&result) {
Ok(sr) => Ok(sr),
Err(e) => {
println!("{:?}", e);
Err(CryptoMktErrorType::MalformedResource)
}
}
}
pub fn post_edge<'a, T>(
&self,
endpoint: &'a str,
payload: HashMap<String, String>,
) -> CryptoMktResult<T>
where
T: DeserializeOwned,
{
let api_url = self.build_url(endpoint, &HashMap::new());
let headers = self.build_headers(endpoint, &payload, false, false);
let result = self.req.post(api_url, headers, payload)?;
match serde_json::from_str(&result) {
Ok(sr) => Ok(sr),
Err(e) => {
println!("{:?}", e);
Err(CryptoMktErrorType::MalformedResource)
}
}
}
pub fn build_signature_format<'a>(
&self,
endpoint: &'a str,
payload: &HashMap<String, String>,
is_get: bool,
) -> String {
let mut signature: String = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs().to_string(),
Err(_) => "".to_string(),
};
signature += format!("/{}/{}", &self.api_version, &endpoint).as_str();
if !is_get {
let mut keys = payload.keys().collect::<Vec<_>>();
keys.sort();
for k in keys {
signature += payload.get(k).unwrap();
}
}
signature
}
pub fn sign_msg<'a>(&self, msg: &'a str) -> String {
let s_key = Key::new(HMAC_SHA384, self.secret_key.as_bytes());
let sign = sign(&s_key, msg.as_bytes());
let mut output = String::new();
for byte in sign.as_ref() {
write!(output, "{:02x}", byte).unwrap();
}
output
}
fn build_headers<'a>(
&self,
endpoint: &'a str,
payload: &HashMap<String, String>,
is_public: bool,
is_get: bool,
) -> HeaderMap {
let mut headers = HeaderMap::new();
if !is_public {
let msg_to_sign = self.build_signature_format(endpoint, &payload, is_get);
let timestamp = msg_to_sign.split("/").collect::<Vec<&str>>();
headers.insert(
X_MKT_APIKEY,
HeaderValue::from_str(self.api_key.as_str()).unwrap(),
);
headers.insert(
X_MKT_SIGNATURE,
HeaderValue::from_str(self.sign_msg(msg_to_sign.as_str()).as_str()).unwrap(),
);
headers.insert(
X_MKT_TIMESTAMP,
HeaderValue::from_str(timestamp.first().unwrap()).unwrap(),
);
}
headers
}
}