use std::fmt;
use fynd_core::types::ComponentId;
use serde::{Deserialize, Serialize};
use tycho_simulation::tycho_common::models::Address;
use utoipa::{IntoParams, ToSchema};
#[derive(Debug, Default, Deserialize, IntoParams)]
pub struct PricesQuery {
#[param(example = "depths,spot_prices")]
pub include: Option<String>,
#[param(example = 1000)]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IncludeField {
Depths,
SpotPrices,
}
impl IncludeField {
pub fn parse_include(raw: &str) -> Result<Vec<Self>, String> {
let mut fields = Vec::new();
for part in raw.split(',') {
let trimmed = part.trim();
if trimmed.is_empty() {
continue;
}
match trimmed {
"depths" => fields.push(Self::Depths),
"spot_prices" => fields.push(Self::SpotPrices),
other => {
return Err(format!(
"unknown include field '{}'. Valid values: depths, spot_prices",
other,
));
}
}
}
Ok(fields)
}
}
impl fmt::Display for IncludeField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Depths => write!(f, "depths"),
Self::SpotPrices => write!(f, "spot_prices"),
}
}
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ComputationBlocks {
pub token_prices: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub spot_prices: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pool_depths: Option<u64>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct PricesResponse {
pub prices: Vec<TokenPriceEntry>,
#[schema(value_type = String, example = "0x0000000000000000000000000000000000000000")]
pub gas_token: Address,
pub blocks: ComputationBlocks,
#[serde(skip_serializing_if = "Option::is_none")]
pub spot_prices: Option<Vec<SpotPriceEntry>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pool_depths: Option<Vec<PoolDepthEntry>>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct TokenPriceEntry {
#[schema(value_type = String, example = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")]
pub token: Address,
pub price: f64,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct SpotPriceEntry {
pub component_id: ComponentId,
#[schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")]
pub token_in: Address,
#[schema(value_type = String, example = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")]
pub token_out: Address,
pub price: f64,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct PoolDepthEntry {
pub component_id: ComponentId,
#[schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")]
pub token_in: Address,
#[schema(value_type = String, example = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")]
pub token_out: Address,
pub depth: String,
}
pub fn price_to_f64(
numerator: &num_bigint::BigUint,
denominator: &num_bigint::BigUint,
) -> Option<f64> {
use num_traits::{ToPrimitive, Zero};
if denominator.is_zero() {
return None;
}
let n = numerator.to_f64()?;
let d = denominator.to_f64()?;
Some(n / d)
}
#[cfg(test)]
mod tests {
use num_bigint::BigUint;
use super::*;
#[test]
fn parse_include_empty() {
assert_eq!(IncludeField::parse_include("").unwrap(), vec![]);
}
#[test]
fn parse_include_depths() {
let fields = IncludeField::parse_include("depths").unwrap();
assert_eq!(fields, vec![IncludeField::Depths]);
}
#[test]
fn parse_include_spot_prices() {
let fields = IncludeField::parse_include("spot_prices").unwrap();
assert_eq!(fields, vec![IncludeField::SpotPrices]);
}
#[test]
fn parse_include_both() {
let fields = IncludeField::parse_include("depths,spot_prices").unwrap();
assert_eq!(fields, vec![IncludeField::Depths, IncludeField::SpotPrices]);
}
#[test]
fn parse_include_with_whitespace() {
let fields = IncludeField::parse_include(" depths , spot_prices ").unwrap();
assert_eq!(fields, vec![IncludeField::Depths, IncludeField::SpotPrices]);
}
#[test]
fn parse_include_unknown_rejects() {
let err = IncludeField::parse_include("depths,foobar").unwrap_err();
assert!(err.contains("foobar"));
}
#[test]
fn price_to_f64_normal() {
let n = BigUint::from(3u64);
let d = BigUint::from(10u64);
let result = price_to_f64(&n, &d).unwrap();
assert!((result - 0.3).abs() < 1e-10);
}
#[test]
fn price_to_f64_zero_denominator() {
let n = BigUint::from(1u64);
let d = BigUint::from(0u64);
assert!(price_to_f64(&n, &d).is_none());
}
#[test]
fn price_to_f64_large_values() {
let n = BigUint::from(10u64).pow(18);
let d = BigUint::from(10u64).pow(18);
let result = price_to_f64(&n, &d).unwrap();
assert!((result - 1.0).abs() < 1e-10);
}
#[test]
fn price_to_f64_small_fraction() {
let n = BigUint::from(1u64);
let d = BigUint::from(10u64).pow(6);
let result = price_to_f64(&n, &d).unwrap();
assert!((result - 1e-6).abs() < 1e-15);
}
}