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")]
11fn 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)]
21pub 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)]
119pub 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
142pub enum Param<'a> {
144 Symbol { v: &'a str, },
147 Instrument { v: &'a str, },
149 Instruments { v: &'a Vec<String>, },
151 ChainAsset { v: &'a str, },
153 Asset { v: &'a str, },
155 Assets { v: Vec<String>, },
157 ToTs { v: Option<i64>, },
160 ToTimestamp { v: Option<i64>, },
162 Limit { v: Option<usize>, },
164 Market { v: String, },
167 Markets { v: Vec<String>, },
169 InstrumentStatus { v: SpotInstrumentStatus, },
171 OCCoreBlockNumber { v: i64, },
173 OCCoreAddress { v: &'a str, },
175 OCCoreQuoteAsset { v: &'a str, },
177 NewsLanguage { v: NewsLang, },
179 NewsSourceID { v: NewsSourceID, },
181 NewsCategories { v: Option<Vec<String>>, },
183 NewsExcludeCategories { v: Option<Vec<String>>, },
185 NewsSourceType { v: NewsSourceType, },
187 NewsStatus { v: NewsStatus, },
189 Groups { v: Option<Vec<Group>> },
191 ApplyMapping { v: bool, },
193 AssetLookupPriority { v: AssetLookupPriority, },
195 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 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 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 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!(""e_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!(""e_asset={v}"),
233 };
234 url.push_str(&url_param);
235 }
236}
237
238
239async 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 #[cfg(feature = "debug")]
245 if debug()? { println!("{:?}", response_body) }
246 let response_body: String = response_body.replace("{}", "null");
248 let data: T = serde_json::from_str(&response_body)?;
250 Ok(data)
251}
252
253
254pub 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 let mut url: String = endpoint.url(&unit);
267 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 params.iter().for_each(|v| v.add_param_to_url(&mut url) );
275 additional_params.map(|v| url.push_str(&v) );
277 process_request::<R>(url).await
279}