Skip to content

Commit

Permalink
Add CoinbaseRateProvider implementation to address issue JavaMoney#3
Browse files Browse the repository at this point in the history
  • Loading branch information
sernamar committed Sep 6, 2024
1 parent 4b5f5c9 commit 2ae0c20
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.javamoney.shelter.bitcoin.provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.javamoney.moneta.convert.ExchangeRateBuilder;
import org.javamoney.moneta.spi.AbstractRateProvider;
import org.javamoney.moneta.spi.DefaultNumberValue;

import javax.money.convert.*;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

public class CoinbaseRateProvider extends AbstractRateProvider {
private static final RateType RATE_TYPE = RateType.DEFERRED;
private static final ProviderContext CONTEXT = ProviderContextBuilder.of("CoinbaseRateProvider", RATE_TYPE)
.set("providerDescription", "Coinbase - Bitcoin exchange rate provider")
.build();
private static final String DEFAULT_BASE_CURRENCY = "BTC";

private final List<String> supportedCurrencies = new ArrayList<>();
private final Map<String, Number> rates = new ConcurrentHashMap<>();

private final Logger log = Logger.getLogger(getClass().getName());

public CoinbaseRateProvider() {
super(CONTEXT);
loadSupportedCurrencies();
}

@Override
public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
var baseCurrency = conversionQuery.getBaseCurrency();
var termCurrency = conversionQuery.getCurrency();
var conversionContext = ConversionContext.of(getContext().getProviderName(), RATE_TYPE);

if (!DEFAULT_BASE_CURRENCY.equals(baseCurrency.getCurrencyCode())) {
throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Base currency not supported: " + baseCurrency);
}

if (!supportedCurrencies.contains(termCurrency.getCurrencyCode())) {
throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Term currency not supported: " + termCurrency);
}

loadRates();

var rate = rates.get(termCurrency.getCurrencyCode());
if (rate == null) {
throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Rate not available for currency: " + termCurrency);
}
return new ExchangeRateBuilder(conversionContext)
.setBase(baseCurrency)
.setTerm(termCurrency)
.setFactor(DefaultNumberValue.of(rate))
.build();
}

private void loadSupportedCurrencies() {
try {
var httpClient = HttpClient.newHttpClient();
var url = "https://api.coinbase.com/v2/currencies";
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var mapper = new ObjectMapper();
var jsonNode = mapper.readTree(response.body());
var dataNode = jsonNode.get("data");
dataNode.forEach(node -> supportedCurrencies.add(node.get("id").asText()));
} catch (IOException | InterruptedException e) {
log.severe("Failed to load supported currencies from Coinbase API: " + e.getMessage());
}
}

private void loadRates() {
try {
var httpClient = HttpClient.newHttpClient();
var url = "https://api.coinbase.com/v2/exchange-rates?currency=" + DEFAULT_BASE_CURRENCY;
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var mapper = new ObjectMapper();
var jsonNode = mapper.readTree(response.body());
var ratesNode = jsonNode.get("data").get("rates");
ratesNode.fields().forEachRemaining(entry -> rates.put(entry.getKey(), entry.getValue().asDouble()));
} catch (IOException | InterruptedException e) {
log.severe("Failed to load exchange rates from Coinbase API: " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.javamoney.shelter.bitcoin.provider;

import org.javamoney.moneta.CurrencyUnitBuilder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.money.UnknownCurrencyException;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;

public class CoinbaseRateProviderTest {
private static CoinbaseRateProvider coinbaseRateProvider;

@BeforeClass
public static void setUpBeforeClass() {
coinbaseRateProvider = new CoinbaseRateProvider();
CurrencyUnitBuilder.of("BTC", "BitcoinProvider")
.setDefaultFractionDigits(8)
.build(true);
}

@AfterClass
public static void tearDownAfterClass() {
coinbaseRateProvider = null;
}

@Test
public void testGetExchangeRate() {
assertNotNull(coinbaseRateProvider.getExchangeRate("BTC", "USD"));
}

@Test
public void testGetExchangeRateWithInvalidCurrency() {
assertThrows(UnknownCurrencyException.class, () -> coinbaseRateProvider.getExchangeRate("BTC", "INVALID"));
}
}

0 comments on commit 2ae0c20

Please sign in to comment.