From b8194f2b1fb8b58332de4e06ce34684962d13cdd Mon Sep 17 00:00:00 2001 From: Kayo Tusthler Date: Tue, 24 Mar 2026 23:25:08 -0300 Subject: [PATCH 1/6] feat: basic template --- src/index.php | 88 +++++++++++++++++++++++++++++++++++++++++++++++ tests/ApiCest.php | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/index.php b/src/index.php index 92841bc87..d3a315801 100644 --- a/src/index.php +++ b/src/index.php @@ -16,3 +16,91 @@ require __DIR__ . '/../vendor/autoload.php'; +header("Content-Type: application/json"); + +$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); +$parts = explode('/', trim($uri, '/')); + +CONST SIGN_SYMBOL = [ + 'BRL' => 'R$', + 'EUR' => '€', + 'USD' => '$' +]; + +CONST PERMITTED_CURRENCIES = [ + 'BRL', + 'EUR', + 'USD' +]; + +CONST ROUTE = 'exchange'; + +CONST ERROR_MESSAGES = [ + 'INVALID_CURRENCY' => "Moeda invalida. Use: USD, BRL, EUR", + 'INVALID_RATE' => "Taxa invalida. Use: > 0", + 'INVALID_AMOUNT' => "Valor invalido. Use: > 0", + 'INVALID_ROUTE' => "Rota invalida. Use: /exchange/{amount}/{from}/{to}/{rate}" +]; + +if (count($parts) !== 5) : + http_response_code(400); + echo json_encode(["erro" => ERROR_MESSAGES['INVALID_ROUTE']]); + exit; +endif; + +if ($parts[0] !== ROUTE) : + http_response_code(404); + echo json_encode(["erro" => ERROR_MESSAGES['INVALID_ROUTE']]); + exit; +endif; + +CONST ROUTE_PARAMS = [ + 'AMOUNT' => 1, + 'FROM' => 2, + 'TO' => 3, + 'RATE' => 4 +]; + +if ( + !is_numeric($parts[ROUTE_PARAMS['AMOUNT']]) + || (float) $parts[ROUTE_PARAMS['AMOUNT']] <= 0 +) : + http_response_code(400); + echo json_encode(["erro" => ERROR_MESSAGES['INVALID_AMOUNT']]); + exit; +endif; + +if ( + !is_numeric($parts[ROUTE_PARAMS['RATE']]) + || (float) $parts[ROUTE_PARAMS['RATE']] <= 0 +) : + http_response_code(400); + echo json_encode(["erro" => ERROR_MESSAGES['INVALID_RATE']]); + exit; +endif; + +if ( + !in_array($parts[ROUTE_PARAMS['FROM']], PERMITTED_CURRENCIES) + || !in_array($parts[ROUTE_PARAMS['TO']], PERMITTED_CURRENCIES) +) : + http_response_code(400); + echo json_encode(["erro" => ERROR_MESSAGES['INVALID_CURRENCY']]); + exit; +endif; + +if ($parts[ROUTE_PARAMS['RATE']] <= 0) : + http_response_code(400); + echo json_encode(["erro" => ERROR_MESSAGES['INVALID_RATE']]); + exit; +endif; + +$amount = (float) trim($parts[ROUTE_PARAMS['AMOUNT']]); +$to = strtoupper(trim($parts[ROUTE_PARAMS['TO']])); +$rate = (float) trim($parts[ROUTE_PARAMS['RATE']]); + +$result = $amount * $rate; + +echo json_encode([ + "valorConvertido" => round($result, 2), + "simboloMoeda" => SIGN_SYMBOL[$to] +]); diff --git a/tests/ApiCest.php b/tests/ApiCest.php index e92352118..9be3a21c6 100644 --- a/tests/ApiCest.php +++ b/tests/ApiCest.php @@ -68,7 +68,7 @@ public function tryApiNegativeRate(ApiTester $I) { $I->sendGET('/10/EUR/USD/-0.5'); $I->seeResponseCodeIs(400); - $I->seeResponseIsJson(); + $I->seeResponseContainsJson(); } public function tryApiBrlToUsd(ApiTester $I) From 023c81b3f3cc39fa8cc8f6478188d04836d1b7cb Mon Sep 17 00:00:00 2001 From: Kayo Tusthler Date: Tue, 24 Mar 2026 23:38:24 -0300 Subject: [PATCH 2/6] feat: poo transformation --- src/Exchange/ExchangeConfig.php | 39 ++++++++++++++ src/Exchange/ExchangeHandler.php | 79 ++++++++++++++++++++++++++++ src/index.php | 90 ++------------------------------ 3 files changed, 122 insertions(+), 86 deletions(-) create mode 100644 src/Exchange/ExchangeConfig.php create mode 100644 src/Exchange/ExchangeHandler.php diff --git a/src/Exchange/ExchangeConfig.php b/src/Exchange/ExchangeConfig.php new file mode 100644 index 000000000..86977dbec --- /dev/null +++ b/src/Exchange/ExchangeConfig.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://github.com/apiki/back-end-challenge + */ +declare(strict_types=1); + +namespace App\Exchange; + +class ExchangeConfig +{ + public const ROUTE = 'exchange'; + + public const SIGN_SYMBOL = [ + 'BRL' => 'R$', + 'EUR' => '€', + 'USD' => '$' + ]; + + public const PERMITTED_CURRENCIES = [ + 'BRL', + 'EUR', + 'USD' + ]; + + public const ERROR_MESSAGES = [ + 'INVALID_AMOUNT' => 'Valor invalido. Use: > 0', + 'INVALID_CURRENCY' => 'Moeda invalida. Use: USD, BRL, EUR', + 'INVALID_RATE' => 'Taxa invalida. Use: > 0', + 'INVALID_ROUTE' => 'Rota invalida. Use: /exchange/{amount}/{from}/{to}/{rate}' + ]; +} diff --git a/src/Exchange/ExchangeHandler.php b/src/Exchange/ExchangeHandler.php new file mode 100644 index 000000000..b46d582c9 --- /dev/null +++ b/src/Exchange/ExchangeHandler.php @@ -0,0 +1,79 @@ +parsePath($requestUri); + + if (count($parts) !== 5) : + $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_ROUTE']); + endif; + + if ($parts[0] !== ExchangeConfig::ROUTE) : + $this->respondError(404, ExchangeConfig::ERROR_MESSAGES['INVALID_ROUTE']); + endif; + + if ( + !is_numeric($parts[self::PARAM_AMOUNT]) + || (float) $parts[self::PARAM_AMOUNT] <= 0 + ) : + $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_AMOUNT']); + endif; + + if ( + !is_numeric($parts[self::PARAM_RATE]) + || (float) $parts[self::PARAM_RATE] <= 0 + ) : + $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_RATE']); + endif; + + $from = trim($parts[self::PARAM_FROM]); + $to = trim($parts[self::PARAM_TO]); + + if ( + !in_array($from, ExchangeConfig::PERMITTED_CURRENCIES, true) + || !in_array($to, ExchangeConfig::PERMITTED_CURRENCIES, true) + ) : + $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_CURRENCY']); + endif; + + $amount = (float) trim($parts[self::PARAM_AMOUNT]); + $rate = (float) trim($parts[self::PARAM_RATE]); + $result = $amount * $rate; + + $this->respondSuccess([ + 'valorConvertido' => round($result, 2), + 'simboloMoeda' => ExchangeConfig::SIGN_SYMBOL[$to], + ]); + } + + private function parsePath(string $requestUri): array + { + $path = parse_url($requestUri, PHP_URL_PATH); + + if (!is_string($path)) : + return []; + endif; + + return explode('/', trim($path, '/')); + } + + private function respondError(int $statusCode, string $message): void + { + http_response_code($statusCode); + echo json_encode(['erro' => $message]); + exit; + } + + private function respondSuccess(array $payload): void + { + echo json_encode($payload); + } +} diff --git a/src/index.php b/src/index.php index d3a315801..743481031 100644 --- a/src/index.php +++ b/src/index.php @@ -16,91 +16,9 @@ require __DIR__ . '/../vendor/autoload.php'; -header("Content-Type: application/json"); +use App\Exchange\ExchangeHandler; -$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); -$parts = explode('/', trim($uri, '/')); +header('Content-Type: application/json'); -CONST SIGN_SYMBOL = [ - 'BRL' => 'R$', - 'EUR' => '€', - 'USD' => '$' -]; - -CONST PERMITTED_CURRENCIES = [ - 'BRL', - 'EUR', - 'USD' -]; - -CONST ROUTE = 'exchange'; - -CONST ERROR_MESSAGES = [ - 'INVALID_CURRENCY' => "Moeda invalida. Use: USD, BRL, EUR", - 'INVALID_RATE' => "Taxa invalida. Use: > 0", - 'INVALID_AMOUNT' => "Valor invalido. Use: > 0", - 'INVALID_ROUTE' => "Rota invalida. Use: /exchange/{amount}/{from}/{to}/{rate}" -]; - -if (count($parts) !== 5) : - http_response_code(400); - echo json_encode(["erro" => ERROR_MESSAGES['INVALID_ROUTE']]); - exit; -endif; - -if ($parts[0] !== ROUTE) : - http_response_code(404); - echo json_encode(["erro" => ERROR_MESSAGES['INVALID_ROUTE']]); - exit; -endif; - -CONST ROUTE_PARAMS = [ - 'AMOUNT' => 1, - 'FROM' => 2, - 'TO' => 3, - 'RATE' => 4 -]; - -if ( - !is_numeric($parts[ROUTE_PARAMS['AMOUNT']]) - || (float) $parts[ROUTE_PARAMS['AMOUNT']] <= 0 -) : - http_response_code(400); - echo json_encode(["erro" => ERROR_MESSAGES['INVALID_AMOUNT']]); - exit; -endif; - -if ( - !is_numeric($parts[ROUTE_PARAMS['RATE']]) - || (float) $parts[ROUTE_PARAMS['RATE']] <= 0 -) : - http_response_code(400); - echo json_encode(["erro" => ERROR_MESSAGES['INVALID_RATE']]); - exit; -endif; - -if ( - !in_array($parts[ROUTE_PARAMS['FROM']], PERMITTED_CURRENCIES) - || !in_array($parts[ROUTE_PARAMS['TO']], PERMITTED_CURRENCIES) -) : - http_response_code(400); - echo json_encode(["erro" => ERROR_MESSAGES['INVALID_CURRENCY']]); - exit; -endif; - -if ($parts[ROUTE_PARAMS['RATE']] <= 0) : - http_response_code(400); - echo json_encode(["erro" => ERROR_MESSAGES['INVALID_RATE']]); - exit; -endif; - -$amount = (float) trim($parts[ROUTE_PARAMS['AMOUNT']]); -$to = strtoupper(trim($parts[ROUTE_PARAMS['TO']])); -$rate = (float) trim($parts[ROUTE_PARAMS['RATE']]); - -$result = $amount * $rate; - -echo json_encode([ - "valorConvertido" => round($result, 2), - "simboloMoeda" => SIGN_SYMBOL[$to] -]); +$handler = new ExchangeHandler(); +$handler->handle($_SERVER['REQUEST_URI']); From bf726bd425b06dab5374dc4b5c076ca032bc5749 Mon Sep 17 00:00:00 2001 From: Kayo Tusthler Date: Wed, 25 Mar 2026 00:12:25 -0300 Subject: [PATCH 3/6] chore: lint fix on config file --- src/Exchange/ExchangeConfig.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Exchange/ExchangeConfig.php b/src/Exchange/ExchangeConfig.php index 86977dbec..4808c3ca4 100644 --- a/src/Exchange/ExchangeConfig.php +++ b/src/Exchange/ExchangeConfig.php @@ -4,16 +4,32 @@ * * PHP version 7.4 * + * Este arquivo contém as constantes e mensagens de erro. + * * @category Challenge * @package Back-end - * @author Seu Nome + * @author Kayo Almondes Tusthler * @license http://opensource.org/licenses/MIT MIT * @link https://github.com/apiki/back-end-challenge */ + declare(strict_types=1); namespace App\Exchange; +/** + * Back-end Challenge. + * + * PHP version 7.4 + * + * Esta classe é responsável por configurar as constantes e mensagens de erro. + * + * @category Challenge + * @package Back-end + * @author Kayo Almondes Tusthler + * @license http://opensource.org/licenses/MIT MIT + * @link https://github.com/apiki/back-end-challenge + */ class ExchangeConfig { public const ROUTE = 'exchange'; @@ -31,9 +47,9 @@ class ExchangeConfig ]; public const ERROR_MESSAGES = [ - 'INVALID_AMOUNT' => 'Valor invalido. Use: > 0', - 'INVALID_CURRENCY' => 'Moeda invalida. Use: USD, BRL, EUR', + 'INVALID_AMOUNT' => 'Valor invalido > 0', + 'INVALID_CURRENCY' => 'Moedas validas: USD, BRL, EUR', 'INVALID_RATE' => 'Taxa invalida. Use: > 0', - 'INVALID_ROUTE' => 'Rota invalida. Use: /exchange/{amount}/{from}/{to}/{rate}' + 'INVALID_ROUTE' => 'Rota válida: /exchange/{amount}/{from}/{to}/{rate}' ]; } From be41c618b886cb64b0afd69bb2dd9f8ae78f9681 Mon Sep 17 00:00:00 2001 From: Kayo Tusthler Date: Wed, 25 Mar 2026 00:31:21 -0300 Subject: [PATCH 4/6] chore: lint fix handler file --- src/Exchange/ExchangeHandler.php | 108 ++++++++++++++++++++++++++----- src/index.php | 2 +- 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/Exchange/ExchangeHandler.php b/src/Exchange/ExchangeHandler.php index b46d582c9..f16c3aa25 100644 --- a/src/Exchange/ExchangeHandler.php +++ b/src/Exchange/ExchangeHandler.php @@ -1,6 +1,35 @@ + * @license http://opensource.org/licenses/MIT MIT + * @link https://github.com/apiki/back-end-challenge + */ + namespace App\Exchange; +/** + * Back-end Challenge. + * + * PHP version 7.4 + * + * Esta classe lida com a requisição + * e retorna o resultado da conversão de moedas. + * + * @category Challenge + * @package Back-end + * @author Kayo Almondes Tusthler + * @license http://opensource.org/licenses/MIT MIT + * @link https://github.com/apiki/back-end-challenge + */ class ExchangeHandler { private const PARAM_AMOUNT = 1; @@ -8,53 +37,83 @@ class ExchangeHandler private const PARAM_TO = 3; private const PARAM_RATE = 4; + /** + * Lida com a requisição + * e retorna o resultado da conversão de moedas. + * + * @param string $requestUri URL da requisição + * + * @return void + */ public function handle(string $requestUri): void { - $parts = $this->parsePath($requestUri); + $parts = $this->_parsePath($requestUri); if (count($parts) !== 5) : - $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_ROUTE']); + $this->_respondError( + 400, + ExchangeConfig::ERROR_MESSAGES['INVALID_ROUTE'] + ); endif; if ($parts[0] !== ExchangeConfig::ROUTE) : - $this->respondError(404, ExchangeConfig::ERROR_MESSAGES['INVALID_ROUTE']); + $this->_respondError( + 404, + ExchangeConfig::ERROR_MESSAGES['INVALID_ROUTE'] + ); endif; - if ( - !is_numeric($parts[self::PARAM_AMOUNT]) + if (!is_numeric($parts[self::PARAM_AMOUNT]) || (float) $parts[self::PARAM_AMOUNT] <= 0 ) : - $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_AMOUNT']); + $this->_respondError( + 400, + ExchangeConfig::ERROR_MESSAGES['INVALID_AMOUNT'] + ); endif; - if ( - !is_numeric($parts[self::PARAM_RATE]) + if (!is_numeric($parts[self::PARAM_RATE]) || (float) $parts[self::PARAM_RATE] <= 0 ) : - $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_RATE']); + $this->_respondError( + 400, + ExchangeConfig::ERROR_MESSAGES['INVALID_RATE'] + ); endif; $from = trim($parts[self::PARAM_FROM]); $to = trim($parts[self::PARAM_TO]); - if ( - !in_array($from, ExchangeConfig::PERMITTED_CURRENCIES, true) + if (!in_array($from, ExchangeConfig::PERMITTED_CURRENCIES, true) || !in_array($to, ExchangeConfig::PERMITTED_CURRENCIES, true) ) : - $this->respondError(400, ExchangeConfig::ERROR_MESSAGES['INVALID_CURRENCY']); + $this->_respondError( + 400, + ExchangeConfig::ERROR_MESSAGES['INVALID_CURRENCY'] + ); endif; $amount = (float) trim($parts[self::PARAM_AMOUNT]); $rate = (float) trim($parts[self::PARAM_RATE]); $result = $amount * $rate; - $this->respondSuccess([ + $this->_respondSuccess( + [ 'valorConvertido' => round($result, 2), 'simboloMoeda' => ExchangeConfig::SIGN_SYMBOL[$to], - ]); + ] + ); } - private function parsePath(string $requestUri): array + /** + * Analisa a URL da requisição + * e retorna os parâmetros. + * + * @param string $requestUri URL da requisição + * + * @return array + */ + private function _parsePath(string $requestUri): array { $path = parse_url($requestUri, PHP_URL_PATH); @@ -65,14 +124,29 @@ private function parsePath(string $requestUri): array return explode('/', trim($path, '/')); } - private function respondError(int $statusCode, string $message): void + /** + * Função que envia a resposta de erro. + * + * @param int $statusCode Código de status da resposta + * @param string $message Mensagem de erro + * + * @return void + */ + private function _respondError(int $statusCode, string $message): void { http_response_code($statusCode); echo json_encode(['erro' => $message]); exit; } - private function respondSuccess(array $payload): void + /** + * Função que envia a resposta de sucesso. + * + * @param array $payload Payload da resposta + * + * @return void + */ + private function _respondSuccess(array $payload): void { echo json_encode($payload); } diff --git a/src/index.php b/src/index.php index 743481031..b87f510b6 100644 --- a/src/index.php +++ b/src/index.php @@ -8,7 +8,7 @@ * * @category Challenge * @package Back-end - * @author Seu Nome + * @author Kayo Almondes Tusthler * @license http://opensource.org/licenses/MIT MIT * @link https://github.com/apiki/back-end-challenge */ From 0c8358ead6cf02afc9fefab98b83bffc942411c4 Mon Sep 17 00:00:00 2001 From: Kayo Tusthler Date: Wed, 25 Mar 2026 00:34:37 -0300 Subject: [PATCH 5/6] fix: add strict type config --- src/Exchange/ExchangeHandler.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Exchange/ExchangeHandler.php b/src/Exchange/ExchangeHandler.php index f16c3aa25..b786124c0 100644 --- a/src/Exchange/ExchangeHandler.php +++ b/src/Exchange/ExchangeHandler.php @@ -14,6 +14,8 @@ * @link https://github.com/apiki/back-end-challenge */ +declare(strict_types=1); + namespace App\Exchange; /** From cb022f842229b237d33536c160642192ec37985f Mon Sep 17 00:00:00 2001 From: Kayo Tusthler Date: Thu, 26 Mar 2026 11:53:42 -0300 Subject: [PATCH 6/6] fix: changed tests to original value --- tests/ApiCest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ApiCest.php b/tests/ApiCest.php index 9be3a21c6..e92352118 100644 --- a/tests/ApiCest.php +++ b/tests/ApiCest.php @@ -68,7 +68,7 @@ public function tryApiNegativeRate(ApiTester $I) { $I->sendGET('/10/EUR/USD/-0.5'); $I->seeResponseCodeIs(400); - $I->seeResponseContainsJson(); + $I->seeResponseIsJson(); } public function tryApiBrlToUsd(ApiTester $I)