Skip to content

Commit

Permalink
Track service
Browse files Browse the repository at this point in the history
  • Loading branch information
tomas-novotny committed Jun 17, 2022
1 parent eb1aace commit f88c0a6
Show file tree
Hide file tree
Showing 21 changed files with 1,703 additions and 861 deletions.
27 changes: 27 additions & 0 deletions src/Client/Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Inspirum\Balikobot\Client;

interface Client
{
/**
* Call API server, parse and validate response data
*
* @param array<mixed,mixed> $data
*
* @return array<mixed,mixed>
*
* @throws \Inspirum\Balikobot\Exception\Exception
*/
public function call(
string $version,
?string $carrier,
string $request,
array $data = [],
string $path = '',
bool $shouldHaveStatus = true,
bool $gzip = false,
): array;
}
85 changes: 85 additions & 0 deletions src/Client/CurlRequester.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace Inspirum\Balikobot\Client;

use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use function base64_encode;
use function count;
use function curl_close;
use function curl_errno;
use function curl_error;
use function curl_exec;
use function curl_getinfo;
use function curl_init;
use function curl_setopt;
use function json_encode;
use function sprintf;
use const CURLINFO_HTTP_CODE;
use const CURLOPT_HEADER;
use const CURLOPT_HTTPHEADER;
use const CURLOPT_POST;
use const CURLOPT_POSTFIELDS;
use const CURLOPT_RETURNTRANSFER;
use const CURLOPT_SSL_VERIFYHOST;
use const CURLOPT_SSL_VERIFYPEER;
use const CURLOPT_URL;

final class CurlRequester implements Requester
{
public function __construct(
private string $apiUser,
private string $apiKey,
private bool $sslVerify = true,
) {
}

/**
* @inheritDoc
*/
public function request(string $url, array $data = []): ResponseInterface
{
// init curl
$ch = curl_init();

// set headers
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);

// disable SSL verification
if ($this->sslVerify === false) {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
}

// set data
if (count($data) > 0) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}

// set auth
curl_setopt($ch, CURLOPT_HTTPHEADER, [
sprintf('Authorization: Basic %s', base64_encode(sprintf('%s:%s', $this->apiUser, $this->apiKey))),
'Content-Type: application/json',
]);

// execute curl
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// check for errors.
if ($response === false) {
throw new RuntimeException(curl_error($ch), curl_errno($ch));
}

// close curl
curl_close($ch);

return new Response((int) $statusCode, [], (string) $response);
}
}
110 changes: 110 additions & 0 deletions src/Client/DefaultClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace Inspirum\Balikobot\Client;

use GuzzleHttp\Psr7\InflateStream;
use Inspirum\Balikobot\Exception\BadRequestException;
use Inspirum\Balikobot\Response\Validator;
use JsonException;
use Psr\Http\Message\StreamInterface;
use Throwable;
use function json_decode;
use function sprintf;
use function str_replace;
use function trim;
use const JSON_THROW_ON_ERROR;

