ccdata_api/
utils.rs

1use serde::de::DeserializeOwned;
2use reqwest::Response;
3use crate::error::Error;
4use crate::{CCUnit, CCAPIEndpoint};
5use crate::schemas::data_api::spot::CCSpotInstrumentStatus;
6use crate::schemas::data_api::news::{CCNewsLang, CCNewsSourceID, CCNewsSourceType, CCNewsStatus};
7
8
9#[cfg(feature = "debug")]
10/// Checks whether the `CCDATA_API_DEBUG` environment variable is enabled.
11/// Note: If `CCDATA_API_DEBUG` variable exists, the reponses will be printed to command line.
12fn debug() -> Result<bool, Error> {
13    dotenv::dotenv()?;
14    let debug: bool = serde_json::from_str(&std::env::var("CCDATA_API_DEBUG")?)?;
15    Ok(debug)
16}
17
18
19/// Market interface that ensures all market enums implement .to_string().
20pub trait Market {
21    /// Converts enum value to `String`.
22    fn to_string(&self) -> String;
23}
24
25
26/// Convert a vector of inputs into a string of inputs suitable for use in REST API requests.
27fn vec_to_str(vec: &Vec<String>) -> String {
28    let mut s: String = String::from("");
29    for v in vec {
30        s.push_str(&v);
31        s.push_str(&",");
32    }
33    s.pop();
34    s
35}
36
37
38/// Converts a vector of optional parameters into a string.
39fn optional_vec_arguments(o: Option<Vec<String>>, api_argument: String) -> String {
40    let mut s: String = String::from("");
41    match o {
42        Some(v) => {
43            s.push_str(&api_argument);
44            s.push_str(&vec_to_str(&v));
45        },
46        None => (),
47    }
48    s
49}
50
51
52/// All possible parameter types for the REST API request.
53pub enum Param<'a> {
54    // Instrument parameters
55    /// Asset symbol
56    Symbol { v: &'a String },
57    /// Instrument symbol
58    Instrument { v: &'a String },
59    /// List of instrument symbols
60    Instruments { v: &'a Vec<String> },
61    /// Chain asset symbol
62    ChainAsset { v: &'a String },
63    /// Asset symbol
64    Asset {v: &'a String},
65    // Overlapping parameters
66    /// Final timestamp up to which the data will be extracted
67    ToTs {v: Option<i64>},
68    /// Final timestamp up to which the data will be extracted
69    ToTimestamp { v: Option<i64> },
70    /// Maximum number of datapoints per API endpoint call
71    Limit { v: Option<usize> },
72    // Special parameters
73    /// Market name
74    Market {v: String},
75    /// Status of the instrument (e.g., `ACTIVE`, `EXPIRED`)
76    InstrumentStatus { v: CCSpotInstrumentStatus },
77    /// Block number on the blockchain
78    OCCoreBlockNumber { v: i64 },
79    /// Blockchain address
80    OCCoreAddress { v: &'a String },
81    /// Asset to quote data in
82    OCCoreQuoteAsset { v: &'a String },
83    /// Language of the news
84    NewsLanguage { v: CCNewsLang },
85    /// Source ID of the news stream
86    NewsSourceID { v: CCNewsSourceID },
87    /// List of news categories
88    NewsCategories { v: Option<Vec<String>> },
89    /// List of news categories to exclude
90    NewsExcludeCategories { v: Option<Vec<String>> },
91    /// Type of news stream
92    NewsSourceType { v: CCNewsSourceType },
93    /// Status of the news stream (e.g., `ACTIVE`, `INACTIVE`)
94    NewsStatus { v: CCNewsStatus },
95}
96
97impl<'a> Param<'a> {
98    fn add_param_to_url(&self, url: &mut String) -> () {
99        let url_param: String = match self {
100            // Instrument parameters
101            Param::Symbol { v } => format!("&fsym={}", v),
102            Param::Instrument { v } => format!("&instrument={}", v),
103            Param::Instruments { v } => format!("&instruments={}", vec_to_str(v.to_owned())),
104            Param::ChainAsset { v } => format!("&chain_asset={}", v),
105            Param::Asset { v } => format!("&asset={}", v),
106            // Overlapping parameters
107            Param::ToTs { v } => match v {
108                Some(v_) => format!("&toTs={}", v_),
109                None => String::from(""),
110            },
111            Param::ToTimestamp { v } => match v {
112                Some(v_) => format!("&to_ts={}", v_),
113                None => String::from(""),
114            },
115            Param::Limit { v } => match v {
116                Some(v_) => format!("&limit={}", v_),
117                None => String::from("&limit=2000"),
118            },
119            // Special parameters
120            Param::Market { v } => format!("&market={}", v),
121            Param::InstrumentStatus { v } => format!("&instrument_status={}", v.to_string()),
122            Param::OCCoreBlockNumber { v } => format!("&block_number={}", v),
123            Param::OCCoreAddress { v } => format!("&address={}", v),
124            Param::OCCoreQuoteAsset { v } => format!("&quote_asset={}", v),
125            Param::NewsLanguage { v } => format!("&lang={}", v.to_string()),
126            Param::NewsSourceID { v } => format!("&source_ids={}", v.to_string()),
127            Param::NewsCategories { v } => optional_vec_arguments(v.to_owned(), String::from("&categories=")),
128            Param::NewsExcludeCategories { v } => optional_vec_arguments(v.to_owned(), String::from("&exclude_categories=")),
129            Param::NewsSourceType { v } => format!("&source_type={}", v.to_string()),
130            Param::NewsStatus { v } => format!("&status={}", v.to_string()),
131        };
132        url.push_str(&url_param);
133    }
134}
135
136
137/// Make a request to the provided URL, validate the status code of the response, and return deserialized data.
138async fn process_request<T: DeserializeOwned>(url: String) -> Result<T, Error> {
139    let response: Response = reqwest::get(url).await?;
140    let response_body: String = response.text().await?;
141    // Print response body to the command line
142    #[cfg(feature = "debug")]
143    if debug()? { println!("{:?}", response_body) }
144    // Replace empty object and empty list placeholders
145    let response_body: String = response_body.replace("{}", "null");
146    // let response_body: String = response_body.replace("[]", "null");
147    let data: T = serde_json::from_str(&response_body)?;
148    Ok(data)
149}
150
151
152/// Constructs a URL for API request, sends the request, and returns the deserialzied response.
153///
154/// # Input
155/// - `api_key`: CCData API key
156/// - `endpoint`: Enum that represents the CCData API endpoint for function to send the request to
157/// - `unit`: Unit for data to be binned by (e.g., `Day`, `Hour`)
158/// - `params`: List of parameters expected by the CCData API endpoint
159/// - `additional_params`: Additional parameters to add to the request
160pub async fn call_api_endpoint<'a, R: DeserializeOwned>(
161    api_key: &String, endpoint: CCAPIEndpoint, unit: CCUnit, params: Vec<Param<'a>>, additional_params: Option<String>) -> Result<R, Error>
162{
163    // Set up a URL for the API endpoint
164    let mut url: String = endpoint.url(&unit);
165    // Add parameters to the URL
166    url.push_str(&format!("?api_key={}", api_key));
167    for param in params {
168        param.add_param_to_url(&mut url);
169    }
170    // Add additional parameters to the URL
171    match additional_params {
172        Some(v) => url.push_str(&v),
173        None => (),
174    }
175    // Process API response
176    process_request::<R>(url).await
177}