extern crate serde;
extern crate serde_json;
extern crate tokio;
use hyper::client::HttpConnector;
use hyper::rt::{Future, Stream};
use hyper::{Body, Client, HeaderMap, Request, Uri};
use hyper_tls::HttpsConnector;
use serde::Deserialize;
use std::fmt::Debug;
use std::marker::PhantomData;
use super::Result;
use error::*;
use super::adapters::*;
use structs::public::*;
use structs::DateTime;
pub struct Public<Adapter> {
pub uri: String,
client: Client<HttpsConnector<HttpConnector>>,
adapter: PhantomData<Adapter>
}
impl<A> Public<A> {
pub const USER_AGENT: &'static str = "coinbase-pro-rs/0.1.0";
fn request(&self, uri: &str) -> Request<Body> {
let uri: Uri = (self.uri.to_string() + uri).parse().unwrap();
let mut req = Request::get(uri);
req.header("User-Agent", Self::USER_AGENT);
req.body(Body::empty()).unwrap()
}
pub fn get_pub<U>(&self, uri: &str) -> A::Result
where
A: Adapter<U> + 'static,
U: 'static,
for <'de> U: serde::Deserialize<'de>
{
self.call(self.request(uri))
}
pub fn get_feature<U>(&self, request: Request<Body>) -> impl Future<Item = U, Error = CBError>
where for<'de> U: serde::Deserialize<'de>,
{
debug!("{:?}", request);
self.client
.request(request)
.map_err(CBError::Http)
.and_then(|res| res.into_body().concat2().map_err(CBError::Http))
.and_then(|body| {
let res = serde_json::from_slice(&body).map_err(|e| {
serde_json::from_slice(&body)
.map(CBError::Coinbase)
.unwrap_or_else(|_| {
let data = String::from_utf8(body.to_vec()).unwrap();
CBError::Serde { error: e, data }
})
})?;
Ok(res)
})
}
pub fn call<U>(&self, request: Request<Body>) -> A::Result
where
A: Adapter<U> + 'static,
U: 'static,
for<'de> U: serde::Deserialize<'de>,
{
A::process(self.get_feature(request))
}
pub fn new() -> Self {
let https = HttpsConnector::new(4).unwrap();
let client = Client::builder().build::<_, Body>(https);
let uri = "https://api-public.sandbox.pro.coinbase.com".to_string();
Self { uri, client, adapter: PhantomData }
}
pub fn get_time(&self) -> A::Result
where A: Adapter<Time> + 'static
{
self.get_pub("/time")
}
pub fn get_products(&self) -> A::Result
where A: Adapter<Vec<Product>> + 'static
{
self.get_pub("/products")
}
pub fn get_book<T>(&self, product_id: &str) -> A::Result
where A: Adapter<Book<T>> + 'static,
T: BookLevel + Debug + 'static,
T: super::std::marker::Send,
T: for<'de> Deserialize<'de>
{
self.get_pub(&format!(
"/products/{}/book?level={}",
product_id,
T::level()
))
}
pub fn get_ticker(&self, product_id: &str) -> A::Result
where A: Adapter<Ticker> + 'static
{
self.get_pub(&format!("/products/{}/ticker", product_id))
}
pub fn get_trades(&self, product_id: &str) -> A::Result
where A: Adapter<Vec<Trade>> + 'static
{
self.get_pub(&format!("/products/{}/trades", product_id))
}
pub fn get_candles(
&self,
product_id: &str,
start: Option<DateTime>,
end: Option<DateTime>,
granularity: Granularity,
) -> A::Result
where A: Adapter<Vec<Candle>> + 'static
{
let param_start = start
.map(|x| format!("&start={}", x.to_rfc3339()))
.unwrap_or_default();
let param_end = end
.map(|x| format!("&end={}", x.to_rfc3339()))
.unwrap_or_default();
let req = format!(
"/products/{}/candles?granularity={}{}{}",
product_id, granularity as usize, param_start, param_end
);
self.get_pub(&req)
}
pub fn get_stats24h(&self, product_id: &str) -> A::Result
where A: Adapter<Stats24H> + 'static
{
self.get_pub(&format!("/products/{}/stats", product_id))
}
pub fn get_currencies(&self) -> A::Result
where A: Adapter<Vec<Currency>> + 'static
{
self.get_pub("/currencies")
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::prelude::*;
use time::Duration;
#[test]
fn test_get_time() {
let client: Public<Sync> = Public::new();
let time = client.get_time().unwrap();
let time_str = format!("{:?}", time);
assert!(time_str.starts_with("Time {"));
assert!(time_str.contains("iso:"));
assert!(time_str.contains("epoch:"));
assert!(time_str.ends_with("}"));
}
#[test]
fn test_get_products() {
let client: Public<Sync> = Public::new();
let products = client.get_products().unwrap();
let str = format!("{:?}", products);
assert!(str.contains("{ id: \"BTC-USD\""));
}
#[test]
fn test_get_book() {
let client: Public<Sync> = Public::new();
let book_l1 = client.get_book::<BookRecordL1>("BTC-USD").unwrap();
let str1 = format!("{:?}", book_l1);
assert_eq!(1, book_l1.bids.len());
assert!(str1.contains("bids: [BookRecordL1"));
let book_l2 = client.get_book::<BookRecordL2>("BTC-USD").unwrap();
let str2 = format!("{:?}", book_l2);
assert!(book_l2.bids.len() > 1);
assert!(str2.contains("[BookRecordL2("));
let book_l3 = client.get_book::<BookRecordL3>("BTC-USD").unwrap();
let str3 = format!("{:?}", book_l3);
assert!(book_l2.bids.len() > 1);
assert!(str2.contains("[BookRecordL2("));
}
#[test]
fn test_get_ticker() {
let client: Public<Sync> = Public::new();
let ticker = client.get_ticker("BTC-USD").unwrap();
let str = format!("{:?}", ticker);
assert!(str.starts_with("Ticker { trade_id:"));
assert!(str.contains("time:"));
}
#[test]
fn test_get_trades() {
let client: Public<Sync> = Public::new();
let trades = client.get_trades("BTC-USD").unwrap();
assert!(trades.len() > 1);
let str = format!("{:?}", trades);
assert!(str.starts_with("[Trade { time: "));
}
#[test]
fn test_get_candles() {
let client: Public<Sync> = Public::new();
let end = Utc::now();
let candles = client
.get_candles("BTC-USD", None, Some(end), Granularity::M1)
.unwrap();
let str = format!("{:?}", candles);
assert!(candles[0].0 > candles[1].0);
}
#[test]
fn test_get_stats24h() {
let client: Public<Sync> = Public::new();
let stats24h = client.get_stats24h("BTC-USD").unwrap();
let str = format!("{:?}", stats24h);
assert!(str.contains("open:"));
assert!(str.contains("high:"));
assert!(str.contains("low:"));
assert!(str.contains("volume:"));
}
#[test]
fn test_get_currencies() {
let client: Public<Sync> = Public::new();
let currencies = client.get_currencies().unwrap();
let currency = currencies.iter().find(|x| x.id == "BTC").unwrap();
assert_eq!(
format!("{:?}", currency),
"Currency { id: \"BTC\", name: \"Bitcoin\", min_size: 0.00000001 }"
);
let currency = currencies.iter().find(|x| x.id == "LTC").unwrap();
assert_eq!(
format!("{:?}", currency),
"Currency { id: \"LTC\", name: \"Litecoin\", min_size: 0.00000001 }"
);
}
}