Skip to main content

ccdata_api/
utils.rs

1use std::fmt::Display;
2use serde::{Serialize, Deserialize, de::DeserializeOwned};
3use reqwest::Response;
4use crate::error::Error;
5use crate::{Unit, APIEndpointTrait, APIEndpoint};
6use crate::schemas::data_api::spot::SpotInstrumentStatus;
7use crate::schemas::data_api::news::{NewsLang, NewsSourceID, NewsSourceType, NewsStatus};
8
9
10#[cfg(feature = "debug")]
11/// Checks whether the `CCDATA_API_DEBUG` environment variable is enabled.
12/// Note: If `CCDATA_API_DEBUG` variable exists, the reponses will be printed to command line.
13fn debug() -> Result<bool, Error> {
14    dotenv::dotenv()?;
15    let debug: bool = serde_json::from_str(&std::env::var("CCDATA_API_DEBUG")?)?;
16    Ok(debug)
17}
18
19
20#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
21/// Filtering groups.
22/// 
23/// # Description (CoinDesk Documentation) 
24/// When requesting market metadata entries you can filter by specific groups of interest.
25/// To do so just pass the groups of interest into the URL as a comma separated list.
26/// If left empty it will get all data that your account is allowed to access.
27pub enum Group {
28    Activity,
29    AssetTypeSpecificMetrics,
30    Basic,
31    Change,
32    Classification,
33    Contact,
34    Description,
35    DescriptionSummary,
36    General,
37    Id,
38    InstrumentSummary,
39    IntegrationFutures,
40    Internal,
41    Mapping,
42    MappingAdvanced,
43    Message,
44    Metadata,
45    Migration,
46    MktCap,
47    OHLC,
48    OHLCMessage,
49    OHLCSwap,
50    OHLCTrade,
51    OrphanTraces,
52    Price,
53    ResourceLinks,
54    SecurityMetrics,
55    SEO,
56    Source,
57    Status,
58    Supply,
59    SupplyAddresses,
60    SupportedPlatforms,
61    Swap,
62    ToplistRank,
63    Trade,
64    Transactions,
65    Uncles,
66    Volume,
67    Withdrawals,
68}
69
70impl Display for Group {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            Self::Activity => write!(f, "ACTIVITY"),
74            Self::AssetTypeSpecificMetrics => write!(f, "ASSET_TYPE_SPECIFIC_METRICS"),
75            Self::Basic => write!(f, "BASIC"),
76            Self::Change => write!(f, "CHANGE"),
77            Self::Classification => write!(f, "CLASSIFICATION"),
78            Self::Contact => write!(f, "CONTACT"),
79            Self::Description => write!(f, "DESCRIPTION"),
80            Self::DescriptionSummary => write!(f, "DESCRIPTION_SUMMARY"),
81            Self::General => write!(f, "GENERAL"),
82            Self::Id => write!(f, "ID"),
83            Self::InstrumentSummary => write!(f, "INSTRUMENT_SUMMARY"),
84            Self::IntegrationFutures => write!(f, "INTEGRATION_FUTURES"),
85            Self::Internal => write!(f, "INTERNAL"),
86            Self::Mapping => write!(f, "MAPPING"),
87            Self::MappingAdvanced => write!(f, "MAPPING_ADVANCED"),
88            Self::Message => write!(f, "MESSAGE"),
89            Self::Metadata => write!(f, "METADATA"),
90            Self::Migration => write!(f, "MIGRATION"),
91            Self::MktCap => write!(f, "MKT_CAP"),
92            Self::OHLC => write!(f, "OHLC"),
93            Self::OHLCMessage => write!(f, "OHLC_MESSAGE"),
94            Self::OHLCSwap => write!(f, "OHLC_SWAP"),
95            Self::OHLCTrade => write!(f, "OHLC_TRADE"),
96            Self::OrphanTraces => write!(f, "ORPHAN_TRACES"),
97            Self::Price => write!(f, "PRICE"),
98            Self::ResourceLinks => write!(f, "RESOURCE_LINKS"),
99            Self::SecurityMetrics => write!(f, "SECURITY_METRICS"),
100            Self::SEO => write!(f, "SEO"),
101            Self::Source => write!(f, "SOURCE"),
102            Self::Status => write!(f, "STATUS"),
103            Self::Supply => write!(f, "SUPPLY"),
104            Self::SupplyAddresses => write!(f, "SUPPLY_ADDRESSES"),
105            Self::SupportedPlatforms => write!(f, "SUPPORTED_PLATFORMS"),
106            Self::Swap => write!(f, "SWAP"),
107            Self::ToplistRank => write!(f, "TOPLIST_RANK"),
108            Self::Trade => write!(f, "TRADE"),
109            Self::Transactions => write!(f, "TRANSACTIONS"),
110            Self::Uncles => write!(f, "UNCLES"),
111            Self::Volume => write!(f, "VOLUME"),
112            Self::Withdrawals => write!(f, "WITHDRAWALS"),
113        }
114    }
115}
116
117
118#[derive(Clone, Debug, Serialize, Deserialize)]
119/// Specifies the matching priority for the asset key provided in the asset parameter.
120/// 
121/// # Description (CoinDesk Documentation)
122/// This parameter specifies the matching priority for the asset key provided in the asset parameter.
123/// You can choose to match against the list of asset SYMBOLS, CoinDesk internal asset IDS, or asset URIs.
124/// Note that asset SYMBOLS and URIs may change due to rebrands or token switches, but the CoinDesk internal asset ID remains consistent.
125pub enum AssetLookupPriority {
126    Symbol,
127    Id,
128    URI,
129}
130
131impl Display for AssetLookupPriority {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match self {
134            Self::Symbol => write!(f, "SYMBOL"),
135            Self::Id => write!(f, "ID"),
136            Self::URI => write!(f, "URI"),
137        }
138    }
139}
140
141
142/// All possible parameter types for the REST API request.
143pub enum Param<'a> {
144    // Instrument parameters
145    /// Asset symbol
146    Symbol { v: &'a str, },
147    /// Instrument symbol
148    Instrument { v: &'a str, },
149    /// List of instrument symbols
150    Instruments { v: &'a Vec<String>, },
151    /// Chain asset symbol
152    ChainAsset { v: &'a str, },
153    /// Asset symbol
154    Asset { v: &'a str, },
155    /// Assets' symbols
156    Assets { v: Vec<String>, },
157    // Overlapping parameters
158    /// Final timestamp up to which the data will be extracted
159    ToTs { v: Option<i64>, },
160    /// Final timestamp up to which the data will be extracted
161    ToTimestamp { v: Option<i64>, },
162    /// Maximum number of datapoints per API endpoint call
163    Limit { v: Option<usize>, },
164    // Special parameters
165    /// Market name
166    Market { v: String, },
167    /// Markets' names
168    Markets { v: Vec<String>, },
169    /// Status of the instrument (e.g., `ACTIVE`, `EXPIRED`)
170    InstrumentStatus { v: SpotInstrumentStatus, },
171    /// Block number on the blockchain
172    OCCoreBlockNumber { v: i64, },
173    /// Blockchain address
174    OCCoreAddress { v: &'a str, },
175    /// Asset to quote data in
176    OCCoreQuoteAsset { v: &'a str, },
177    /// Language of the news
178    NewsLanguage { v: NewsLang, },
179    /// Source ID of the news stream
180    NewsSourceID { v: NewsSourceID, },
181    /// List of news categories
182    NewsCategories { v: Option<Vec<String>>, },
183    /// List of news categories to exclude
184    NewsExcludeCategories { v: Option<Vec<String>>, },
185    /// Type of news stream
186    NewsSourceType { v: NewsSourceType, },
187    /// Status of the news stream (e.g., `ACTIVE`, `INACTIVE`)
188    NewsStatus { v: NewsStatus, },
189    /// Filtering groups
190    Groups { v: Option<Vec<Group>> },
191    /// Determines if provided instrument values are converted according to internal mappings.
192    ApplyMapping { v: bool, },
193    /// Specifies the matching priority for the asset key provided in the asset parameter.
194    AssetLookupPriority { v: AssetLookupPriority, },
195    /// Specify the digital asset for the quote values by providing either the CoinDesk internal asset ID, its unique SYMBOL,
196    /// or the CoinDesk recommened URI.
197    QuoteAsset { v: &'a str, },
198}
199
200impl<'a> Param<'a> {
201    fn add_param_to_url(&self, url: &mut String) -> () {
202        let url_param: String = match self {
203            // Instrument parameters
204            Self::Symbol { v } => format!("&fsym={v}"),
205            Self::Instrument { v } => format!("&instrument={v}"),
206            Self::Instruments { v } => format!("&instruments={}", v.join(",")),
207            Self::ChainAsset { v } => format!("&chain_asset={v}"),
208            Self::Asset { v } => format!("&asset={v}"),
209            Self::Assets { v } => format!("&assets={}", v.join(",")),
210            // Overlapping parameters
211            Self::ToTs { v } => v.map_or("".to_owned(), |i| format!("&toTs={i}") ),
212            Self::ToTimestamp { v } => v.map_or("".to_owned(), |i| format!("&to_ts={i}") ),
213            Self::Limit { v } => format!("&limit={}", v.unwrap_or(2_000)),
214            // Special parameters
215            Self::Market { v } => format!("&market={v}"),
216            Self::Markets { v } => format!("&markets={}", v.join(",")),
217            Self::InstrumentStatus { v } => format!("&instrument_status={v}"),
218            Self::OCCoreBlockNumber { v } => format!("&block_number={v}"),
219            Self::OCCoreAddress { v } => format!("&address={v}"),
220            Self::OCCoreQuoteAsset { v } => format!("&quote_asset={v}"),
221            Self::NewsLanguage { v } => format!("&lang={v}"),
222            Self::NewsSourceID { v } => format!("&source_ids={v}"),
223            Self::NewsCategories { v } => format!("&categories={}", v.as_ref().unwrap_or(&vec![]).join(",")),
224            Self::NewsExcludeCategories { v } => format!("&exclude_categories={}", v.as_ref().unwrap_or(&vec![]).join(",")),
225            Self::NewsSourceType { v } => format!("&source_type={v}"),
226            Self::NewsStatus { v } => format!("&status={v}"),
227            Self::Groups { v } => v.as_ref().map_or("".to_owned(), |i|
228                format!("&groups={}", i.iter().map(|v| v.to_string() ).collect::<Vec<String>>().join(","))
229            ),
230            Self::ApplyMapping { v } => format!("&apply_mapping={v}"),
231            Self::AssetLookupPriority { v } => format!("&asset_lookup_priority={v}"),
232            Self::QuoteAsset { v } => format!("&quote_asset={v}"),
233        };
234        url.push_str(&url_param);
235    }
236}
237
238
239/// Make a request to the provided URL, validate the status code of the response, and return deserialized data.
240async fn process_request<T: DeserializeOwned>(url: String) -> Result<T, Error> {
241    let response: Response = reqwest::get(url).await?;
242    let response_body: String = response.text().await?;
243    // Print response body to the command line
244    #[cfg(feature = "debug")]
245    if debug()? { println!("{:?}", response_body) }
246    // Replace empty object and empty list placeholders
247    let response_body: String = response_body.replace("{}", "null");
248    // let response_body: String = response_body.replace("[]", "null");
249    let data: T = serde_json::from_str(&response_body)?;
250    Ok(data)
251}
252
253
254/// Constructs a URL for API request, sends the request, and returns the deserialzied response.
255///
256/// # Input
257/// - `api_key`: CoinDesk API key
258/// - `endpoint`: Enum that represents the CoinDesk API endpoint for function to send the request to
259/// - `unit`: Unit for data to be binned by (e.g., `Day`, `Hour`)
260/// - `params`: List of parameters expected by the CoinDesk API endpoint
261/// - `additional_params`: Additional parameters to add to the request
262pub async fn call_api_endpoint<'a, R: DeserializeOwned>(
263    api_key: &str, endpoint: APIEndpoint, unit: Unit, mut params: Vec<Param<'a>>, additional_params: Option<String>) -> Result<R, Error>
264{
265    // Set up a URL for the API endpoint
266    let mut url: String = endpoint.url(&unit);
267    // Add API key, default parameters and groups to the URL
268    url.push_str(&format!("?api_key={}", api_key));
269    params.push(Param::Groups { v: endpoint.default_groups() });
270    if let Some(default_params) = endpoint.default_params() {
271        params.extend(default_params);
272    }
273    // Add parameters to the URL
274    params.iter().for_each(|v| v.add_param_to_url(&mut url) );
275    // Add additional parameters to the URL
276    additional_params.map(|v| url.push_str(&v) );
277    // Process API response
278    process_request::<R>(url).await
279}