Skip to content

Commit

Permalink
feat: api layer exchange rates data
Browse files Browse the repository at this point in the history
  • Loading branch information
florianv committed Dec 11, 2022
1 parent 6b8aff1 commit be3e4b3
Show file tree
Hide file tree
Showing 16 changed files with 459 additions and 7 deletions.
7 changes: 5 additions & 2 deletions src/Service/ApiLayer/CurrencyData.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Exchanger\Contract\ExchangeRateQuery;
use Exchanger\Contract\HistoricalExchangeRateQuery;
use Exchanger\Exception\Exception;
use Exchanger\Exception\NonBreakingInvalidArgumentException;
use Exchanger\Exception\UnsupportedCurrencyPairException;
use Exchanger\ExchangeRate;
use Exchanger\Service\HttpService;
Expand All @@ -25,7 +26,9 @@
use Exchanger\Contract\ExchangeRate as ExchangeRateContract;

/**
* ApiLayer Currency Data Service (https://apilayer.com/marketplace/currency_data-api).
* ApiLayer Currency Data Service.
*
* @see https://apilayer.com/marketplace/currency_data-api
*
* @author Florian Voutzinos <florian@voutzinos.com>
*/
Expand All @@ -45,7 +48,7 @@ final class CurrencyData extends HttpService
public function processOptions(array &$options): void
{
if (!isset($options[self::API_KEY_OPTION])) {
throw new \InvalidArgumentException('The "api_key" option must be provided to use CurrencyData (https://apilayer.com/marketplace/currency_data-api).');
throw new NonBreakingInvalidArgumentException('The "api_key" option must be provided to use CurrencyData (https://apilayer.com/marketplace/currency_data-api).');
}
}

Expand Down
146 changes: 146 additions & 0 deletions src/Service/ApiLayer/ExchangeRatesData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

declare(strict_types=1);

/*
* This file is part of Exchanger.
*
* (c) Florian Voutzinos <florian@voutzinos.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Exchanger\Service\ApiLayer;

use Exchanger\Contract\CurrencyPair;
use Exchanger\Contract\ExchangeRate as ExchangeRateContract;
use Exchanger\Contract\ExchangeRateQuery;
use Exchanger\Contract\HistoricalExchangeRateQuery;
use Exchanger\Exception\Exception;
use Exchanger\Exception\NonBreakingInvalidArgumentException;
use Exchanger\Exception\UnsupportedCurrencyPairException;
use Exchanger\ExchangeRate;
use Exchanger\Service\HttpService;
use Exchanger\Service\SupportsHistoricalQueries;
use Exchanger\StringUtil;

/**
* ApiLayer Exchange Rates Data API.
*
* @see https://apilayer.com/marketplace/exchangerates_data-api
*
* @author Florian Voutzinos <florian@voutzinos.com>
*/
final class ExchangeRatesData extends HttpService
{
use SupportsHistoricalQueries;

const API_KEY_OPTION = 'api_key';

const LATEST_URL = 'https://api.apilayer.com/exchangerates_data/latest?base=%s&apikey=%s&symbols=%s';

const HISTORICAL_URL = 'https://api.apilayer.com/exchangerates_data/%s?base=%s&apikey=%s&symbols=%s';

/**
* {@inheritdoc}
*/
public function processOptions(array &$options): void
{
if (!isset($options[self::API_KEY_OPTION])) {
throw new NonBreakingInvalidArgumentException('The "api_key" option must be provided to use Exchange Rates Data (https://apilayer.com/marketplace/exchangerates_data-api).');
}
}

/**
* {@inheritdoc}
*/
protected function getLatestExchangeRate(ExchangeRateQuery $exchangeQuery): ExchangeRateContract
{
$currencyPair = $exchangeQuery->getCurrencyPair();

$url = sprintf(
self::LATEST_URL,
$currencyPair->getBaseCurrency(),
$this->options[self::API_KEY_OPTION],
$currencyPair->getQuoteCurrency()
);

return $this->doCreateRate($url, $currencyPair);
}

/**
* {@inheritdoc}
*/
protected function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchangeQuery): ExchangeRateContract
{
$currencyPair = $exchangeQuery->getCurrencyPair();

$url = sprintf(
self::HISTORICAL_URL,
$exchangeQuery->getDate()->format('Y-m-d'),
$exchangeQuery->getCurrencyPair()->getBaseCurrency(),
$this->options[self::API_KEY_OPTION],
$currencyPair->getQuoteCurrency()
);

return $this->doCreateRate($url, $currencyPair);
}

/**
* {@inheritdoc}
*/
public function supportQuery(ExchangeRateQuery $exchangeQuery): bool
{
return true;
}

