diff --git a/price-adapter/examples/coingecko-basic.rs b/price-adapter/examples/coingecko-basic.rs index fda520d6..0281175f 100644 --- a/price-adapter/examples/coingecko-basic.rs +++ b/price-adapter/examples/coingecko-basic.rs @@ -3,7 +3,7 @@ use price_adapter::CoinGecko; #[tokio::main] async fn main() { - let band_static_mapper = BandStaticMapper::new("coingecko".to_string()); + let band_static_mapper = BandStaticMapper::from_source("coingecko").unwrap(); let coingecko = CoinGecko::new(band_static_mapper, None); let queries = vec!["ETH", "BAND"]; let prices = coingecko.get_prices(&queries).await; diff --git a/price-adapter/src/coingecko.rs b/price-adapter/src/coingecko.rs index 1bc68f52..1b617450 100644 --- a/price-adapter/src/coingecko.rs +++ b/price-adapter/src/coingecko.rs @@ -3,56 +3,72 @@ use crate::error::Error; use crate::types::PriceInfo; use price_adapter_raw::CoinGecko as CoinGeckoRaw; -/// An object to query Coingecko public api. +// Generic struct `CoinGecko` parameterized over a `Mapper` type. pub struct CoinGecko { raw: CoinGeckoRaw, mapper: M, } impl CoinGecko { + // Constructor for the `CoinGecko` struct. pub fn new(mapper: M, api_key: Option) -> Self { - let raw: CoinGeckoRaw; - if let Some(key) = api_key { - raw = CoinGeckoRaw::new_with_api_key(key); + // Initialize `CoinGeckoRaw` based on the presence of an API key. + let raw = if let Some(key) = api_key { + CoinGeckoRaw::new_with_api_key(key) } else { - raw = CoinGeckoRaw::new(); - } + CoinGeckoRaw::new() + }; Self { raw, mapper } } - /// get pair prices from the given queries (list of a tuple of (base, quote)). + // Asynchronous function to get prices for symbols. pub async fn get_prices(&self, symbols: &[&str]) -> Vec> { + // Retrieve the symbol-to-id mapping from the provided mapper. let mapping = self.mapper.get_mapping(); - let ids_with_index: Vec<(&str, &str, usize)> = symbols - .iter() - .enumerate() - .filter_map(|(index, &symbol)| { - mapping - .get(symbol) - .and_then(|id| id.as_str().and_then(|id| Some((symbol, id, index)))) - }) - .collect(); - - let ids: Vec<&str> = ids_with_index.iter().map(|(_, id, _)| *id).collect(); - let prices = self.raw.get_prices(ids.as_slice()).await; - - let mut res: Vec> = symbols - .iter() - .map(|_| Err(Error::UnsupportedSymbol)) - .collect(); - - for (&id, price) in ids_with_index.iter().zip(prices) { - res[id.2] = price - .map_err(Error::PriceAdapterRawError) - .map(|p| PriceInfo { - symbol: id.0.to_string(), - price: p.price, - timestamp: p.timestamp, - }); - } + // Match on the result of obtaining the mapping. + if let Ok(mapping) = mapping { + // Collect symbols with associated ids and indices. + let ids_with_index: Vec<(&str, &str, usize)> = symbols + .iter() + .enumerate() + .filter_map(|(index, &symbol)| { + mapping + .get(symbol) + .and_then(|id| id.as_str().map(|id| (symbol, id, index))) + }) + .collect(); + + // Extract only the ids from the collected tuples. + let ids: Vec<&str> = ids_with_index.iter().map(|(_, id, _)| *id).collect(); + + // Retrieve prices for the collected ids asynchronously. + let prices = self.raw.get_prices(ids.as_slice()).await; - res + // Initialize a vector to store the results. + let mut res: Vec> = symbols + .iter() + .map(|_| Err(Error::UnsupportedSymbol)) + .collect(); + + // Iterate over collected ids and prices to populate the results vector. + for (&id, price) in ids_with_index.iter().zip(prices) { + // Assign the result based on the price, mapping errors. + res[id.2] = price + .map_err(Error::PriceAdapterRawError) + .map(|p| PriceInfo { + symbol: id.0.to_string(), + price: p.price, + timestamp: p.timestamp, + }); + } + + // Return the results vector. + res + } else { + // Return errors for symbols if there's an issue with the mapping. + symbols.iter().map(|_| Err(Error::MappingError)).collect() + } } } diff --git a/price-adapter/src/error.rs b/price-adapter/src/error.rs index 09f87f28..8b21740c 100644 --- a/price-adapter/src/error.rs +++ b/price-adapter/src/error.rs @@ -11,6 +11,12 @@ pub enum Error { #[error("file error: {0}")] FileError(#[from] std::io::Error), + #[error("serde-json error: {0}")] + SerdeJsonError(#[from] serde_json::Error), + #[error("unsupported symbol")] UnsupportedSymbol, + + #[error("mapping error")] + MappingError, } diff --git a/price-adapter/src/mapper/band_static_mapper.rs b/price-adapter/src/mapper/band_static_mapper.rs index d11b2ddf..53eb395d 100644 --- a/price-adapter/src/mapper/band_static_mapper.rs +++ b/price-adapter/src/mapper/band_static_mapper.rs @@ -1,29 +1,48 @@ use super::types::Mapper; +use crate::error::Error; use serde_json::Value; use std::collections::HashMap; use std::fs::File; use std::io::Read; +use std::path::Path; +// A struct representing a static mapper using a HashMap of String keys to Values. pub struct BandStaticMapper { mapping: HashMap, } impl BandStaticMapper { - pub fn new(source: String) -> Self { - // Read the JSON file content - let mut file = File::open(format!("resources/{}.json", source)).unwrap(); + // Constructor to create a new BandStaticMapper from a pre-existing mapping. + pub fn new(mapping: HashMap) -> Self { + Self { mapping } + } + + // Constructor to create a BandStaticMapper from a source file. + pub fn from_source(source: &str) -> Result { + let path = format!("resources/{}.json", source.to_lowercase()); + Self::from_path(path) + } + + // Constructor to create a BandStaticMapper from a file path. + pub fn from_path>(path: P) -> Result { + // Attempt to open the file at the specified path. + let mut file = File::open(&path)?; + + // Read the file content into a String. let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); + file.read_to_string(&mut content)?; - // Deserialize the JSON content into a Vec - let mapping = serde_json::from_str(&content).unwrap(); + // Deserialize the JSON content into a HashMap. + let mapping = serde_json::from_str(&content)?; - Self { mapping } + Ok(Self { mapping }) } } +// Implementing the Mapper trait for BandStaticMapper. impl Mapper for BandStaticMapper { - fn get_mapping(&self) -> &HashMap { - &self.mapping + // Retrieve the mapping as a reference, wrapped in a Result. + fn get_mapping(&self) -> Result<&HashMap, Error> { + Ok(&self.mapping) } } diff --git a/price-adapter/src/mapper/types.rs b/price-adapter/src/mapper/types.rs index a348b5e3..813b57b8 100644 --- a/price-adapter/src/mapper/types.rs +++ b/price-adapter/src/mapper/types.rs @@ -1,6 +1,7 @@ +use crate::error::Error; use serde_json::Value; use std::collections::HashMap; pub trait Mapper { - fn get_mapping(&self) -> &HashMap; + fn get_mapping(&self) -> Result<&HashMap, Error>; }