final class DefaultClient implements Client
{
public function __construct(
private Requester $requester,
private Validator $validator,
) {
}

/**
* @inheritDoc
*/
public function call(
string $version,
?string $carrier,
string $request,
array $data = [],
string $path = '',
bool $shouldHaveStatus = true,
bool $gzip = false,
): array {
$url = $this->resolveUrl($version, $carrier, $request, $path, $gzip);

$response = $this->requester->request($url, $data);

$statusCode = $response->getStatusCode();
$contents = $this->getContents($response->getBody(), $gzip);
$parsedContent = $this->parseContents($contents, $statusCode < 300);

$this->validateResponse($statusCode, $parsedContent, $shouldHaveStatus);

return $parsedContent;
}

private function resolveUrl(string $version, ?string $carrier, string $request, string $path, bool $gzip): string
{
$url = sprintf('%s/%s/%s', $carrier, $request, $path);
$url = trim(str_replace('//', '/', $url), '/');

if ($gzip) {
$url = sprintf('%s?gzip=1', $path);
}

return sprintf('%s/%s', $version, $url);
}

/**
* @return array<mixed,mixed>
*
* @throws \Inspirum\Balikobot\Exception\Exception
*/
private function parseContents(string $content, bool $throwOnError): array
{
try {
return json_decode($content, true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
if ($throwOnError) {
throw new BadRequestException([], 400, $exception, 'Cannot parse response data');
}

return [];
}
}

private function getContents(StreamInterface $stream, bool $gzip): string
{
if ($gzip === false) {
return $stream->getContents();
}

try {
$inflateStream = new InflateStream($stream);

return $inflateStream->getContents();
} catch (Throwable) {
$stream->rewind();

return $stream->getContents();
}
}

/**
* @param array<mixed,mixed> $response
*
* @throws \Inspirum\Balikobot\Exception\Exception
*/
private function validateResponse(int $statusCode, array $response, bool $shouldHaveStatus): void
{
$this->validator->validateStatus($statusCode, $response);

$this->validator->validateResponseStatus($response, null, $shouldHaveStatus);
}
}
15 changes: 15 additions & 0 deletions src/Client/Requester.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Inspirum\Balikobot\Client;

use Psr\Http\Message\ResponseInterface;

interface Requester
{
/**
* @param array<mixed,mixed> $data
*/
public function request(string $url, array $data = []): ResponseInterface;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

declare(strict_types=1);

namespace Inspirum\Balikobot\Exceptions;
namespace Inspirum\Balikobot\Exception;

use Inspirum\Balikobot\Definitions\Response;
use Throwable;
use function is_array;
use function is_numeric;

class BadRequestException extends AbstractException
class BadRequestException extends BaseException
{
/**
* BadRequestException constructor
Expand Down Expand Up @@ -135,14 +135,14 @@ private function getErrorMessage(string $key, int $code): string
{
// get error message from code
if ($key === 'status') {
return Response::$statusCodesErrors[$code] ?? Response::$statusCodesErrors[500];
return Response::STATUS_CODE_ERRORS[$code] ?? Response::STATUS_CODE_ERRORS[500];
}

if (isset(Response::$packageDataKeyErrors[$code][$key])) {
return Response::$packageDataKeyErrors[$code][$key];
if (isset(Response::PACKAGE_DATA_KEY_ERRORS[$code][$key])) {
return Response::PACKAGE_DATA_KEY_ERRORS[$code][$key];
}

return Response::$packageDataErrors[$code] ?? 'Nespecifikovaná chyba.';
return Response::PACKAGE_DATA_ERRORS[$code] ?? 'Nespecifikovaná chyba.';
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

declare(strict_types=1);

namespace Inspirum\Balikobot\Exceptions;
namespace Inspirum\Balikobot\Exception;

use Inspirum\Balikobot\Contracts\ExceptionInterface;
use Inspirum\Balikobot\Definitions\Response;
use RuntimeException;
use Throwable;
use function implode;
use function json_encode;
use function sprintf;

abstract class AbstractException extends RuntimeException implements ExceptionInterface
abstract class BaseException extends RuntimeException implements Exception
{
/**
* Response data
Expand Down Expand Up @@ -57,7 +56,7 @@ public function __construct(

// overwrite default message
if ($message === null) {
$message = Response::$statusCodesErrors[$statusCode] ?? 'Operace neproběhla v pořádku.';
$message = Response::STATUS_CODE_ERRORS[$statusCode] ?? 'Operace neproběhla v pořádku.';
$message = $this->getMessageWithErrors($message);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

declare(strict_types=1);

namespace Inspirum\Balikobot\Contracts;
namespace Inspirum\Balikobot\Exception;

use Throwable;

interface ExceptionInterface extends Throwable
interface Exception extends Throwable
{
/**
* Get response HTTP status code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

declare(strict_types=1);

namespace Inspirum\Balikobot\Exceptions;
namespace Inspirum\Balikobot\Exception;

use Throwable;

class UnauthorizedException extends AbstractException
class UnauthorizedException extends BaseException
{
/**
* UnauthorizedException constructor
Expand Down
Loading

0 comments on commit f88c0a6

Please sign in to comment.