/**
* Creates a rate.
*
* @param string $url
*
* @throws Exception
*/
private function doCreateRate($url, CurrencyPair $currencyPair): ExchangeRate
{
$content = $this->request($url);
$data = StringUtil::jsonToArray($content);

if (isset($data['error'])) {
if (isset($data['error']['code'])) {
if (\in_array($data['error']['code'], [
'invalid_currency_codes',
'invalid_base_currency',
'no_rates_available',
], true)) {
throw new UnsupportedCurrencyPairException($currencyPair, $this);
}
if (isset($data['error']['message'])) {
throw new Exception($data['error']['message']);
} else {
throw new Exception('Service return error code: '.$data['error']['code']);
}
} else {
throw new Exception('Service return unhandled error');
}
}

if (isset($data['rates'][$currencyPair->getQuoteCurrency()])) {
$date = new \DateTime($data['date']);
$rate = $data['rates'][$currencyPair->getQuoteCurrency()];

return $this->createRate($currencyPair, (float) $rate, $date);
}

throw new UnsupportedCurrencyPairException($currencyPair, $this);
}

/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'apilayer_exchange_rates_data';
}
}
7 changes: 5 additions & 2 deletions src/Service/ApiLayer/Fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Exchanger\Contract\ExchangeRateQuery;
use Exchanger\Contract\HistoricalExchangeRateQuery;
use Exchanger\Exception\Exception;
use Exchanger\Exception\NonBreakingInvalidArgumentException;
use Exchanger\Exception\UnsupportedCurrencyPairException;
use Exchanger\ExchangeRate;
use Exchanger\Service\HttpService;
Expand All @@ -25,7 +26,9 @@
use Exchanger\Contract\ExchangeRate as ExchangeRateContract;

/**
* ApiLayer Fixer Service (https://apilayer.com/marketplace/fixer-api).
* ApiLayer Fixer Service.
*
* @see https://apilayer.com/marketplace/fixer-api
*
* @author Florian Voutzinos <florian@voutzinos.com>
*/
Expand All @@ -45,7 +48,7 @@ final class Fixer extends HttpService
public function processOptions(array &$options): void
{
if (!isset($options[self::API_KEY_OPTION])) {
throw new \InvalidArgumentException('The "api_key" option must be provided to use Fixer (https://apilayer.com/marketplace/fixer-api).');
throw new NonBreakingInvalidArgumentException('The "api_key" option must be provided to use Fixer (https://apilayer.com/marketplace/fixer-api).');
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Service/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public static function getServices(): array
'abstract_api' => AbstractApi::class,
'exchangeratehost' => ExchangerateHost::class,
'apilayer_fixer' => ApiLayer\Fixer::class,
'apilayer_currency_data' => ApiLayer\CurrencyData::class
'apilayer_currency_data' => ApiLayer\CurrencyData::class,
'apilayer_exchange_rates_data' => ApiLayer\ExchangeRatesData::class
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"base_currency_access_restricted",
"message":"An unexpected error ocurred. [Technical Support: support@apilayer.com]"
}
}
10 changes: 10 additions & 0 deletions tests/Fixtures/Service/ApiLayer/ExchangeRatesData/historical.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"success":true,
"timestamp":1618531199,
"historical":true,
"base":"EUR",
"date":"2021-04-15",
"rates":{
"USD":1.196953
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"https_access_restricted",
"message":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"invalid_access_key",
"message":"You have not supplied a valid API Access Key. [Technical Support: support@apilayer.com]"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"invalid_base_currency",
"message":"An unexpected error ocurred. [Technical Support: support@apilayer.com]"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"invalid_currency_codes",
"message":"You have provided one or more invalid Currency Codes. [Required format: currencies=EUR,USD,GBP,...]"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"invalid_date",
"message":"You have entered an invalid date. [Required format: date=YYYY-MM-DD]"
}
}
9 changes: 9 additions & 0 deletions tests/Fixtures/Service/ApiLayer/ExchangeRatesData/latest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"success":true,
"timestamp":1619171643,
"base":"EUR",
"date":"2021-04-23",
"rates":{
"USD":1.20555
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error":{
"code":"no_rates_available",
"message":"Your query did not return any results. Please try again."
}
}
3 changes: 2 additions & 1 deletion tests/Tests/Service/ApiLayer/CurrencyDataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Exchanger\Tests\Service;

use Exchanger\Exception\Exception;
use Exchanger\Exception\NonBreakingInvalidArgumentException;
use Exchanger\ExchangeRateQuery;
use Exchanger\HistoricalExchangeRateQuery;
use Exchanger\CurrencyPair;
Expand All @@ -29,7 +30,7 @@ class CurrencyDataTest extends ServiceTestCase
*/
public function it_throws_an_exception_if_api_key_option_missing()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectException(NonBreakingInvalidArgumentException::class);
$this->expectExceptionMessage('The "api_key" option must be provided to use CurrencyData (https://apilayer.com/marketplace/currency_data-api).');
new CurrencyData($this->createMock('Http\Client\HttpClient'));
}
Expand Down
Loading

0 comments on commit be3e4b3

Please sign in to comment.