Skip to content

Commit

Permalink
fix based on comment
Browse files Browse the repository at this point in the history
  • Loading branch information
RogerKSI committed Nov 21, 2023
1 parent e635a79 commit fefb97c
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 46 deletions.
2 changes: 1 addition & 1 deletion price-adapter/examples/coingecko-basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
86 changes: 51 additions & 35 deletions price-adapter/src/coingecko.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<M: Mapper> {
raw: CoinGeckoRaw,
mapper: M,
}

impl<M: Mapper> CoinGecko<M> {
// Constructor for the `CoinGecko` struct.
pub fn new(mapper: M, api_key: Option<String>) -> 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<Result<PriceInfo, Error>> {
// 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<Result<PriceInfo, Error>> = 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<Result<PriceInfo, Error>> = 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()
}
}
}
6 changes: 6 additions & 0 deletions price-adapter/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
37 changes: 28 additions & 9 deletions price-adapter/src/mapper/band_static_mapper.rs
Original file line number Diff line number Diff line change
@@ -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<String, Value>,
}

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<String, Value>) -> Self {
Self { mapping }
}

// Constructor to create a BandStaticMapper from a source file.
pub fn from_source(source: &str) -> Result<Self, Error> {
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<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
// 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<Mapping>
let mapping = serde_json::from_str(&content).unwrap();
// Deserialize the JSON content into a HashMap<String, Value>.
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<String, Value> {
&self.mapping
// Retrieve the mapping as a reference, wrapped in a Result.
fn get_mapping(&self) -> Result<&HashMap<String, Value>, Error> {
Ok(&self.mapping)
}
}
3 changes: 2 additions & 1 deletion price-adapter/src/mapper/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::error::Error;
use serde_json::Value;
use std::collections::HashMap;

pub trait Mapper {
fn get_mapping(&self) -> &HashMap<String, Value>;
fn get_mapping(&self) -> Result<&HashMap<String, Value>, Error>;
}

0 comments on commit fefb97c

Please sign in to comment.