1use crate::{
4 CurrencyError, Result,
5 api::ExchangeRateService,
6 conversion::{ConversionRequest, ConversionResult, ConversionType, Currency, Money},
7};
8
9#[derive(Debug, Clone)]
11pub struct CurrencyConverter {
12 pub exchange_service: ExchangeRateService,
13}
14
15impl CurrencyConverter {
16 pub fn new(exchange_service: ExchangeRateService) -> Self {
18 CurrencyConverter { exchange_service }
19 }
20
21 pub fn from_env() -> Result<Self> {
23 let service = ExchangeRateService::from_env()?;
24 Ok(CurrencyConverter::new(service))
25 }
26
27 pub async fn convert(&self, request: ConversionRequest) -> Result<ConversionResult> {
29 if request.is_same_currency() {
31 return Ok(self.same_currency_conversion(request));
32 }
33
34 if let Ok(result) = self.try_direct_conversion(&request).await {
36 return Ok(result);
37 }
38
39 self.cross_conversion(&request, "USD").await
41 }
42
43 pub async fn convert_batch(
45 &self,
46 requests: Vec<ConversionRequest>,
47 ) -> Vec<Result<ConversionResult>> {
48 use futures::future::join_all;
49
50 let futures = requests.into_iter().map(|req| self.convert(req));
51 join_all(futures).await
52 }
53
54 fn same_currency_conversion(&self, request: ConversionRequest) -> ConversionResult {
56 let result_money = Money::new(request.from.amount(), request.to.clone()).unwrap(); ConversionResult::new(
59 request,
60 result_money,
61 1.0, ConversionType::SameCurrency,
63 )
64 }
65
66 async fn try_direct_conversion(&self, request: &ConversionRequest) -> Result<ConversionResult> {
68 if let Ok(result) = self
70 .direct_conversion_with_base(request, request.from.currency().code())
71 .await
72 {
73 return Ok(result);
74 }
75
76 self.direct_conversion_with_base(request, request.to.code())
78 .await
79 }
80
81 async fn direct_conversion_with_base(
83 &self,
84 request: &ConversionRequest,
85 base_currency: &str,
86 ) -> Result<ConversionResult> {
87 let rates = self
88 .exchange_service
89 .get_latest_rates(base_currency)
90 .await?;
91
92 let exchange_rate = if base_currency == request.from.currency().code() {
93 rates.get_rate(request.to.code()).ok_or_else(|| {
95 CurrencyError::api(format!(
96 "Exchange rate not available for {}",
97 request.to.code()
98 ))
99 })?
100 } else if base_currency == request.to.code() {
101 let rate_to_base = rates
103 .get_rate(request.from.currency().code())
104 .ok_or_else(|| {
105 CurrencyError::api(format!(
106 "Exchange rate not available for {}",
107 request.from.currency().code()
108 ))
109 })?;
110
111 if rate_to_base == 0.0 {
112 return Err(CurrencyError::conversion(
113 "Cannot divide by zero exchange rate",
114 ));
115 }
116
117 1.0 / rate_to_base
118 } else {
119 return Err(CurrencyError::conversion("Neither currency matches base"));
120 };
121
122 let converted_amount = request.from.amount() * exchange_rate;
123 let result_money = Money::new(converted_amount, request.to.clone())?;
124
125 Ok(ConversionResult::new(
126 request.clone(),
127 result_money,
128 exchange_rate,
129 ConversionType::Direct,
130 ))
131 }
132
133 async fn cross_conversion(
135 &self,
136 request: &ConversionRequest,
137 via_currency: &str,
138 ) -> Result<ConversionResult> {
139 let rates = self.exchange_service.get_latest_rates(via_currency).await?;
141
142 let from_rate = rates
144 .get_rate(request.from.currency().code())
145 .ok_or_else(|| {
146 CurrencyError::api(format!(
147 "Exchange rate not available for {}",
148 request.from.currency().code()
149 ))
150 })?;
151
152 let to_rate = rates.get_rate(request.to.code()).ok_or_else(|| {
153 CurrencyError::api(format!(
154 "Exchange rate not available for {}",
155 request.to.code()
156 ))
157 })?;
158
159 if from_rate == 0.0 {
160 return Err(CurrencyError::conversion(
161 "Cannot divide by zero exchange rate",
162 ));
163 }
164
165 let cross_rate = to_rate / from_rate;
169
170 let converted_amount = request.from.amount() * cross_rate;
171 let result_money = Money::new(converted_amount, request.to.clone())?;
172
173 Ok(ConversionResult::new(
174 request.clone(),
175 result_money,
176 cross_rate,
177 ConversionType::Cross {
178 via_currency: via_currency.to_string(),
179 },
180 ))
181 }
182
183 pub async fn get_available_currencies(&self) -> Result<Vec<String>> {
185 let rates = self.exchange_service.get_latest_rates("USD").await?;
186 let mut currencies = rates.available_currencies();
187 currencies.push("USD".to_string()); currencies.sort();
189 Ok(currencies)
190 }
191
192 pub async fn is_currency_supported(&self, currency: &str) -> Result<bool> {
194 self.exchange_service.is_currency_supported(currency).await
195 }
196
197 pub async fn get_exchange_rate(&self, from: &str, to: &str) -> Result<f64> {
199 let from_currency = Currency::new(from)?;
200 let to_currency = Currency::new(to)?;
201
202 if from_currency.is_same_as(&to_currency) {
203 return Ok(1.0);
204 }
205
206 let request = ConversionRequest::from_components(1.0, from, to)?;
208 let result = self.convert(request).await?;
209
210 Ok(result.exchange_rate)
211 }
212}