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
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
extern crate bytes;
extern crate hyper;
extern crate hyper_tls;
extern crate serde;
extern crate serde_json;

use bytes::{BufMut, BytesMut};
use hyper::{
    body::HttpBody,
    client::{Client, HttpConnector},
    Body, Method, Request, StatusCode, Uri,
};
use hyper_tls::HttpsConnector;
use serde::Deserialize;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;

const BASE_URL: &str = "https://api-invest.tinkoff.ru/openapi";

#[derive(Deserialize, Debug)]
pub enum Currency {
    RUB,
    USD,
    EUR,
    GBP,
    HKD,
    CHF,
    JPY,
    CNY,
    TRY,
}

#[derive(Deserialize, Debug)]
pub enum InstrumentType {
    Stock,
    Currency,
    Bond,
    Etf,
}

pub struct TinkoffInvest {
    client: Client<HttpsConnector<HttpConnector>>,
    base_url: &'static str,
    token: &'static str,
}

#[derive(Deserialize, Debug)]
pub struct TinkoffInstrument {
    pub figi: String,
    pub ticker: String,
    pub name: String,
    pub isin: Option<String>,
    #[serde(rename(serialize = "minPriceIncrement", deserialize = "minPriceIncrement"))]
    pub min_price_increment: Option<f64>,
    pub lot: u64,
    pub currency: Option<Currency>,
    #[serde(rename(serialize = "type", deserialize = "type"))]
    pub type_: InstrumentType,
}

#[derive(Deserialize, Debug)]
struct TinkoffInstruments {
    instruments: Vec<TinkoffInstrument>,
}

#[derive(Deserialize, Debug)]
struct TinkoffResponseData<P> {
    #[serde(rename(serialize = "trackingId", deserialize = "trackingId"))]
    tracking_id: String,
    payload: P,
    status: String,
    error: Option<String>,
}

async fn request_get(
    client: &Client<HttpsConnector<HttpConnector>>,
    uri: &Uri,
    token: &str,
) -> Result<BytesMut> {
    let request = Request::builder()
        .method(Method::GET)
        .uri(uri)
        .header("Authorization", "Bearer ".to_owned() + token)
        .body(Body::empty())?;
    let mut response = client.request(request).await?;
    let status = response.status();
    if status == StatusCode::UNAUTHORIZED {
        let error = std::io::Error::new(std::io::ErrorKind::Other, "UNAUTHORIZED");
        return Err(Box::new(error));
    }
    let mut body = BytesMut::with_capacity(1024);
    while let Some(chunk) = response.body_mut().data().await {
        body.put(chunk?);
    }
    Ok(body)
}

impl TinkoffInvest {
    pub fn new(token: &'static str) -> TinkoffInvest {
        let https = HttpsConnector::new();
        let client = Client::builder().build(https);
        TinkoffInvest {
            client,
            base_url: BASE_URL,
            token,
        }
    }

    pub async fn get_stocks(&self) -> Result<Vec<TinkoffInstrument>> {
        let uri = (self.base_url.to_owned() + "/market/stocks").parse::<Uri>()?;
        let response_data = request_get(&self.client, &uri, self.token).await?;
        let data = serde_json::from_slice::<TinkoffResponseData<TinkoffInstruments>>(
            response_data.as_ref(),
        )?;
        Ok(data.payload.instruments)
    }
}