use std::fmt::Display;
use serde::{Serialize, Deserialize, de::DeserializeOwned};
use reqwest::Response;
use crate::error::Error;
use crate::{Unit, APIEndpointTrait, APIEndpoint};
use crate::schemas::data_api::spot::SpotInstrumentStatus;
use crate::schemas::data_api::news::{NewsLang, NewsSourceID, NewsSourceType, NewsStatus};
#[cfg(feature = "debug")]
fn debug() -> Result<bool, Error> {
dotenv::dotenv()?;
let debug: bool = serde_json::from_str(&std::env::var("CCDATA_API_DEBUG")?)?;
Ok(debug)
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum Group {
Activity,
AssetTypeSpecificMetrics,
Basic,
Change,
Classification,
Contact,
Description,
DescriptionSummary,
General,
Id,
InstrumentSummary,
IntegrationFutures,
Internal,
Mapping,
MappingAdvanced,
Message,
Metadata,
Migration,
MktCap,
OHLC,
OHLCMessage,
OHLCSwap,
OHLCTrade,
OrphanTraces,
Price,
ResourceLinks,
SecurityMetrics,
SEO,
Source,
Status,
Supply,
SupplyAddresses,
SupportedPlatforms,
Swap,
ToplistRank,
Trade,
Transactions,
Uncles,
Volume,
Withdrawals,
}
impl Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Activity => write!(f, "ACTIVITY"),
Self::AssetTypeSpecificMetrics => write!(f, "ASSET_TYPE_SPECIFIC_METRICS"),
Self::Basic => write!(f, "BASIC"),
Self::Change => write!(f, "CHANGE"),
Self::Classification => write!(f, "CLASSIFICATION"),
Self::Contact => write!(f, "CONTACT"),
Self::Description => write!(f, "DESCRIPTION"),
Self::DescriptionSummary => write!(f, "DESCRIPTION_SUMMARY"),
Self::General => write!(f, "GENERAL"),
Self::Id => write!(f, "ID"),
Self::InstrumentSummary => write!(f, "INSTRUMENT_SUMMARY"),
Self::IntegrationFutures => write!(f, "INTEGRATION_FUTURES"),
Self::Internal => write!(f, "INTERNAL"),
Self::Mapping => write!(f, "MAPPING"),
Self::MappingAdvanced => write!(f, "MAPPING_ADVANCED"),
Self::Message => write!(f, "MESSAGE"),
Self::Metadata => write!(f, "METADATA"),
Self::Migration => write!(f, "MIGRATION"),
Self::MktCap => write!(f, "MKT_CAP"),
Self::OHLC => write!(f, "OHLC"),
Self::OHLCMessage => write!(f, "OHLC_MESSAGE"),
Self::OHLCSwap => write!(f, "OHLC_SWAP"),
Self::OHLCTrade => write!(f, "OHLC_TRADE"),
Self::OrphanTraces => write!(f, "ORPHAN_TRACES"),
Self::Price => write!(f, "PRICE"),
Self::ResourceLinks => write!(f, "RESOURCE_LINKS"),
Self::SecurityMetrics => write!(f, "SECURITY_METRICS"),
Self::SEO => write!(f, "SEO"),
Self::Source => write!(f, "SOURCE"),
Self::Status => write!(f, "STATUS"),
Self::Supply => write!(f, "SUPPLY"),
Self::SupplyAddresses => write!(f, "SUPPLY_ADDRESSES"),
Self::SupportedPlatforms => write!(f, "SUPPORTED_PLATFORMS"),
Self::Swap => write!(f, "SWAP"),
Self::ToplistRank => write!(f, "TOPLIST_RANK"),
Self::Trade => write!(f, "TRADE"),
Self::Transactions => write!(f, "TRANSACTIONS"),
Self::Uncles => write!(f, "UNCLES"),
Self::Volume => write!(f, "VOLUME"),
Self::Withdrawals => write!(f, "WITHDRAWALS"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AssetLookupPriority {
Symbol,
Id,
URI,
}
impl Display for AssetLookupPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Symbol => write!(f, "SYMBOL"),
Self::Id => write!(f, "ID"),
Self::URI => write!(f, "URI"),
}
}
}
pub enum Param<'a> {
Symbol { v: &'a str, },
Instrument { v: &'a str, },
Instruments { v: &'a Vec<String>, },
ChainAsset { v: &'a str, },
Asset { v: &'a str, },
Assets { v: Vec<String>, },
ToTs { v: Option<i64>, },
ToTimestamp { v: Option<i64>, },
Limit { v: Option<usize>, },
Market { v: String, },
Markets { v: Vec<String>, },
InstrumentStatus { v: SpotInstrumentStatus, },
OCCoreBlockNumber { v: i64, },
OCCoreAddress { v: &'a str, },
OCCoreQuoteAsset { v: &'a str, },
NewsLanguage { v: NewsLang, },
NewsSourceID { v: NewsSourceID, },
NewsCategories { v: Option<Vec<String>>, },
NewsExcludeCategories { v: Option<Vec<String>>, },
NewsSourceType { v: NewsSourceType, },
NewsStatus { v: NewsStatus, },
Groups { v: Option<Vec<Group>> },
ApplyMapping { v: bool, },
AssetLookupPriority { v: AssetLookupPriority, },
QuoteAsset { v: &'a str, },
}
impl<'a> Param<'a> {
fn add_param_to_url(&self, url: &mut String) -> () {
let url_param: String = match self {
Self::Symbol { v } => format!("&fsym={v}"),
Self::Instrument { v } => format!("&instrument={v}"),
Self::Instruments { v } => format!("&instruments={}", v.join(",")),
Self::ChainAsset { v } => format!("&chain_asset={v}"),
Self::Asset { v } => format!("&asset={v}"),
Self::Assets { v } => format!("&assets={}", v.join(",")),
Self::ToTs { v } => v.map_or("".to_owned(), |i| format!("&toTs={i}") ),
Self::ToTimestamp { v } => v.map_or("".to_owned(), |i| format!("&to_ts={i}") ),
Self::Limit { v } => format!("&limit={}", v.unwrap_or(2_000)),
Self::Market { v } => format!("&market={v}"),
Self::Markets { v } => format!("&markets={}", v.join(",")),
Self::InstrumentStatus { v } => format!("&instrument_status={v}"),
Self::OCCoreBlockNumber { v } => format!("&block_number={v}"),
Self::OCCoreAddress { v } => format!("&address={v}"),
Self::OCCoreQuoteAsset { v } => format!(""e_asset={v}"),
Self::NewsLanguage { v } => format!("&lang={v}"),
Self::NewsSourceID { v } => format!("&source_ids={v}"),
Self::NewsCategories { v } => format!("&categories={}", v.as_ref().unwrap_or(&vec![]).join(",")),
Self::NewsExcludeCategories { v } => format!("&exclude_categories={}", v.as_ref().unwrap_or(&vec![]).join(",")),
Self::NewsSourceType { v } => format!("&source_type={v}"),
Self::NewsStatus { v } => format!("&status={v}"),
Self::Groups { v } => v.as_ref().map_or("".to_owned(), |i|
format!("&groups={}", i.iter().map(|v| v.to_string() ).collect::<Vec<String>>().join(","))
),
Self::ApplyMapping { v } => format!("&apply_mapping={v}"),
Self::AssetLookupPriority { v } => format!("&asset_lookup_priority={v}"),
Self::QuoteAsset { v } => format!(""e_asset={v}"),
};
url.push_str(&url_param);
}
}
async fn process_request<T: DeserializeOwned>(url: String) -> Result<T, Error> {
let response: Response = reqwest::get(url).await?;
let response_body: String = response.text().await?;
#[cfg(feature = "debug")]
if debug()? { println!("{:?}", response_body) }
let response_body: String = response_body.replace("{}", "null");
let data: T = serde_json::from_str(&response_body)?;
Ok(data)
}
pub async fn call_api_endpoint<'a, R: DeserializeOwned>(
api_key: &str, endpoint: APIEndpoint, unit: Unit, mut params: Vec<Param<'a>>, additional_params: Option<String>) -> Result<R, Error>
{
let mut url: String = endpoint.url(&unit);
url.push_str(&format!("?api_key={}", api_key));
params.push(Param::Groups { v: endpoint.default_groups() });
if let Some(default_params) = endpoint.default_params() {
params.extend(default_params);
}
params.iter().for_each(|v| v.add_param_to_url(&mut url) );
additional_params.map(|v| url.push_str(&v) );
process_request::<R>(url).await
}